fix(checkout): call payment composables at setup, not mid-flow - #651
Merged
Conversation
Setting a saved card as default blew up with `SyntaxError: 26` and the verification never completed. 26 is vue-i18n's MUST_BE_CALL_SETUP_TOP: in a production build the message text is stripped and only the numeric code survives, which is why it read as a bare SyntaxError. `useCardThreeDSecure` called `usePaymentCards()` inside `verifyCardWith3DS`, an async function reached from `initialize` after two awaits. By then there is no current component instance, so the `useI18n()` at the top of `usePaymentCards` throws. The composable already destructured `setDefaultCard` from a call at its own top, so this just takes `createCardNonce` from there too. `useThreeDSecure` had the same shape in `verifyAndFinalizePayment`, with a comment claiming these "can only be used at component level" — the opposite of how setup-bound composables work. Those two survived only because neither `usePaymentApi` nor `useAccountRefresh` happens to reach `useI18n`, so it was latent rather than broken. Hoisted, and the comment dropped. `useThreeDSecure` has a single caller, at setup in `3d-secure.vue`. Swept the workspace for other setup-bound composables initialized inside a function. The rest are fine: the calls in `plugins/startup.ts` run synchronously inside the plugin context before its only await, and `use-oauth.ts` uses `useTimeoutFn` deliberately, since `@rotki/composable-require-cleanup` wants a cleanup-registering helper rather than a bare setTimeout.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #651 +/- ##
=======================================
Coverage 27.92% 27.92%
=======================================
Files 391 391
Lines 12457 12456 -1
Branches 1945 1945
=======================================
Hits 3478 3478
+ Misses 8880 8879 -1
Partials 99 99
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Setting a saved card as default fails, and the verification never completes:
26is vue-i18n'sMUST_BE_CALL_SETUP_TOP. Production builds strip the message text and pass the bare numeric code tonew SyntaxError(String(code)), which is why it surfaces as an opaqueSyntaxError: 26rather than "Must be called at the top of asetupfunction".Cause
useCardThreeDSecurecalledusePaymentCards()insideverifyCardWith3DS:That function is reached from
initializeafterawait create3DSecureInstance(...)andawait fetchPaymentMethodBin(...). By then there is no current component instance, so theuseI18n()at the top ofusePaymentCardsthrows.The composable already destructured
setDefaultCardfrom ausePaymentCards()call at its own top, so the fix is just to takecreateCardNoncefrom that same call. One line moved.Same shape, one file over
useThreeDSecure.verifyAndFinalizePaymentdid the same thing, under a comment asserting the opposite of how setup-bound composables work:Those two only survive because neither
usePaymentApinoruseAccountRefreshhappens to reachuseI18n— latent rather than broken, and it would start throwing the moment either grew a setup-bound dependency. Hoisted to the composable top and the comment dropped.useThreeDSecurehas exactly one caller, at setup in3d-secure.vue, so hoisting is safe.The rest of the sweep
I swept the workspace for setup-bound composables initialized inside a function body. Everything else that matched is fine, and deliberately left alone:
plugins/startup.ts—useUtmTracking,useReferralTracking,useAuthHintCookie,useMainStoreare insideif (import.meta.client)blocks, but run synchronously within thedefineNuxtPlugincontext, before the plugin's onlyawait.use-oauth.ts—useTimeoutFn()in two redirect handlers. I did try replacing these with plainsetTimeoutand reverted:@rotki/composable-require-cleanupflags a baresetTimeoutin a composable, which is exactly why the cleanup-registering helper is there. It also never throws, sincetryOnScopeDisposeis a no-op outside a scope.Scope
Pre-existing on main, from the 3DS work — not from the dependency PRs. It surfaced while smoke-testing the #650 build.
typecheck clean, lint clean (68 pre-existing warnings, 0 errors), 502 tests pass,
generatebuilds.CI cannot exercise this path, so it still wants the real check on staging: set a saved card as default, and re-authorize one, both of which route through
verifyCardWith3DS.