Skip to content

refactor(subscribe): integrate Privy authentication for checkout sessions#1724

Open
ahmednahima0-beep wants to merge 2 commits intotestfrom
feat/chat-subscription-checkout-via-recoup-api
Open

refactor(subscribe): integrate Privy authentication for checkout sessions#1724
ahmednahima0-beep wants to merge 2 commits intotestfrom
feat/chat-subscription-checkout-via-recoup-api

Conversation

@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator

@ahmednahima0-beep ahmednahima0-beep commented Apr 30, 2026

  • Updated useSubscribeClick to retrieve an access token using usePrivy before creating a checkout session.
  • Modified createClientCheckoutSession to accept an access token and use it in the request headers for enhanced security.
  • Adjusted the success URL handling to ensure proper session creation and error handling.

This change improves the authentication flow for subscription management.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced validation and error handling for subscription checkout operations
    • Improved response processing for checkout sessions with better error detection
    • Strengthened security controls for subscription authentication flows

…ions

- Updated `useSubscribeClick` to retrieve an access token using `usePrivy` before creating a checkout session.
- Modified `createClientCheckoutSession` to accept an access token and use it in the request headers for enhanced security.
- Adjusted the success URL handling to ensure proper session creation and error handling.

This change improves the authentication flow for subscription management.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 30, 2026

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

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Apr 30, 2026 10:24pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

📝 Walkthrough

Walkthrough

The checkout flow is refactored to use Privy authentication tokens instead of account IDs. The hook now asynchronously retrieves an access token for checkout, while the client session function signature changes to accept accessToken and successUrl parameters, calling a new /api/subscriptions/sessions endpoint with improved error validation and Bearer token authentication.

Changes

Cohort / File(s) Summary
Privy Auth Integration
hooks/useSubscribeClick.ts
Made handleClick asynchronous; checkout flow now retrieves accessToken via Privy and passes it to the session creator instead of account_id; subscribed flow uses void wrapper.
Checkout Session API Refactor
lib/stripe/createClientCheckoutSession.ts
Function signature changed to accept accessToken and successUrl instead of accountId; now calls /api/subscriptions/sessions endpoint with Bearer authorization header; response parsing validates url as string and includes error handling; window open uses noopener,noreferrer instead of __blank.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Hook as useSubscribeClick
    participant Privy
    participant ClientFn as createClientCheckoutSession
    participant API as /api/subscriptions/sessions
    participant Browser

    User->>Hook: Click subscribe
    Hook->>Privy: getAccessToken()
    Privy-->>Hook: accessToken
    alt Token missing
        Hook->>Hook: Exit early
    else Token present
        Hook->>ClientFn: createClientCheckoutSession(accessToken, currentURL)
        ClientFn->>API: POST with Bearer auth<br/>{successUrl}
        API-->>ClientFn: {id?, url?, error?}
        alt url is valid
            ClientFn->>Browser: window.open(url, noopener,noreferrer)
            Browser-->>User: Checkout page
        else url invalid
            ClientFn->>ClientFn: console.error & return {error}
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🔐 With Privy's token now in hand,
The checkout flow takes firmer stand,
Auth flows through, no account ID needed—
Sessions created, validation heeded.
Security and SOLID principles unite,
Clean architecture shining bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning PR violates SOLID and clean code principles: early guard blocks checkout for users without account_id despite new Privy flow not requiring it, breaking API contract mismatch exists between client and backend endpoints, and inconsistent error handling lacks explicit return types. Move account_id guard into isSubscribed branch, verify matching backend endpoint with correct contracts, add explicit TypeScript return type annotations, and create higher-level abstraction for session creation functions.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chat-subscription-checkout-via-recoup-api

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

1 issue found across 3 files

Confidence score: 3/5

  • There is a concrete security concern in lib/stripe/createClientCheckoutSession.ts: using window.open(..., '_blank') without noopener,noreferrer can allow reverse-tabnabbing.
  • Given the 6/10 severity and high confidence (9/10), this introduces some real user-impacting risk, so this sits in a moderate-risk merge range until patched.
  • The fix is typically straightforward (add noopener,noreferrer to the new-tab behavior), which keeps this from looking like a high-risk architectural issue.
  • Pay close attention to lib/stripe/createClientCheckoutSession.ts - ensure external checkout navigation cannot access window.opener.
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/createClientCheckoutSession.ts">

<violation number="1" location="lib/stripe/createClientCheckoutSession.ts:30">
P2: `window.open` with `_blank` should include `noopener,noreferrer` to prevent reverse-tabnabbing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread lib/stripe/createClientCheckoutSession.ts Outdated
- Updated the `window.open` method in `createClientCheckoutSession` to include `noopener,noreferrer` for improved security against potential tab-nabbing attacks.
- Adjusted the corresponding test to verify the new parameters are correctly passed when opening the checkout session URL.

This change enhances the security of the checkout process.
Copy link
Copy Markdown
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useSubscribeClick.ts`:
- Around line 12-23: The handleClick function currently returns early if
userData?.account_id is missing, which incorrectly blocks the checkout path;
change the guard so that the account_id check is only used for the portal flow:
inside the isSubscribed branch, verify userData?.account_id and call
createClientPortalSession(userData.account_id) (or return if missing), while
leaving the non-subscribed path to proceed to await getAccessToken() and call
createClientCheckoutSession(accessToken, window.location.href); update
references to isSubscribed, userData.account_id, createClientPortalSession,
getAccessToken, and createClientCheckoutSession accordingly.

In `@lib/stripe/createClientCheckoutSession.ts`:
- Around line 8-27: The client is posting the wrong payload and expecting the
wrong response shape; update createClientCheckoutSession to POST both accountId
and successUrl (matching app/api/stripe/session/create/route.ts) to the correct
endpoint (`/api/stripe/session/create`) via getClientApiBaseUrl(), then parse
the returned JSON as { data: session } and read session.url (or throw using
data.data.error) and still check response.ok; locate the fetch call in
lib/stripe/createClientCheckoutSession.ts and adjust the request body, endpoint
path, and response handling accordingly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75ff84ac-1689-4ef5-80ba-fc43ac2b02b8

📥 Commits

Reviewing files that changed from the base of the PR and between 5897f0d and a36332c.

⛔ Files ignored due to path filters (1)
  • lib/stripe/__tests__/createClientCheckoutSession.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (2)
  • hooks/useSubscribeClick.ts
  • lib/stripe/createClientCheckoutSession.ts

Comment on lines +12 to +23
const handleClick = async () => {
if (!userData?.account_id) return;

if (isSubscribed) {
createClientPortalSession(userData.account_id);
void createClientPortalSession(userData.account_id);
return;
}
createClientCheckoutSession(userData.account_id);

const accessToken = await getAccessToken();
if (!accessToken) return;

void createClientCheckoutSession(accessToken, window.location.href);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't gate checkout on account_id.

The new checkout flow uses a Privy access token, so the early return on userData?.account_id blocks the non-subscribed path entirely for users who no longer have an account ID. Move that guard inside the isSubscribed branch so only the portal flow depends on account_id.

Suggested fix
 const handleClick = async () => {
-  if (!userData?.account_id) return;
-
   if (isSubscribed) {
+    if (!userData?.account_id) return;
     void createClientPortalSession(userData.account_id);
     return;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleClick = async () => {
if (!userData?.account_id) return;
if (isSubscribed) {
createClientPortalSession(userData.account_id);
void createClientPortalSession(userData.account_id);
return;
}
createClientCheckoutSession(userData.account_id);
const accessToken = await getAccessToken();
if (!accessToken) return;
void createClientCheckoutSession(accessToken, window.location.href);
const handleClick = async () => {
if (isSubscribed) {
if (!userData?.account_id) return;
void createClientPortalSession(userData.account_id);
return;
}
const accessToken = await getAccessToken();
if (!accessToken) return;
void createClientCheckoutSession(accessToken, window.location.href);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useSubscribeClick.ts` around lines 12 - 23, The handleClick function
currently returns early if userData?.account_id is missing, which incorrectly
blocks the checkout path; change the guard so that the account_id check is only
used for the portal flow: inside the isSubscribed branch, verify
userData?.account_id and call createClientPortalSession(userData.account_id) (or
return if missing), while leaving the non-subscribed path to proceed to await
getAccessToken() and call createClientCheckoutSession(accessToken,
window.location.href); update references to isSubscribed, userData.account_id,
createClientPortalSession, getAccessToken, and createClientCheckoutSession
accordingly.

Comment on lines +8 to +27
const response = await fetch(
`${getClientApiBaseUrl()}/api/subscriptions/sessions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ successUrl }),
},
body: JSON.stringify({
accountId,
successUrl: `${window.location.href}`,
}),
});
);

const data = (await response.json()) as {
id?: string;
url?: string;
error?: string;
};

if (!response.ok || typeof data.url !== "string") {
throw new Error(data.error || "Failed to create checkout session");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Checkout requests are pointed at the wrong API contract.

app/api/stripe/session/create/route.ts expects { accountId, successUrl } and returns { data: session }, but this client now posts only { successUrl } to /api/subscriptions/sessions and then looks for a top-level url. Unless a matching backend route lands in this PR, the checkout flow will fail at runtime.

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

In `@lib/stripe/createClientCheckoutSession.ts` around lines 8 - 27, The client is
posting the wrong payload and expecting the wrong response shape; update
createClientCheckoutSession to POST both accountId and successUrl (matching
app/api/stripe/session/create/route.ts) to the correct endpoint
(`/api/stripe/session/create`) via getClientApiBaseUrl(), then parse the
returned JSON as { data: session } and read session.url (or throw using
data.data.error) and still check response.ok; locate the fetch call in
lib/stripe/createClientCheckoutSession.ts and adjust the request body, endpoint
path, and response handling accordingly.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: This PR modifies the authentication and checkout flow for subscriptions, which is a critical business path involving payments and security. Human review is required.

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