TryGhost/Ghost · error · Error

Latest Stripe checkout session does not include a success UR

Error message

Latest Stripe checkout session does not include a success URL

What it means

Thrown by the shared helper `getLatestCheckoutSuccessUrl` used in upgrade flows. It reads `stripe.getCheckoutSessions().at(-1)?.response.success_url` and throws if absent, centralizing the same check duplicated across donations/offers/signup flows. Failure means no upgrade checkout session was recorded or its response omits `success_url`.

Source

Thrown at e2e/helpers/playwright/flows/upgrade.ts:11

import {FakeStripeCheckoutPage, HomePage, PortalAccountPage, PortalAccountPlanPage} from '@/helpers/pages';
import {signInAsMember} from './sign-in';
import type {Member} from '@/data-factory';
import type {Page} from '@playwright/test';
import type {StripeTestService} from '@/helpers/services/stripe';

function getLatestCheckoutSuccessUrl(stripe: StripeTestService): string {
    const successUrl = stripe.getCheckoutSessions().at(-1)?.response.success_url;

    if (!successUrl) {
        throw new Error('Latest Stripe checkout session does not include a success URL');
    }

    return successUrl;
}

export async function completePaidUpgradeViaPortal(page: Page, stripe: StripeTestService, member: Member, opts: {
    cadence: 'monthly' | 'yearly';
    tierName: string;
}): Promise<void> {
    await signInAsMember(page, member);

    const homePage = new HomePage(page);
    await homePage.openAccountPortal();

    const portalAccountPage = new PortalAccountPage(page);
    await portalAccountPage.waitForPortalToOpen();
    await portalAccountPage.openPlanSelection();

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Confirm the member is genuinely changing plans/cadence so a checkout session is created.
  2. Verify `completeLatestSubscriptionCheckout` ran after the portal plan change was submitted.
  3. Inspect `stripe.getCheckoutSessions()` length and the last session's shape before the helper is called.
  4. Ensure the target tier is Stripe-synced before attempting the upgrade.
Defensive patterns

Strategy: validation

Validate before calling

const sessions = stripe.getCheckoutSessions();
if (!sessions.length || !sessions.at(-1)?.response?.success_url) {
    throw new Error(`Upgrade checkout not recorded (${sessions.length} sessions)`);
}

Type guard

function isCheckoutSessionWithSuccessUrl(s: unknown): s is {response: {success_url: string}} {
    return !!s && typeof (s as any).response?.success_url === 'string';
}

Prevention

When it happens

Trigger: The paid-upgrade-via-portal flow completes checkout (`completeLatestSubscriptionCheckout`), but the recorded sessions array is empty or the last session lacks `success_url`.

Common situations: Member upgrade did not actually create a new Stripe checkout (e.g., already on the target plan); Stripe test service did not capture the upgrade checkout; portal plan-change flow navigated away before checkout creation; mock response shape change.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/f186f0a57b6e4de8. Report an issue: GitHub.