{"record":{"id":"536a0d1d077c5dc4","repo":"koala73/worldmonitor","slug":"payment-in-progress","errorCode":"PAYMENT_IN_PROGRESS","errorMessage":"A ${pending.displayName} payment is already in progress for this account. It may still be completing — finish it, or start a new checkout.","messagePattern":"A (.+?) payment is already in progress for this account\\. It may still be completing — finish it, or start a new checkout\\.","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"warning","filePath":"convex/payments/checkout.ts","lineNumber":371,"sourceCode":"      // pending-payment block. Logged server-side so a future double-charge\n      // investigation has the bypass record (#4438 review — the original\n      // incident was undetected stacked payments).\n      console.info(`[checkout] pending-payment guard bypassed user=${userId} product=${args.productId}`);\n    }\n    // Run both guards concurrently — they share no data, so serial awaits only\n    // add a Convex round-trip to every checkout (#4438 review). Subscription\n    // block still WINS (evaluated first); bypass skips the pending query.\n    const [blocking, pending] = await Promise.all([\n      getCheckoutBlockingSubscription(ctx, userId, args.productId),\n      args.bypassPendingGuard\n        ? Promise.resolve(null)\n        : getCheckoutBlockingPendingPayment(ctx, userId, args.productId),\n    ]);\n    if (blocking) {\n      throw new ConvexError(buildBlockedCheckoutPayload(blocking));\n    }\n    if (pending) {\n      throw new ConvexError(buildPendingBlockedPayload(pending));\n    }\n\n    const customerName = identity\n      ? [identity.givenName, identity.familyName].filter(Boolean).join(\" \") ||\n        identity.name\n      : undefined;\n\n    const result = await _createCheckoutSession(args, {\n      userId,\n      email: identity?.email,\n      name: customerName,\n    });\n    // The public Convex action historically rejects provider failures. Keep\n    // that error-channel contract: only the trusted internal relay consumes\n    // the typed outcome and translates it into HTTP 429 + Retry-After.\n    if (isCheckoutRateLimitedOutcome(result)) {\n      throw new ConvexError({\n        code: CHECKOUT_RATE_LIMITED,","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/payments/checkout.ts#L353-L389","documentation":"`createCheckout`'s second guard, `getCheckoutBlockingPendingPayment`, detects an in-flight payment for the same product and refuses with `buildPendingBlockedPayload(pending)` / `PAYMENT_IN_PROGRESS`. This prevents stacked charges from double-clicks or back-navigation. Unlike the subscription guard, it can be skipped by passing `bypassPendingGuard: true` after explicit user confirmation.","triggerScenarios":"Starting a second checkout while a previous payment for the same product is still completing; double-click on the purchase button; navigating back and re-submitting.","commonSituations":"Accidental double-click; user re-checks out after a slow provider response; stale pending-payment row left by an abandoned session.","solutions":["Wait for the in-progress payment to settle, then retry if needed","If the user has explicitly confirmed 'start a new checkout anyway', pass `bypassPendingGuard: true` (the bypass is audit-logged server-side)","Disable the purchase button after first click to prevent duplicate submission"],"exampleFix":"// before\ncreateCheckout({ productId })\n// after — user-confirmed override\ncreateCheckout({ productId, bypassPendingGuard: true })","handlingStrategy":"validation","validationCode":"// Disable the purchase button after first click to avoid a second in-flight payment.\nlet submitting = false;\nasync function onPurchase() {\n  if (submitting) return;\n  submitting = true;\n  try { await convex.action(api.payments.checkout.createCheckout, args); }\n  finally { submitting = false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await convex.action(api.payments.checkout.createCheckout, args);\n} catch (err) {\n  if (err.data?.code === 'PAYMENT_IN_PROGRESS') {\n    // offer 'finish existing' or, with explicit user confirm, retry with bypassPendingGuard: true\n  } else { throw err; }\n}","preventionTips":["Disable the checkout button immediately after first click","Only pass bypassPendingGuard: true after explicit user confirmation — the bypass is audit-logged","Wait for the prior payment to settle before re-attempting"],"tags":["payments","checkout","pending","double-charge","guard"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}