export const CHECKOUT_RATE_LIMITED = "CHECKOUT_RATE_LIMITED"; export const CHECKOUT_RETRY_AFTER_SECONDS = 10; export interface CheckoutRateLimitedOutcome { checkoutFailed: true; code: typeof CHECKOUT_RATE_LIMITED; retryAfterSeconds: number; } /** * Dodo's Convex component throws a plain Error for upstream HTTP failures. * Preserve only the observed, unambiguous 429 shape as a typed outcome so the * relay can return 429 instead of collapsing it into a retry-amplifying 502. */ export function checkoutRateLimitedOutcomeFromError( error: unknown, ): CheckoutRateLimitedOutcome | null { const message = error instanceof Error ? error.message : String(error); if (!/\b429\b.*(?:status code|too many requests|rate limit)/i.test(message)) { return null; } return { checkoutFailed: true, code: CHECKOUT_RATE_LIMITED, retryAfterSeconds: CHECKOUT_RETRY_AFTER_SECONDS, }; } export function isCheckoutRateLimitedOutcome( value: unknown, ): value is CheckoutRateLimitedOutcome { if (typeof value !== "object" || value === null) return false; const candidate = value as Partial; return ( candidate.checkoutFailed === true && candidate.code === CHECKOUT_RATE_LIMITED && candidate.retryAfterSeconds === CHECKOUT_RETRY_AFTER_SECONDS ); }