{"record":{"id":"f186f0a57b6e4de8","repo":"TryGhost/Ghost","slug":"latest-stripe-checkout-session-does-not-include-a-f186f0","errorCode":null,"errorMessage":"Latest Stripe checkout session does not include a success URL","messagePattern":"Latest Stripe checkout session does not include a success URL","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"e2e/helpers/playwright/flows/upgrade.ts","lineNumber":11,"sourceCode":"import {FakeStripeCheckoutPage, HomePage, PortalAccountPage, PortalAccountPlanPage} from '@/helpers/pages';\nimport {signInAsMember} from './sign-in';\nimport type {Member} from '@/data-factory';\nimport type {Page} from '@playwright/test';\nimport type {StripeTestService} from '@/helpers/services/stripe';\n\nfunction getLatestCheckoutSuccessUrl(stripe: StripeTestService): string {\n    const successUrl = stripe.getCheckoutSessions().at(-1)?.response.success_url;\n\n    if (!successUrl) {\n        throw new Error('Latest Stripe checkout session does not include a success URL');\n    }\n\n    return successUrl;\n}\n\nexport async function completePaidUpgradeViaPortal(page: Page, stripe: StripeTestService, member: Member, opts: {\n    cadence: 'monthly' | 'yearly';\n    tierName: string;\n}): Promise<void> {\n    await signInAsMember(page, member);\n\n    const homePage = new HomePage(page);\n    await homePage.openAccountPortal();\n\n    const portalAccountPage = new PortalAccountPage(page);\n    await portalAccountPage.waitForPortalToOpen();\n    await portalAccountPage.openPlanSelection();\n","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/helpers/playwright/flows/upgrade.ts#L1-L29","documentation":"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`.","triggerScenarios":"The paid-upgrade-via-portal flow completes checkout (`completeLatestSubscriptionCheckout`), but the recorded sessions array is empty or the last session lacks `success_url`.","commonSituations":"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.","solutions":["Confirm the member is genuinely changing plans/cadence so a checkout session is created.","Verify `completeLatestSubscriptionCheckout` ran after the portal plan change was submitted.","Inspect `stripe.getCheckoutSessions()` length and the last session's shape before the helper is called.","Ensure the target tier is Stripe-synced before attempting the upgrade."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const sessions = stripe.getCheckoutSessions();\nif (!sessions.length || !sessions.at(-1)?.response?.success_url) {\n    throw new Error(`Upgrade checkout not recorded (${sessions.length} sessions)`);\n}","typeGuard":"function isCheckoutSessionWithSuccessUrl(s: unknown): s is {response: {success_url: string}} {\n    return !!s && typeof (s as any).response?.success_url === 'string';\n}","tryCatchPattern":null,"preventionTips":["Ensure the member is genuinely changing plan/cadence so a checkout is created.","Sync the target tier to Stripe before the upgrade.","Prefer the shared `getLatestCheckoutSuccessUrl` helper over re-implementing the check."],"tags":["stripe","e2e","checkout","upgrade","subscriptions"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}