Skip to content

🐛 Fixed paid checkout returning to homepage instead of originating page#28485

Draft
9larsons wants to merge 1 commit into
mainfrom
claude/pensive-bose-89dcb8
Draft

🐛 Fixed paid checkout returning to homepage instead of originating page#28485
9larsons wants to merge 1 commit into
mainfrom
claude/pensive-bose-89dcb8

Conversation

@9larsons

Copy link
Copy Markdown
Contributor

Summary

When a member starts a paid signup or upgrade from a page other than the homepage, Portal returned them to the site root after a successful Stripe Checkout (e.g. https://example.com/?stripe=success) instead of the page where checkout began. Cancelling checkout already returned to the originating page, so the behaviour was inconsistent — and especially disruptive for paywall flows, where a member who just paid to unlock an article was bounced to the homepage instead of back to the article.

Root cause

checkoutPlan() in apps/portal/src/utils/api.js already derived a contextual cancelUrl from window.location when none was supplied, but had no equivalent fallback for successUrl. The two standard callers (signup and upgrade in apps/portal/src/actions.js) don't pass one, so successUrl arrived at the backend as undefined and Ghost fell back to the configured site-root default (stripe-api.jscheckoutSessionSuccessUrl).

Fix

Derive a contextual successUrl from window.location when none is supplied, mirroring the existing cancelUrl logic. This is the same approach continueGiftCheckout() already uses for gift flows (and matches #27643 / #27827).

No backend change is needed: sanitizeReturnUrl() already enforces that return URLs are same-origin / within the site subpath, so the contextual URL flows through safely.

Behaviour preserved

  • Welcome-page tiers — the server still overrides the success URL with a configured tier welcome page (_generateSuccessUrl); the contextual URL only applies when no welcome page is set.
  • New-email signup — the contextual URL becomes the post-signup magic-link referrer, so the member returns to the originating page after confirming (an improvement over the previous site-root referrer).

Tests

Added unit tests in apps/portal/test/api.test.js covering:

  • contextual successUrl/cancelUrl derived from the current page when none supplied
  • fallback to the site root when the current location is off-site (open-redirect guard)
  • explicitly-supplied successUrl/cancelUrl are preserved

ref https://linear.app/ghost/issue/PLA-86
fixes #28369

ref https://linear.app/ghost/issue/PLA-86
ref #28369

- checkoutPlan() already derived a contextual cancelUrl from window.location but had no equivalent fallback for successUrl, so the standard signup/upgrade callers omitted it and the backend fell back to the site-root default — landing paid members on the homepage after Stripe Checkout instead of the article they just unlocked
- mirrors the existing cancelUrl logic and continueGiftCheckout(), which already does this for gift flows
- backend sanitizeReturnUrl() already enforces same-origin, so the contextual URL flows through safely with no server change needed
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 69212837-c1b1-4486-b087-dd213a832a31

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pensive-bose-89dcb8

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Portal paid checkout returns to site root instead of originating page

1 participant