Skip to content

feat: honor pro-trial checkout intent param on homepage - #1904

Open
sweetmantech wants to merge 1 commit into
mainfrom
feat/1902-checkout-intent
Open

feat: honor pro-trial checkout intent param on homepage#1904
sweetmantech wants to merge 1 commit into
mainfrom
feat/1902-checkout-intent

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

What

Visitors landing on chat.recoupable.dev/?intent=pro-trial are now carried into the Stripe Pro trial checkout instead of being dropped on the homepage.

  • Signed in: a checkout session is created (POST /api/subscriptions/sessions with the Privy bearer token) and the browser navigates to it same-tab via window.location.href.
  • Signed out: the Privy login modal opens; once auth completes and the account hydrates, the flow continues into checkout automatically.
  • Unknown or absent intent values: nothing happens.

Why

Part of #1902 (web UX conversion audit, item C1). Marketing links can now deep-link straight into the Pro trial purchase instead of losing the visitor on the homepage.

How

  • lib/checkout/shouldTriggerCheckoutIntent.ts: pure decision helper encoding the ordering rules (unknown intent, Privy readiness, auth state, account hydration, double-fire guard). Test-driven: the vitest suite was written first and run RED before implementation.
  • lib/stripe/requestCheckoutSessionUrl.ts: creates the checkout session and returns the URL. The success URL strips the query string so returning from Stripe does not re-enter the intent flow.
  • hooks/useCheckoutIntent.ts: composed hook reading the intent search param, usePrivy (ready, authenticated, login, getAccessToken), and useUserProvider. Double-firing is guarded by refs plus a sessionStorage flag that survives the Stripe round trip. Built alongside (not on top of) useSubscribeClick, which a sibling PR is reworking.
  • components/Home/HomePage.tsx: mounts the hook.

How to test

  1. Open the preview deployment at /?intent=pro-trial while signed in: you should be redirected same-tab to a Stripe checkout page for the Pro trial.
  2. Open the same URL in a fresh incognito session: the Privy login modal opens; complete sign-in and you should land on the Stripe checkout automatically.
  3. Open /?intent=something-else and plain /: no checkout or login side effects beyond the existing auto-login behavior.
  4. Cancel out of Stripe checkout and return: the flow does not re-fire in the same tab session.
  5. pnpm exec vitest run: 75 files / 284 tests pass, including the 8 new shouldTriggerCheckoutIntent cases.

Preview verification to follow in a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Honors the ?intent=pro-trial param on the homepage to carry visitors straight into the Stripe Pro trial checkout. Supports deep links for marketing and reduces drop-offs. Part of #1902 (conversion audit, C1).

  • New Features
    • Added useCheckoutIntent to read the intent param and @privy-io/react-auth state, then trigger login or create a checkout session and same-tab redirect.
    • Introduced a pure, tested shouldTriggerCheckoutIntent to handle ordering rules and double-fire guards (ref + sessionStorage).
    • Created checkout sessions via POST /api/subscriptions/sessions; success URL strips the query to avoid re-entering the flow.
    • Mounted the hook on HomePage. Added focused tests for the decision helper.

Written for commit 8cd2dac. Summary will update on new commits.

Review in cubic

Visitors landing on / with ?intent=pro-trial are carried into the
Stripe Pro trial checkout instead of being dropped on the homepage.
Signed-in users get a checkout session and a same-tab redirect;
signed-out users get the Privy login modal and continue into checkout
automatically once auth completes. Unknown or absent intent values do
nothing.

The ordering rules live in a pure, unit-tested decision helper. A ref
plus a sessionStorage flag guard against double-firing, and the
checkout success URL drops the query string so returning from Stripe
does not re-enter the flow.

Part of #1902 (item C1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 29, 2026 2:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 29b034d2-b75d-4b7d-808b-1e80b2cbb422

📥 Commits

Reviewing files that changed from the base of the PR and between d0913d1 and 8cd2dac.

⛔ Files ignored due to path filters (2)
  • __tests__/shouldTriggerCheckoutIntent.test.ts is excluded by !**/*.test.* and included by none
  • components/Home/__tests__/HomePage.onboarding.test.tsx is excluded by !**/*.test.* and included by components/**
📒 Files selected for processing (4)
  • components/Home/HomePage.tsx
  • hooks/useCheckoutIntent.ts
  • lib/checkout/shouldTriggerCheckoutIntent.ts
  • lib/stripe/requestCheckoutSessionUrl.ts

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.

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8cd2dac35e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


if (action !== "checkout") return;
hasStartedCheckout.current = true;
markHandled();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move the handled flag until checkout can redirect

If an authenticated visitor lands on ?intent=pro-trial but getAccessToken() returns null or the checkout-session request fails/returns no URL, this marks the intent as handled before any redirect happens. Because wasHandled() then reads the sessionStorage flag on subsequent effect runs or reloads in the same tab, the marketing link stops trying to create checkout even though the user never reached Stripe; keep the in-flight ref for de-duping, but only persist the handled flag once a checkout URL is actually available.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 6 files

Confidence score: 3/5

  • In components/Home/HomePage.tsx, marking the landing intent as handled before a checkout URL is successfully returned can permanently consume the intent after a transient token/session error, leaving users unable to retry checkout in the same tab — persist the handled flag only after a valid checkout URL is obtained (or clear it on failure).
  • In lib/stripe/requestCheckoutSessionUrl.ts, checkout-session request behavior is duplicated across helpers, so endpoint/response/error-handling updates can diverge between same-tab and new-tab flows and cause inconsistent checkout behavior — centralize the request logic in one shared helper and keep only navigation differences at call sites.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/stripe/requestCheckoutSessionUrl.ts">

<violation number="1" location="lib/stripe/requestCheckoutSessionUrl.ts:8">
P3: Checkout-session request logic now exists in two helpers, so endpoint or response/error-handling changes can drift between same-tab and new-tab checkout flows. Share the request helper and keep navigation as the caller-specific behavior.</violation>
</file>

<file name="components/Home/HomePage.tsx">

<violation number="1" location="components/Home/HomePage.tsx:16">
P2: A transient token or checkout-session failure leaves this landing intent permanently consumed for the tab, although no Stripe navigation occurred. Persist the handled flag only after a checkout URL is obtained (retain the in-memory guard while the request is in flight).</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

const { setFrameReady, isFrameReady } = useMiniKit();
const router = useRouter();
const onboarding = useOnboardingGate();
useCheckoutIntent();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A transient token or checkout-session failure leaves this landing intent permanently consumed for the tab, although no Stripe navigation occurred. Persist the handled flag only after a checkout URL is obtained (retain the in-memory guard while the request is in flight).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/Home/HomePage.tsx, line 16:

<comment>A transient token or checkout-session failure leaves this landing intent permanently consumed for the tab, although no Stripe navigation occurred. Persist the handled flag only after a checkout URL is obtained (retain the in-memory guard while the request is in flight).</comment>

<file context>
@@ -5,13 +5,15 @@ import { useRouter } from "next/navigation";
   const { setFrameReady, isFrameReady } = useMiniKit();
   const router = useRouter();
   const onboarding = useOnboardingGate();
+  useCheckoutIntent();
 
   useEffect(() => {
</file context>

* navigation. The success URL drops the query string so a returning
* visitor does not re-enter the intent flow.
*/
const requestCheckoutSessionUrl = async (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Checkout-session request logic now exists in two helpers, so endpoint or response/error-handling changes can drift between same-tab and new-tab checkout flows. Share the request helper and keep navigation as the caller-specific behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/stripe/requestCheckoutSessionUrl.ts, line 8:

<comment>Checkout-session request logic now exists in two helpers, so endpoint or response/error-handling changes can drift between same-tab and new-tab checkout flows. Share the request helper and keep navigation as the caller-specific behavior.</comment>

<file context>
@@ -0,0 +1,34 @@
+ * navigation. The success URL drops the query string so a returning
+ * visitor does not re-enter the intent flow.
+ */
+const requestCheckoutSessionUrl = async (
+  accessToken: string,
+): Promise<string | null> => {
</file context>

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.

1 participant