Skip to content

fix(checkout): call payment composables at setup, not mid-flow - #651

Merged
kelsos merged 1 commit into
rotki:mainfrom
kelsos:fix/3ds-setup-context
Aug 4, 2026
Merged

fix(checkout): call payment composables at setup, not mid-flow#651
kelsos merged 1 commit into
rotki:mainfrom
kelsos:fix/3ds-setup-context

Conversation

@kelsos

@kelsos kelsos commented Aug 4, 2026

Copy link
Copy Markdown
Member

Setting a saved card as default fails, and the verification never completes:

payment-methods:error 3DS verification failed: SyntaxError: 26
    at Da (vue-core…)   ← vue-i18n
    at $  (saved-cards…)
    at async h (saved-cards…)
    at async I (saved-cards…)

26 is vue-i18n's MUST_BE_CALL_SETUP_TOP. Production builds strip the message text and pass the bare numeric code to new SyntaxError(String(code)), which is why it surfaces as an opaque SyntaxError: 26 rather than "Must be called at the top of a setup function".

Cause

useCardThreeDSecure called usePaymentCards() inside verifyCardWith3DS:

async function verifyCardWith3DS(...) {
  const { createCardNonce } = usePaymentCards();   // ← here

That function is reached from initialize after await create3DSecureInstance(...) and await fetchPaymentMethodBin(...). By then there is no current component instance, so the useI18n() at the top of usePaymentCards throws.

The composable already destructured setDefaultCard from a usePaymentCards() call at its own top, so the fix is just to take createCardNonce from that same call. One line moved.

Same shape, one file over

useThreeDSecure.verifyAndFinalizePayment did the same thing, under a comment asserting the opposite of how setup-bound composables work:

// These composables need to be inside the function since they can only be used at component level
const paymentApi = usePaymentApi();
const { requestRefresh } = useAccountRefresh();

Those two only survive because neither usePaymentApi nor useAccountRefresh happens to reach useI18n — 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. useThreeDSecure has exactly one caller, at setup in 3d-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.tsuseUtmTracking, useReferralTracking, useAuthHintCookie, useMainStore are inside if (import.meta.client) blocks, but run synchronously within the defineNuxtPlugin context, before the plugin's only await.
  • use-oauth.tsuseTimeoutFn() in two redirect handlers. I did try replacing these with plain setTimeout and reverted: @rotki/composable-require-cleanup flags a bare setTimeout in a composable, which is exactly why the cleanup-registering helper is there. It also never throws, since tryOnScopeDispose is 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, generate builds.

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.

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.
@kelsos
kelsos requested a review from a team as a code owner August 4, 2026 15:16
@codecov-commenter

codecov-commenter commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 27.92%. Comparing base (67c1d81) to head (ba65f5f).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...modules/checkout/composables/use-three-d-secure.ts 0.00% 2 Missing ⚠️
...es/checkout/composables/use-card-three-d-secure.ts 0.00% 1 Missing ⚠️
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           
Flag Coverage Δ
backend 56.55% <ø> (ø)
frontend 18.72% <0.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kelsos
kelsos merged commit ba65f5f into rotki:main Aug 4, 2026
9 checks passed
@kelsos
kelsos deleted the fix/3ds-setup-context branch August 4, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants