Skip to content

fix: restore original URL on Stripe cancel and add return_to on success#158

Merged
lfariabr merged 2 commits intomasterfrom
fix/stripe-redirect-urls
Mar 12, 2026
Merged

fix: restore original URL on Stripe cancel and add return_to on success#158
lfariabr merged 2 commits intomasterfrom
fix/stripe-redirect-urls

Conversation

@lfariabr
Copy link
Owner

@lfariabr lfariabr commented Mar 11, 2026

  • Accept optional returnUrl in CheckoutInput (GraphQL schema + Zod validation)
  • Pass returnUrl from frontend (window.location.href) via useStripeCheckout hook
  • Use returnUrl as cancel_url in Stripe session (fallback to /payment/cancel)
  • Encode returnUrl as return_to query param on success_url so the success page can render a 'Return to page' button
  • Update PaymentSuccessPage to read return_to param and conditionally render the return button
  • Clarify FRONTEND_URL in .env.example must be a base URL with no path suffix

Summary by CodeRabbit

  • Documentation

    • Resume updated with expanded skills, projects and professional experience.
  • New Features

    • Checkout flow now supports an optional return URL so users can be redirected to a specified page after payment.
    • Success page shows a "Return to page" link when a valid return URL is present.
  • Chores

    • Example environment now includes a default frontend URL for local setup.

- Accept optional returnUrl in CheckoutInput (GraphQL schema + Zod validation)
- Pass returnUrl from frontend (window.location.href) via useStripeCheckout hook
- Use returnUrl as cancel_url in Stripe session (fallback to /payment/cancel)
- Encode returnUrl as return_to query param on success_url so the success
  page can render a 'Return to page' button
- Update PaymentSuccessPage to read return_to param and conditionally render
  the return button
- Clarify FRONTEND_URL in .env.example must be a base URL with no path suffix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Walkthrough

Adds an optional return URL through the Stripe checkout flow (frontend hook/types → GraphQL input → backend service → Stripe session), validates same-origin return URLs, and surfaces a conditional "Return to page" link on the payment success page. Also updates resume documentation and an env example.

Changes

Cohort / File(s) Summary
Resume & Documentation
docs/studying/refs/resume.md
Major rewrite: role/title, expanded SUMMARY, added Data Analyst role, expanded TECHNICAL SKILLS (T-SQL, SQL Server, SSIS, Power BI, GitHub Actions, etc.), reworked PROJECTS and outcomes.
Env Example
backend/.env.example
Replaced placeholder FRONTEND_URL=xxx with FRONTEND_URL=http://localhost:3000 and added explanatory comment.
GraphQL Types & Resolvers
backend/src/schemas/types/stripeTypes.ts, backend/src/resolvers/stripe/mutations.ts
Added optional returnUrl to CheckoutInput type and threaded it through the resolver input.
Backend Validation & Service
backend/src/validation/schemas/checkout.schema.ts, backend/src/services/stripe.ts
Validation: new optional returnUrl with URL+origin check against config. Service: CreateCheckoutSessionInput accepts returnUrl?, validates origin, computes success_url/cancel_url dynamically including return_to param.
Frontend types & hook
frontend/src/lib/graphql/types/stripe.types.ts, frontend/src/lib/hooks/useStripeCheckout.ts
Added returnUrl?: string to mutation variables; hook captures window.location.href (browser only) and passes it as returnUrl.
Payment Success Page
frontend/src/app/payment/success/page.tsx
Reads and sanitizes optional return_to query param (relative or same-origin); conditionally renders "Return to page" link and adjusts button styling.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant FE as Frontend\n(useStripeCheckout)
    participant GQL as GraphQL\n(Resolver)
    participant SVC as Backend\n(stripe service)
    participant STR as Stripe API
    participant Success as Success Page

    User->>FE: Click "Checkout"
    FE->>FE: Capture window.location.href as returnUrl
    FE->>GQL: CREATE_CHECKOUT_SESSION(productKey,email,returnUrl)
    GQL->>GQL: Validate input (including returnUrl origin)
    GQL->>SVC: createCheckoutSession(..., returnUrl)
    SVC->>SVC: isSameOrigin(returnUrl)? → compute success_url & cancel_url
    SVC->>STR: stripe.checkout.sessions.create(success_url, cancel_url, ...)
    STR-->>SVC: sessionId & checkout URL
    SVC-->>GQL: session info
    GQL-->>FE: session info
    FE->>STR: Redirect user to Stripe checkout
    User->>STR: Complete payment
    STR->>Success: Redirect to success_url (includes return_to if provided)
    Success->>Success: Read/sanitize return_to
    Success->>User: Show "Return to page" button (if valid)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Poem

The checkout thread now finds its way,
From page to session, safe and fey.
Same-origin guards stand tall and mean—
No sketchy redirects in this machine.
Resume polished, pipeline keen. 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main change: implementing URL restoration during Stripe checkout flows with return_to on success and cancel handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/stripe-redirect-urls

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
_docs/studying/refs/resume.md (1)

2-95: ⚠️ Potential issue | 🟠 Major

Split this resume rewrite out of the Stripe PR.

This whole document change is off-mission for a PR that’s supposed to fix Stripe return URLs. Bundling unrelated resume edits into a payments change makes review, rollback, and cherry-picks unnecessarily messy. Keep the checkout surgery clean; don’t bench-press docs drift in the same set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@_docs/studying/refs/resume.md` around lines 2 - 95, The PR includes unrelated
resume edits (resume.md) alongside the Stripe return-URL fix; remove the resume
changes from this checkout-focused PR by reverting or resetting modifications to
resume.md (e.g., undo the commit or checkout the file from the base branch) so
the branch only contains the Stripe/checkout changes, then create a new branch
(e.g., docs/resume-update) with the resume edits and open a separate PR titled
"docs: update resume" for that work; target symbols: resume.md (the markdown
file containing the SUMMARY, TECHNICAL SKILLS, PROFESSIONAL EXPERIENCE,
PROJECTS, EDUCATION, SOFT SKILLS sections), current Stripe/checkout branch (your
checkout/stripe-return-url fix branch), and the new docs branch
(docs/resume-update).
🧹 Nitpick comments (1)
backend/.env.example (1)

37-38: Good docs, but comments don't stop bugs—code does.

This comment is solid documentation, and making the value explicit instead of a placeholder is a step up. But here's the problem: you're asking developers to READ and FOLLOW instructions perfectly. That's like asking Goggins to skip a workout—theoretically possible, but why rely on it?

Right now, if someone fat-fingers a trailing slash into their .env file (say FRONTEND_URL=http://localhost:3000/), your code in stripe.ts will happily concatenate and produce malformed URLs like http://localhost:3000//payment/cancel. That's a production bug waiting to happen because config.ts reads FRONTEND_URL as-is with zero defensive handling.

Elite-level code doesn't hope developers read comments—it enforces requirements.

💪 Proposed defensive fix in config.ts

Add trimming logic where frontendUrl is assigned in backend/src/config/config.ts:

- frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000',
+ frontendUrl: (process.env.FRONTEND_URL || 'http://localhost:3000').replace(/\/+$/, ''),

This strips trailing slashes automatically, making your code bulletproof regardless of what someone throws in their .env file. No more hope and pray—just guaranteed correctness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/.env.example` around lines 37 - 38, The FRONTEND_URL env value is
used raw and can contain a trailing slash, causing double-slash URLs (e.g., in
stripe.ts); update the config loading so the FRONTEND_URL (or exported
frontendUrl) is normalized by trimming whitespace and removing any trailing
slashes when assigning it in config.ts (e.g., sanitize process.env.FRONTEND_URL
into frontendUrl) so all downstream concatenation produces correct paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@_docs/studying/refs/resume.md`:
- Line 56: Update the user-facing project labels to use the correct product name
"GitHub" instead of the lowercase "github"; specifically change occurrences like
"## luisfaria.dev Portfolio (github / website)" and the other instances noted
(the similar labels at the other occurrences) so they read "GitHub" (e.g., "##
luisfaria.dev Portfolio (GitHub / website)").

In `@backend/src/services/stripe.ts`:
- Around line 112-115: The cancelUrl/successUrl construction uses unvalidated
returnUrl allowing open-redirects; update the code around cancelUrl and
successUrl to defensively validate returnUrl before using it: attempt to parse
returnUrl with the URL constructor, ensure its origin matches config.frontendUrl
origin (or only allow relative paths), and if validation fails fall back to the
existing `${config.frontendUrl}/payment/...` defaults; apply the same
validatedReturnUrl (or encoded relative path) when building successUrl so both
`cancelUrl` and `successUrl` never directly redirect to arbitrary external
hosts.

In `@backend/src/validation/schemas/checkout.schema.ts`:
- Line 13: The returnUrl schema currently uses z.string().url() which allows
external origins; update the returnUrl validator to ensure the URL's origin
matches your frontend origin (config.frontendUrl) by parsing the value (new
URL(value).origin) and refining/transforming it — e.g., replace returnUrl:
z.string().url().optional() with a .refine(...) (or a z.preprocess + refine)
that checks new URL(value).origin === new URL(config.frontendUrl).origin and
fails validation otherwise; reference the returnUrl field and config.frontendUrl
when implementing the check.

In `@frontend/src/app/payment/success/page.tsx`:
- Around line 88-92: The rendered Link using the unvalidated returnTo value
(from return_to query) can cause an open-redirect; validate and normalize
returnTo before rendering. In page.tsx ensure the code that sets/uses returnTo
only allows relative paths or absolute URLs that match your app origin: parse
the incoming return_to, reject or replace any value with a safe fallback if it
has a different hostname or an unsafe scheme (e.g., not http/https), and prefer
pathname-only values; then pass the sanitized variable to the Link component
(where returnTo is currently used) so only safe internal redirects are rendered.

---

Outside diff comments:
In `@_docs/studying/refs/resume.md`:
- Around line 2-95: The PR includes unrelated resume edits (resume.md) alongside
the Stripe return-URL fix; remove the resume changes from this checkout-focused
PR by reverting or resetting modifications to resume.md (e.g., undo the commit
or checkout the file from the base branch) so the branch only contains the
Stripe/checkout changes, then create a new branch (e.g., docs/resume-update)
with the resume edits and open a separate PR titled "docs: update resume" for
that work; target symbols: resume.md (the markdown file containing the SUMMARY,
TECHNICAL SKILLS, PROFESSIONAL EXPERIENCE, PROJECTS, EDUCATION, SOFT SKILLS
sections), current Stripe/checkout branch (your checkout/stripe-return-url fix
branch), and the new docs branch (docs/resume-update).

---

Nitpick comments:
In `@backend/.env.example`:
- Around line 37-38: The FRONTEND_URL env value is used raw and can contain a
trailing slash, causing double-slash URLs (e.g., in stripe.ts); update the
config loading so the FRONTEND_URL (or exported frontendUrl) is normalized by
trimming whitespace and removing any trailing slashes when assigning it in
config.ts (e.g., sanitize process.env.FRONTEND_URL into frontendUrl) so all
downstream concatenation produces correct paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 733c5243-40b3-4180-ab2e-92b2cf6b2015

📥 Commits

Reviewing files that changed from the base of the PR and between 7264a61 and 166def2.

⛔ Files ignored due to path filters (1)
  • _docs/.DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (9)
  • _docs/studying/refs/resume.md
  • backend/.env.example
  • backend/src/resolvers/stripe/mutations.ts
  • backend/src/schemas/types/stripeTypes.ts
  • backend/src/services/stripe.ts
  • backend/src/validation/schemas/checkout.schema.ts
  • frontend/src/app/payment/success/page.tsx
  • frontend/src/lib/graphql/types/stripe.types.ts
  • frontend/src/lib/hooks/useStripeCheckout.ts

- checkout.schema.ts: add origin refine to returnUrl Zod validator so
  only URLs matching config.frontendUrl origin are accepted
- stripe.ts: add isSameOrigin() defense-in-depth check before using
  returnUrl as cancel_url/success_url; falls back to /payment/* defaults
- payment/success/page.tsx: add sanitizeReturnUrl() to validate return_to
  query param against FRONTEND_URL origin before rendering as link
- resume.md: fix github → GitHub capitalization in project labels

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/src/services/stripe.ts (1)

81-90: Extract the origin rule before future-you forks reality.

isSameOrigin() here duplicates isAllowedReturnUrlOrigin() in backend/src/validation/schemas/checkout.schema.ts. Two copies of the same security rule is how drift sneaks in and punches you later. A tiny shared helper would keep the schema check and the service fallback locked to one policy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/services/stripe.ts` around lines 81 - 90, Replace the duplicated
origin-check logic by extracting a single shared helper (e.g., export a new
function like isAllowedReturnUrlOrigin or isSameOriginHelper) and use it from
both places; remove the duplicate implementation in the Stripe service (function
isSameOrigin) and import/call the shared helper instead, and update the schema
code (which currently defines isAllowedReturnUrlOrigin) to import the same
helper so both the checkout schema and the Stripe service use the identical
origin-check function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@_docs/studying/refs/resume.md`:
- Around line 73-74: Update the blurb string in the resume entry (the line
starting "7+ projects implemented and mapped on this Open Source repo...") to
use the correct, tightened stack casing: replace "Open Source" with
"open-source", "Typescript" with "TypeScript", and "Javascript" with
"JavaScript" so the line reads with consistent, professional terminology.
- Around line 58-60: The phrase "httpOnly JWT auth" is inaccurate and has wrong
casing; replace it with the canonical, clearer wording "JWT auth via HttpOnly
cookies" (use "HttpOnly" casing) in the sentence describing the resume project
so the authentication method is precise and professionally phrased.

---

Nitpick comments:
In `@backend/src/services/stripe.ts`:
- Around line 81-90: Replace the duplicated origin-check logic by extracting a
single shared helper (e.g., export a new function like isAllowedReturnUrlOrigin
or isSameOriginHelper) and use it from both places; remove the duplicate
implementation in the Stripe service (function isSameOrigin) and import/call the
shared helper instead, and update the schema code (which currently defines
isAllowedReturnUrlOrigin) to import the same helper so both the checkout schema
and the Stripe service use the identical origin-check function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 13a871ff-9627-4535-9513-b99a1b99fefe

📥 Commits

Reviewing files that changed from the base of the PR and between 166def2 and efff231.

📒 Files selected for processing (4)
  • _docs/studying/refs/resume.md
  • backend/src/services/stripe.ts
  • backend/src/validation/schemas/checkout.schema.ts
  • frontend/src/app/payment/success/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/app/payment/success/page.tsx

Comment on lines +58 to +60
Full-stack portfolio with AI chatbot, NASA APOD integration, Stripe payments, httpOnly JWT auth,
and a CI/CD pipeline (GitHub Actions → GHCR → DigitalOcean). Includes Sentry observability,
Redis rate limiting (Lua), SSR SEO with JSON-LD structured data, and 192+ automated tests.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten the auth phrasing here.

httpOnly JWT auth is doing burpees without form. HttpOnly should keep its canonical casing, and the current wording is a bit technically fuzzy. JWT auth via HttpOnly cookies reads sharper and is more accurate on a resume.

✏️ Tiny cleanup
-Full-stack portfolio with AI chatbot, NASA APOD integration, Stripe payments, httpOnly JWT auth,
+Full-stack portfolio with AI chatbot, NASA APOD integration, Stripe payments, JWT auth via HttpOnly cookies,
 and a CI/CD pipeline (GitHub Actions → GHCR → DigitalOcean). Includes Sentry observability,
 Redis rate limiting (Lua), SSR SEO with JSON-LD structured data, and 192+ automated tests.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@_docs/studying/refs/resume.md` around lines 58 - 60, The phrase "httpOnly JWT
auth" is inaccurate and has wrong casing; replace it with the canonical, clearer
wording "JWT auth via HttpOnly cookies" (use "HttpOnly" casing) in the sentence
describing the resume project so the authentication method is precise and
professionally phrased.

Comment on lines +73 to 74
## Masters’ Degree in SWE & AI (GitHub)
7+ projects implemented and mapped on this Open Source repo, documenting my Master's degree SWE/AI. Notes, projects, insights & experiments in AI/ML, software dev & beyond. Apps in Python, C#, Typescript, Javascript. Term 2 of 8 to go.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Polish the stack names in this blurb.

This line is close, but Open Source, Typescript, and Javascript make the resume look less dialed-in than it is. Small copy, real signal. Tighten it to open-source, TypeScript, and JavaScript.

🧹 Suggested wording fix
-7+ projects implemented and mapped on this Open Source repo, documenting my Master's degree SWE/AI. Notes, projects, insights & experiments in AI/ML, software dev & beyond. Apps in Python, C#, Typescript, Javascript. Term 2 of 8 to go.
+7+ projects implemented and mapped in this open-source repo, documenting my Master's degree SWE/AI. Notes, projects, insights & experiments in AI/ML, software dev & beyond. Apps in Python, C#, TypeScript, and JavaScript. Term 2 of 8 to go.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~74-~74: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...projects implemented and mapped on this Open Source repo, documenting my Master's degree SW...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@_docs/studying/refs/resume.md` around lines 73 - 74, Update the blurb string
in the resume entry (the line starting "7+ projects implemented and mapped on
this Open Source repo...") to use the correct, tightened stack casing: replace
"Open Source" with "open-source", "Typescript" with "TypeScript", and
"Javascript" with "JavaScript" so the line reads with consistent, professional
terminology.

@lfariabr lfariabr merged commit e0eda73 into master Mar 12, 2026
8 checks passed
@lfariabr lfariabr deleted the fix/stripe-redirect-urls branch March 12, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant