koala73/worldmonitor · warning · ConvexError
PAYMENT_IN_PROGRESS
PAYMENT_IN_PROGRESS
Error message
A ${pending.displayName} payment is already in progress for this account. It may still be completing — finish it, or start a new checkout. What it means
`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.
Source
Thrown at convex/payments/checkout.ts:371
// pending-payment block. Logged server-side so a future double-charge
// investigation has the bypass record (#4438 review — the original
// incident was undetected stacked payments).
console.info(`[checkout] pending-payment guard bypassed user=${userId} product=${args.productId}`);
}
// Run both guards concurrently — they share no data, so serial awaits only
// add a Convex round-trip to every checkout (#4438 review). Subscription
// block still WINS (evaluated first); bypass skips the pending query.
const [blocking, pending] = await Promise.all([
getCheckoutBlockingSubscription(ctx, userId, args.productId),
args.bypassPendingGuard
? Promise.resolve(null)
: getCheckoutBlockingPendingPayment(ctx, userId, args.productId),
]);
if (blocking) {
throw new ConvexError(buildBlockedCheckoutPayload(blocking));
}
if (pending) {
throw new ConvexError(buildPendingBlockedPayload(pending));
}
const customerName = identity
? [identity.givenName, identity.familyName].filter(Boolean).join(" ") ||
identity.name
: undefined;
const result = await _createCheckoutSession(args, {
userId,
email: identity?.email,
name: customerName,
});
// The public Convex action historically rejects provider failures. Keep
// that error-channel contract: only the trusted internal relay consumes
// the typed outcome and translates it into HTTP 429 + Retry-After.
if (isCheckoutRateLimitedOutcome(result)) {
throw new ConvexError({
code: CHECKOUT_RATE_LIMITED,View on GitHub (pinned to ffec79ac33)
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
Example fix
// before
createCheckout({ productId })
// after — user-confirmed override
createCheckout({ productId, bypassPendingGuard: true }) Defensive patterns
Strategy: validation
Validate before calling
// Disable the purchase button after first click to avoid a second in-flight payment.
let submitting = false;
async function onPurchase() {
if (submitting) return;
submitting = true;
try { await convex.action(api.payments.checkout.createCheckout, args); }
finally { submitting = false; }
} Try / catch
try {
await convex.action(api.payments.checkout.createCheckout, args);
} catch (err) {
if (err.data?.code === 'PAYMENT_IN_PROGRESS') {
// offer 'finish existing' or, with explicit user confirm, retry with bypassPendingGuard: true
} else { throw err; }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Accidental double-click; user re-checks out after a slow provider response; stale pending-payment row left by an abandoned session.
Related errors
- ACTIVE_SUBSCRIPTION_EXISTS
- Dodo checkout session ${session.session_id} has no checkout_
- Invalid returnUrl: must be a valid absolute URL
- Invalid returnUrl: must use a trusted worldmonitor.app origi
- Checkout failed: ${msg}
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/536a0d1d077c5dc4.
Report an issue: GitHub.