{"record":{"id":"5d16e5f43e5f67c4","repo":"TryGhost/Ghost","slug":"unable-to-create-stripe-checkout-session","errorCode":null,"errorMessage":"Unable to create stripe checkout session","messagePattern":"Unable to create stripe checkout session","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/portal/src/utils/api.js","lineNumber":758,"sourceCode":"                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);\n                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');\n                cancelUrl = checkoutCancelUrl.href;\n            }\n            return makeRequest({\n                url,\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json'\n                },\n                body: JSON.stringify({\n                    identity: identity,\n                    subscription_id: subscriptionId,\n                    successUrl,\n                    cancelUrl\n                })\n            }).then(function (res) {\n                if (!res.ok) {\n                    throw new Error('Unable to create stripe checkout session');\n                }\n                return res.json();\n            }).then(function (result) {\n                const stripe = window.Stripe(result.publicKey);\n                return stripe.redirectToCheckout({\n                    sessionId: result.sessionId\n                });\n            }).then(function (result) {\n                if (result.error) {\n                    throw new Error(result.error.message);\n                }\n            }).catch(function (err) {\n                throw err;\n            });\n        },\n\n        async manageBilling({returnUrl, subscriptionId} = {}) {\n            const identity = await api.member.identity();","sourceCodeStart":740,"sourceCodeEnd":776,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/portal/src/utils/api.js#L740-L776","documentation":"Thrown by editBilling() when the POST to members 'create-stripe-update-session' returns non-2xx. This endpoint builds a Stripe Checkout Session for updating the card on an existing subscription. Unlike other handlers, this throw discards the backend error body entirely — it always uses the generic message regardless of what the backend returned.","triggerScenarios":"Member clicks 'Edit billing details' to update their card; the backend can't create the update session — subscriptionId is missing or not found, the subscription belongs to a different member, Stripe customer/subscription was deleted in the Stripe dashboard, or the member's identity token doesn't authorize that subscription.","commonSituations":"subscriptionId passed is empty/stale; the subscription was cancelled in Stripe but still listed in Ghost; identity token expired; Stripe Connect key rotated so session creation fails server-side; member trying to edit billing on a complimentary (non-Stripe) subscription.","solutions":["Check the Ghost server log for the create-stripe-update-session handler error — the backend reason is logged but not surfaced here.","Confirm subscriptionId is a valid, active Stripe subscription belonging to the signed-in member.","Re-authenticate the member (refresh identity) before retrying.","Verify the Stripe subscription still exists in the Stripe dashboard (not deleted).","Patch editBilling() to surface the backend's res.json() error body instead of the generic string (improves diagnosability)."],"exampleFix":"// before: backend error body discarded\nif (!res.ok) {\n    throw new Error('Unable to create stripe checkout session');\n}\n\n// after: surface the backend reason like checkoutPlan does\nif (!res.ok) {\n    const errData = await res.json().catch(() => null);\n    const err = errData?.errors?.[0] || {};\n    throw new Error(err.message || 'Unable to create stripe checkout session');\n}","handlingStrategy":"try-catch","validationCode":"// Validate the subscription context before calling\nfunction canEditBilling(identity, subscriptionId) {\n    return Boolean(identity && subscriptionId && typeof subscriptionId === 'string');\n}","typeGuard":null,"tryCatchPattern":"try {\n    await api.member.editBilling({subscriptionId});\n} catch (err) {\n    // Generic message — check Ghost server logs for the real reason\n    showToast(err.message);\n}","preventionTips":["Pass a valid, active subscriptionId that belongs to the signed-in member.","Patch editBilling to surface the backend res.json() error body (the generic string hides detail).","Don't offer billing edit on complimentary (non-Stripe) subscriptions."],"tags":["portal","stripe","billing","subscription","members-api"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}