{"record":{"id":"4e6b2f62895e66c5","repo":"koala73/worldmonitor","slug":"active-subscription-exists","errorCode":"ACTIVE_SUBSCRIPTION_EXISTS","errorMessage":"A ${subscription.displayName} subscription already exists for this account. Use Manage Billing to update it instead of purchasing again.","messagePattern":"A (.+?) subscription already exists for this account\\. Use Manage Billing to update it instead of purchasing again\\.","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"warning","filePath":"convex/payments/checkout.ts","lineNumber":368,"sourceCode":"    const identity = await resolveUserIdentity(ctx);\n    if (args.bypassPendingGuard) {\n      // Audit trail: the user confirmed \"start a new checkout anyway\" past a\n      // 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.","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/payments/checkout.ts#L350-L386","documentation":"`createCheckout` runs `getCheckoutBlockingSubscription` first; if the user already has an active subscription that blocks this product, checkout is refused and the error carries `buildBlockedCheckoutPayload(blocking)` with `ACTIVE_SUBSCRIPTION_EXISTS`. The message directs the user to Manage Billing rather than re-purchasing. This guard always runs, even with `bypassPendingGuard`.","triggerScenarios":"Calling `createCheckout` for a product the user already actively subscribes to; attempting to stack an identical subscription.","commonSituations":"User forgot they are already subscribed; double-purchase attempt; UI failed to reflect existing subscription state.","solutions":["Use Manage Billing to update/cancel the existing subscription instead of purchasing again","Cancel or let the existing subscription lapse before re-purchasing","Reflect the existing subscription in the purchase UI to prevent the attempt"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reflect existing subscription state before offering purchase.\nconst sub = await convex.query(api.payments.subscriptions.activeForProduct, { productId: args.productId });\nif (sub) { /* route to Manage Billing instead of createCheckout */ }","typeGuard":null,"tryCatchPattern":"try {\n  await convex.action(api.payments.checkout.createCheckout, args);\n} catch (err) {\n  if (err.data?.code === 'ACTIVE_SUBSCRIPTION_EXISTS') {\n    // redirect to Manage Billing\n  } else { throw err; }\n}","preventionTips":["Show 'Manage Billing' instead of 'Purchase' when an active subscription exists","Refresh subscription state before presenting the purchase UI"],"tags":["payments","checkout","subscription","guard"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}