{"record":{"id":"0dbb029bf564351d","repo":"TryGhost/Ghost","slug":"we-re-unable-to-process-your-payment-right-now-pl","errorCode":null,"errorMessage":"We're unable to process your payment right now. Please try again later.","messagePattern":"We're unable to process your payment right now\\. Please try again later\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/portal/src/utils/api.js","lineNumber":723,"sourceCode":"\n            const response = await makeRequest({\n                url,\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json'\n                },\n                body: JSON.stringify(body)\n            });\n\n            const responseJson = await response.json();\n\n            if (!response.ok) {\n                const error = responseJson?.errors?.[0];\n                if (error) {\n                    throw error;\n                }\n\n                throw new Error('We\\'re unable to process your payment right now. Please try again later.');\n            }\n\n            return responseJson;\n        },\n\n        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {\n            const siteUrlObj = new URL(siteUrl);\n            const identity = await api.member.identity();\n            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});\n            if (!successUrl) {\n                const checkoutSuccessUrl = new URL(siteUrl);\n                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');\n                successUrl = checkoutSuccessUrl.href;\n            }\n\n            if (!cancelUrl) {\n                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);\n                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/portal/src/utils/api.js#L705-L741","documentation":"Thrown by checkoutDonation() when the create-stripe-checkout-session POST (type:'donation') returns non-2xx with no structured errors[0] to rethrow. Donations are one-time payments, so this fires when the backend rejects the donation session creation and provides no parseable error detail.","triggerScenarios":"Donations not enabled on the site but the Portal offered the flow; the donation tier is retired; Stripe Connect account misconfigured for one-time payments; amount/currency invalid; or a proxy interposed an HTML error page (checkoutDonation calls response.json() directly without the defensive try/catch that checkoutGift has, so a non-JSON body would throw a SyntaxError before reaching this branch).","commonSituations":"Ghost version where the donations feature flag is off but the theme/Portal bundle is newer; Stripe account not configured to accept one-time payments (only subscriptions); currency mismatch between Portal and Stripe; response.json() throwing on an HTML proxy error (which surfaces as a different error but leaves this one as the documented fallback).","solutions":["Confirm donations are enabled in Ghost Admin → Settings → Membership and a donation tier exists.","Inspect the raw response in devtools — status + body — to see whether it's a Ghost 4xx or a proxy HTML page.","Verify the Stripe Connect account supports one-time payments and is fully linked.","Check that Portal and Ghost backend versions are compatible for the donation flow.","If response.json() throws (HTML body), fix the proxy routing so the request reaches Ghost's JSON API."],"exampleFix":"// before: response.json() throws on non-JSON before this fallback can fire\nconst responseJson = await response.json();\nif (!response.ok) { ... throw new Error('We\\'re unable...'); }\n\n// after: parse defensively like checkoutGift does, then fall back\nlet responseJson = {};\ntry { responseJson = await response.json(); } catch { /* HTML page */ }\nif (!response.ok) {\n    const error = responseJson?.errors?.[0];\n    if (error) throw error;\n    throw new Error(`We're unable to process your payment right now (HTTP ${response.status}).`);\n}","handlingStrategy":"try-catch","validationCode":"// Confirm donations are configured before offering the flow\n// (check the site config / Portal capabilities, not the raw API)\nfunction donationsEnabled(siteConfig) {\n    return Boolean(siteConfig?.donations?.enabled && siteConfig?.donations?.tier);\n}","typeGuard":"function hasStructuredError(body) {\n    return body && Array.isArray(body.errors) && body.errors[0];\n}","tryCatchPattern":"try {\n    await api.member.checkoutDonation({...});\n} catch (err) {\n    showToast(err.message);\n    // If response.json() threw upstream (non-JSON), err is a SyntaxError — fix the proxy path.\n}","preventionTips":["Enable donations and a donation tier in Ghost Admin before exposing the flow.","Ensure Stripe Connect supports one-time payments.","Patch checkoutDonation to parse the body defensively (try/catch around response.json())."],"tags":["portal","stripe","donation","members-api","payment"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}