TryGhost/Ghost · error · Error

We're unable to process your payment right now. Please try a

Error message

We're unable to process your payment right now. Please try again later.

What it means

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.

Source

Thrown at apps/portal/src/utils/api.js:723

            const response = await makeRequest({
                url,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(body)
            });

            const responseJson = await response.json();

            if (!response.ok) {
                const error = responseJson?.errors?.[0];
                if (error) {
                    throw error;
                }

                throw new Error('We\'re unable to process your payment right now. Please try again later.');
            }

            return responseJson;
        },

        async editBilling({successUrl, cancelUrl, subscriptionId} = {}) {
            const siteUrlObj = new URL(siteUrl);
            const identity = await api.member.identity();
            const url = endpointFor({type: 'members', resource: 'create-stripe-update-session'});
            if (!successUrl) {
                const checkoutSuccessUrl = new URL(siteUrl);
                checkoutSuccessUrl.searchParams.set('stripe', 'billing-update-success');
                successUrl = checkoutSuccessUrl.href;
            }

            if (!cancelUrl) {
                const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);
                checkoutCancelUrl.searchParams.set('stripe', 'billing-update-cancel');

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Confirm donations are enabled in Ghost Admin → Settings → Membership and a donation tier exists.
  2. Inspect the raw response in devtools — status + body — to see whether it's a Ghost 4xx or a proxy HTML page.
  3. Verify the Stripe Connect account supports one-time payments and is fully linked.
  4. Check that Portal and Ghost backend versions are compatible for the donation flow.
  5. If response.json() throws (HTML body), fix the proxy routing so the request reaches Ghost's JSON API.

Example fix

// before: response.json() throws on non-JSON before this fallback can fire
const responseJson = await response.json();
if (!response.ok) { ... throw new Error('We\'re unable...'); }

// after: parse defensively like checkoutGift does, then fall back
let responseJson = {};
try { responseJson = await response.json(); } catch { /* HTML page */ }
if (!response.ok) {
    const error = responseJson?.errors?.[0];
    if (error) throw error;
    throw new Error(`We're unable to process your payment right now (HTTP ${response.status}).`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm donations are configured before offering the flow
// (check the site config / Portal capabilities, not the raw API)
function donationsEnabled(siteConfig) {
    return Boolean(siteConfig?.donations?.enabled && siteConfig?.donations?.tier);
}

Type guard

function hasStructuredError(body) {
    return body && Array.isArray(body.errors) && body.errors[0];
}

Try / catch

try {
    await api.member.checkoutDonation({...});
} catch (err) {
    showToast(err.message);
    // If response.json() threw upstream (non-JSON), err is a SyntaxError — fix the proxy path.
}

Prevention

When it happens

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

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

Related errors


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