koala73/worldmonitor · error · Error

Dodo checkout session ${session.session_id} has no checkout_

Error message

Dodo checkout session ${session.session_id} has no checkout_url

What it means

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.

Source

Thrown at convex/lib/dodo.ts:86

    maxRetries: 0,
    timeout: CHECKOUT_PROVIDER_ATTEMPT_TIMEOUT_MS,
  };
}

/**
 * Create one checkout session — exactly one HTTP request (no SDK-internal
 * retries). Throws the SDK's typed APIError on failure (status 429 for rate
 * limits, classified by payments/checkoutRateLimit.ts).
 */
export async function createDodoCheckoutSession(
  payload: CheckoutSessionPayload,
): Promise<CheckoutSessionResult> {
  const client = new DodoPayments(buildCheckoutClientOptions(process.env));
  const session = await client.checkoutSessions.create(payload);
  if (!session.checkout_url) {
    // Session created but no redirect URL — surface as a hard (non-429)
    // failure on the existing error channel rather than returning a dead link.
    throw new Error(
      `Dodo checkout session ${session.session_id} has no checkout_url`,
    );
  }
  return { checkout_url: session.checkout_url };
}

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Log `session.session_id` and the full session object to diagnose which field Dodo omitted.
  2. Verify `DODO_PAYMENTS_ENVIRONMENT` matches the key's mode (test_mode vs live_mode) in the Convex dashboard.
  3. Check the `payload` passed to `createDodoCheckoutSession` against the Dodo API spec — required fields for URL generation must be present.
  4. 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.
  5. If this is a known provider regression, pin a Dodo SDK version that returns the URL reliably.

Example fix

// before — no visibility into the malformed session
const { checkout_url } = await createDodoCheckoutSession(payload);

// after — catch and surface a clear error with the session id
try {
  const { checkout_url } = await createDodoCheckoutSession(payload);
  window.location.href = checkout_url;
} catch (err) {
  if (err.message?.includes("no checkout_url")) {
    console.error("Dodo returned session without checkout_url", err.message);
    showUserError("Checkout is temporarily unavailable. Please try again.");
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify env and payload shape before creating a session
if (!process.env.DODO_API_KEY) throw new Error("DODO_API_KEY not set");
if (!payload.product_id || !payload.amount) throw new Error("payload missing required fields");

Type guard

function hasCheckoutUrl(session: unknown): session is { checkout_url: string } {
  return typeof (session as any)?.checkout_url === "string" && (session as any).checkout_url.length > 0;
}

Try / catch

try {
  const { checkout_url } = await createDodoCheckoutSession(payload);
  window.location.href = checkout_url;
} catch (err) {
  if (err.message?.includes("no checkout_url")) {
    console.error("Dodo session malformed", err.message);
    showUserError("Checkout is temporarily unavailable.");
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/8ee366c78c520a8e. Report an issue: GitHub.