Detail Bug Report
https://app.detail.dev/org_ecfbc7cf-6d21-4ce1-8c61-cda1d650ccf7/bugs/bug_10ecc79a-80c4-43f6-bb1f-1109f0c5c35a
Introduced in #11941 by @sebastianekstrom on May 27, 2026
Summary
- Context: The
useCheckoutConfirmedRedirect hook has a bug on line 55 where it checks embed (the URL parameter) instead of checkout.embed_origin (the server-side field) to determine whether to wait for payment fulfillment before sending the success postMessage.
- Bug: It uses
embed to decide whether to await listenFulfillment, but the intended condition is whether checkout.embed_origin is set.
- Actual vs. expected: Actual: when
embed=false but checkout.embed_origin is set, the hook sends a success postMessage immediately without waiting for fulfillment. Expected: it should wait for fulfillment whenever checkout.embed_origin is set (or when the success URL is external).
- Impact: When
embed=false but checkout.embed_origin is set, the hook sends a success postMessage immediately without waiting for fulfillment. Merchants may receive a success message for payments that later fail during fulfillment.
Code with Bug
// clients/apps/web/src/hooks/checkout.ts
if ((!isInternalSuccessURL || embed) && listenFulfillment) {
// <-- BUG 🔴 checks URL param `embed` instead of `checkout.embed_origin`, so fulfillment wait is skipped when embed_origin is set but embed=false
Explanation
The fulfillment wait is meant to prevent declaring success before the payment reaches a terminal state when either:
- the redirect destination can’t observe in-flight state (external success URL), or
- a merchant is listening for postMessages on the parent page (
checkout.embed_origin).
However, the hook gates the wait on embed (UI chrome flag derived from ?embed=true) rather than checkout.embed_origin (server-provided communication address). This creates a real gap in flows where embed_origin is set server-side but the checkout URL does not include ?embed=true.
A concrete triggering flow exists via the checkout link redirect endpoint, which accepts embed_origin as a query parameter, persists it on the checkout session, and redirects to a checkout URL that does not include embed_origin or embed=true. In that case the frontend sees embed=false but checkout.embed_origin is set, so it skips listenFulfillment and can post success too early.
Codebase Inconsistency
Within the same hook, checkout.embed_origin is already used to decide whether to send postMessages (e.g., confirmed and success). The fulfillment wait should use the same signal (merchant listener presence), but it is the only place using embed instead.
Failing Test
it('waits for listenFulfillment when embed_origin is set (even without embed=true)', async () => {
const listenFulfillment = vi.fn(() => Promise.resolve())
await callHook({
embed: false,
listenFulfillment,
checkout: baseCheckout({ embed_origin: EMBED_ORIGIN }),
})
expect(listenFulfillment).toHaveBeenCalledTimes(1)
})
Test output:
FAIL src/hooks/checkout.test.ts > useCheckoutConfirmedRedirect > waits for listenFulfillment when embed_origin is set (even without embed=true)
AssertionError: expected "vi.fn()" to be called 1 times, but got 0 times
Recommended Fix
Change the fulfillment-wait condition to check checkout.embed_origin:
if ((!isInternalSuccessURL || checkout.embed_origin) && listenFulfillment) {
History
This bug was introduced in commit ddffa36. The commit added logic to wait for payment fulfillment before emitting a success event to embedded checkouts, expanding the existing condition from !isInternalSuccessURL to (!isInternalSuccessURL || embed). The bug is a typo: the author wrote embed (the URL parameter controlling UI chrome) when the accompanying comment explicitly states the check should be for "a merchant is listening on the parent page (embed_origin)" — meaning checkout.embed_origin was the intended variable.
Detail Bug Report
https://app.detail.dev/org_ecfbc7cf-6d21-4ce1-8c61-cda1d650ccf7/bugs/bug_10ecc79a-80c4-43f6-bb1f-1109f0c5c35a
Introduced in #11941 by @sebastianekstrom on May 27, 2026
Summary
useCheckoutConfirmedRedirecthook has a bug on line 55 where it checksembed(the URL parameter) instead ofcheckout.embed_origin(the server-side field) to determine whether to wait for payment fulfillment before sending thesuccesspostMessage.embedto decide whether to awaitlistenFulfillment, but the intended condition is whethercheckout.embed_originis set.embed=falsebutcheckout.embed_originis set, the hook sends asuccesspostMessage immediately without waiting for fulfillment. Expected: it should wait for fulfillment whenevercheckout.embed_originis set (or when the success URL is external).embed=falsebutcheckout.embed_originis set, the hook sends asuccesspostMessage immediately without waiting for fulfillment. Merchants may receive a success message for payments that later fail during fulfillment.Code with Bug
Explanation
The fulfillment wait is meant to prevent declaring success before the payment reaches a terminal state when either:
checkout.embed_origin).However, the hook gates the wait on
embed(UI chrome flag derived from?embed=true) rather thancheckout.embed_origin(server-provided communication address). This creates a real gap in flows whereembed_originis set server-side but the checkout URL does not include?embed=true.A concrete triggering flow exists via the checkout link redirect endpoint, which accepts
embed_originas a query parameter, persists it on the checkout session, and redirects to a checkout URL that does not includeembed_originorembed=true. In that case the frontend seesembed=falsebutcheckout.embed_originis set, so it skipslistenFulfillmentand can postsuccesstoo early.Codebase Inconsistency
Within the same hook,
checkout.embed_originis already used to decide whether to send postMessages (e.g.,confirmedandsuccess). The fulfillment wait should use the same signal (merchant listener presence), but it is the only place usingembedinstead.Failing Test
Test output:
Recommended Fix
Change the fulfillment-wait condition to check
checkout.embed_origin:History
This bug was introduced in commit ddffa36. The commit added logic to wait for payment fulfillment before emitting a success event to embedded checkouts, expanding the existing condition from
!isInternalSuccessURLto(!isInternalSuccessURL || embed). The bug is a typo: the author wroteembed(the URL parameter controlling UI chrome) when the accompanying comment explicitly states the check should be for "a merchant is listening on the parent page (embed_origin)" — meaningcheckout.embed_originwas the intended variable.