TryGhost/Ghost · error · Error

result.error.message

Error message

result.error.message

What it means

Thrown in apps/portal/src/data-attributes.js update-billing flow when Stripe.js redirectToCheckout (used here to enter the billing portal session) resolves with result.error populated. The thrown error message is t(result.error.message), feeding the Stripe message through Portal's i18n lookup (which usually returns the original string because no key matches).

Source

Thrown at apps/portal/src/data-attributes.js:283

                    body: JSON.stringify({
                        identity: identity,
                        successUrl: successUrl,
                        cancelUrl: cancelUrl
                    })
                }).then(function (res) {
                    if (!res.ok) {
                        throw new Error(t('Could not create Stripe checkout session'));
                    }
                    return res.json();
                });
            }).then(function (result) {
                let stripe = window.Stripe(result.publicKey);
                return stripe.redirectToCheckout({
                    sessionId: result.sessionId
                });
            }).then(function (result) {
                if (result.error) {
                    throw new Error(t(result.error.message));
                }
            }).catch(function (err) {
                console.error(err);
                el.addEventListener('click', clickHandler);
                el.classList.remove('loading');
                if (errorEl) {
                    errorEl.innerText = err.message;
                }
                el.classList.add('error');
            });
        }
        el.addEventListener('click', clickHandler);
    });

    Array.prototype.forEach.call(document.querySelectorAll('[data-members-manage-billing]'), function (el) {
        let errorEl = el.querySelector('[data-members-error]');
        let membersReturn = el.dataset.membersReturn;
        let returnUrl;

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Surface result.error.message directly to the user — wrapping it in t() often returns the English original anyway.
  2. Have the user click the edit-billing button again (the catch re-binds the handler) to mint a fresh session.
  3. Verify the publishable key returned by the backend matches the connected Stripe account.
  4. Ask the user to disable content blockers that interfere with js.stripe.com.

Example fix

// before
if (result.error) {
    throw new Error(t(result.error.message));
}

// after: don't route a dynamic Stripe message through i18n key lookup
if (result.error) {
    throw new Error(result.error.message);
}
Defensive patterns

Strategy: try-catch

Type guard

function isStripeRedirectError(result) {
    return result?.error != null && typeof result.error.message === 'string';
}

Try / catch

try {
    const result = await stripe.redirectToCheckout({sessionId});
    if (isStripeRedirectError(result)) { throw new Error(result.error.message); }
} catch (err) {
    showError(el, err.message);
    el.classList.remove('loading');
    el.addEventListener('click', clickHandler);
}

Prevention

When it happens

Trigger: create-stripe-update-session returned a valid sessionId/publicKey; window.Stripe(publicKey).redirectToCheckout({sessionId}) resolves with {error:{message}} — Stripe.js could not perform the redirect (session expired, key mismatch, browser blocked).

Common situations: Customer portal session expired before redirect completed; publishable key returned by the backend differs from the account that created the session; browser blocking third-party cookies / popups; network interruption during redirect.

Related errors


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