{"record":{"id":"8ee366c78c520a8e","repo":"koala73/worldmonitor","slug":"dodo-checkout-session-session-session-id-has-no","errorCode":null,"errorMessage":"Dodo checkout session ${session.session_id} has no checkout_url","messagePattern":"Dodo checkout session (.+?) has no checkout_url","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/lib/dodo.ts","lineNumber":86,"sourceCode":"    maxRetries: 0,\n    timeout: CHECKOUT_PROVIDER_ATTEMPT_TIMEOUT_MS,\n  };\n}\n\n/**\n * Create one checkout session — exactly one HTTP request (no SDK-internal\n * retries). Throws the SDK's typed APIError on failure (status 429 for rate\n * limits, classified by payments/checkoutRateLimit.ts).\n */\nexport async function createDodoCheckoutSession(\n  payload: CheckoutSessionPayload,\n): Promise<CheckoutSessionResult> {\n  const client = new DodoPayments(buildCheckoutClientOptions(process.env));\n  const session = await client.checkoutSessions.create(payload);\n  if (!session.checkout_url) {\n    // Session created but no redirect URL — surface as a hard (non-429)\n    // failure on the existing error channel rather than returning a dead link.\n    throw new Error(\n      `Dodo checkout session ${session.session_id} has no checkout_url`,\n    );\n  }\n  return { checkout_url: session.checkout_url };\n}\n","sourceCodeStart":68,"sourceCodeEnd":92,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/lib/dodo.ts#L68-L92","documentation":"Thrown by `createDodoCheckoutSession` after the DodoPayments SDK's `checkoutSessions.create()` returns a session object whose `checkout_url` is null/empty. The session was created server-side (so the API call succeeded) but no redirect URL came back — surfacing it as a hard (non-429) Error rather than returning a dead link to the user. This is a plain `Error` (NOT a ConvexError) — it propagates through the action layer and is classified by `payments/checkoutRateLimit.ts`. Note the client is built with `maxRetries: 0` and a 3.5s per-attempt timeout so the bounded retry ladder owns all retry policy.","triggerScenarios":"DodoPayments API returns 200 with a session that lacks `checkout_url` (provider bug, partial response, or a test-mode quirk); the `DODO_PAYMENTS_ENVIRONMENT` is misconfigured so the session is created in the wrong mode; a payload field that Dodo requires for URL generation was omitted. Distinct from a 429 (rate limit) which throws the SDK's `APIError` and is retried.","commonSituations":"Provider outage returning malformed responses; test_mode vs live_mode mismatch; a Dodo API version change that renames or omits the field; a payload missing a required product/amount field so Dodo creates a session shell without a checkout URL.","solutions":["Log `session.session_id` and the full session object to diagnose which field Dodo omitted.","Verify `DODO_PAYMENTS_ENVIRONMENT` matches the key's mode (test_mode vs live_mode) in the Convex dashboard.","Check the `payload` passed to `createDodoCheckoutSession` against the Dodo API spec — required fields for URL generation must be present.","Retry the checkout once at the action layer only if transient; do NOT retry on a deterministic provider bug — surface a user-facing error and alert ops.","If this is a known provider regression, pin a Dodo SDK version that returns the URL reliably."],"exampleFix":"// before — no visibility into the malformed session\nconst { checkout_url } = await createDodoCheckoutSession(payload);\n\n// after — catch and surface a clear error with the session id\ntry {\n  const { checkout_url } = await createDodoCheckoutSession(payload);\n  window.location.href = checkout_url;\n} catch (err) {\n  if (err.message?.includes(\"no checkout_url\")) {\n    console.error(\"Dodo returned session without checkout_url\", err.message);\n    showUserError(\"Checkout is temporarily unavailable. Please try again.\");\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"// Verify env and payload shape before creating a session\nif (!process.env.DODO_API_KEY) throw new Error(\"DODO_API_KEY not set\");\nif (!payload.product_id || !payload.amount) throw new Error(\"payload missing required fields\");","typeGuard":"function hasCheckoutUrl(session: unknown): session is { checkout_url: string } {\n  return typeof (session as any)?.checkout_url === \"string\" && (session as any).checkout_url.length > 0;\n}","tryCatchPattern":"try {\n  const { checkout_url } = await createDodoCheckoutSession(payload);\n  window.location.href = checkout_url;\n} catch (err) {\n  if (err.message?.includes(\"no checkout_url\")) {\n    console.error(\"Dodo session malformed\", err.message);\n    showUserError(\"Checkout is temporarily unavailable.\");\n  } else throw err;\n}","preventionTips":["Verify DODO_API_KEY and DODO_PAYMENTS_ENVIRONMENT mode match in the Convex dashboard.","Check the payload against the Dodo API spec for all URL-generating fields.","Log session.session_id on failure for provider-side diagnosis.","This is a plain Error (not ConvexError) — it flows through the action retry ladder; don't retry a deterministic provider bug.","Pin a Dodo SDK version known to return checkout_url."],"tags":["payments","dodo","checkout","provider","convex"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}