Skip to content

feat(chat): subscribe checkout via recoup-api sessions#1728

Merged
sweetmantech merged 1 commit intotestfrom
feature/chat-subscribe-recoup-api-sessions
May 6, 2026
Merged

feat(chat): subscribe checkout via recoup-api sessions#1728
sweetmantech merged 1 commit intotestfrom
feature/chat-subscribe-recoup-api-sessions

Conversation

@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator

@ahmednahima0-beep ahmednahima0-beep commented May 3, 2026

Replace the chat app’s POST /api/stripe/session/create flow with POST {recoup-api}/api/subscriptions/sessions, authenticated with a Privy Bearer token and body { successUrl }. Remove the unused session route and createSession helper so subscribe still works end-to-end.


Summary by cubic

Switches checkout creation to recoup-api subscription sessions using a Privy Bearer token. Removes the old Stripe session endpoint and updates the client flow to open the returned checkout URL.

  • Refactors
    • Replace POST /api/stripe/session/create with POST {recoup-api}/api/subscriptions/sessions using Authorization: Bearer <Privy token> and { successUrl }.
    • Update useSubscribeClick to get the access token via @privy-io/react-auth and call createClientCheckoutSession(token); portal flow unchanged for subscribed users.
    • Rewrite createClientCheckoutSession to use getClientApiBaseUrl, add basic error checks, and open data.url in a new tab with noopener,noreferrer.
    • Remove app/api/stripe/session/create and lib/stripe/createSession.

Written for commit 3d1fe09. Summary will update on new commits.

Summary by CodeRabbit

  • Refactor
    • Updated the subscription checkout process architecture and authentication flow to use access token-based authentication instead of account ID-based authentication.
    • Streamlined the session creation mechanism for improved security and reliability in the checkout workflow.

…on creation to use access tokens. Update subscription handling in the useSubscribeClick hook to ensure proper authentication flow.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 3, 2026

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

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview May 3, 2026 11:53am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

📝 Walkthrough

Walkthrough

This PR migrates Stripe checkout session creation from an internal Next.js API route to a new backend endpoint, replacing account ID authentication with Privy access tokens. The old session creation route and server-side Stripe logic are removed, while the client hook and checkout function are updated to fetch tokens and call the new endpoint.

Changes

Checkout Flow Refactoring

Layer / File(s) Summary
Authentication Integration
hooks/useSubscribeClick.ts
useSubscribeClick now imports and uses usePrivy to acquire access tokens before checkout, converting handleClick to an async function and adding early returns when token is unavailable.
Client Checkout Function
lib/stripe/createClientCheckoutSession.ts
Signature changes from accountId to accessToken parameter; now calls new /api/subscriptions/sessions backend endpoint with Bearer token auth header, expects top-level { url } response, and opens checkout with security attributes noopener,noreferrer.
Removed Old Endpoints
app/api/stripe/session/create/route.ts, lib/stripe/createSession.ts
Next.js API route and server-side session creation function are deleted; old architecture (internal API → Stripe client) is fully removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • sweetmantech

Poem

🔐 Tokens now flow where IDs once flew,
Privy unlocks what the checkout must do,
Old routes fade as new endpoints shine,
Bearer auth draws a cleaner line. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates SOLID principles with inconsistent return types, missing error logging, and silent error suppression throughout. Fix return type inconsistency in createClientCheckoutSession, add console.error() logging, move account_id check, and implement toast.error() feedback for all failure paths.
✅ 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 feature/chat-subscribe-recoup-api-sessions

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
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: 3

🤖 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 13-17: The early unconditional guard "if (!userData?.account_id)
return;" prevents checkout flows that don't need account_id; move the account_id
check inside the portal branch so only
createClientPortalSession(userData.account_id) requires it. Concretely, update
the logic around isSubscribed and createClientPortalSession so that: when
isSubscribed is true, verify userData?.account_id and call
createClientPortalSession; when isSubscribed is false, proceed to the checkout
session flow (e.g., createCheckoutSession or equivalent) without requiring
account_id. Ensure error handling/logging for a missing account_id only applies
to the portal path.
- Around line 20-23: The click handler currently swallows failures from
getAccessToken() and createClientCheckoutSession(), so update the logic in
hooks/useSubscribeClick.ts (referencing getAccessToken and
createClientCheckoutSession) to surface errors instead of no-ops: if
getAccessToken() returns falsy, show a user-facing toast via sonner (e.g.,
toast.error) and log the situation; when createClientCheckoutSession(...)
returns a response containing an error, show a descriptive toast with the error
message and log it; ensure success and failure paths return/exit appropriately
so callers know the outcome (do not silently return on failures).

In `@lib/stripe/createClientCheckoutSession.ts`:
- Around line 23-28: The code trusts data.url and opens it directly; validate
the returned checkout URL before calling window.open by parsing it with the URL
constructor and checking scheme is https and host is an allowlisted checkout
host (or matches expected pattern), returning an error if validation fails;
update the logic around variable data (from response.json()) to perform this
validation and only call window.open(data.url, "_blank", "noopener,noreferrer")
when the URL passes checks, otherwise return a descriptive Error indicating an
invalid or disallowed checkout URL.
🪄 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: 136504f9-ec3f-4802-9689-874692258002

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfd528 and 3d1fe09.

📒 Files selected for processing (4)
  • app/api/stripe/session/create/route.ts
  • hooks/useSubscribeClick.ts
  • lib/stripe/createClientCheckoutSession.ts
  • lib/stripe/createSession.ts
💤 Files with no reviewable changes (2)
  • lib/stripe/createSession.ts
  • app/api/stripe/session/create/route.ts

Comment on lines 13 to 17
if (!userData?.account_id) return;

if (isSubscribed) {
createClientPortalSession(userData.account_id);
return;
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

Only require account_id for the portal branch.

The early return on Line 13 now blocks checkout even though the new session endpoint only needs a Privy access token. That means unsubscribed users can get stuck here if userData hasn't hydrated account_id yet.

Minimal fix
-    if (!userData?.account_id) return;
-
     if (isSubscribed) {
+      if (!userData?.account_id) return;
       createClientPortalSession(userData.account_id);
       return;
     }

As per coding guidelines, hooks/**/*.ts: "Handle edge cases and errors".

📝 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
if (!userData?.account_id) return;
if (isSubscribed) {
createClientPortalSession(userData.account_id);
return;
if (isSubscribed) {
if (!userData?.account_id) return;
createClientPortalSession(userData.account_id);
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useSubscribeClick.ts` around lines 13 - 17, The early unconditional
guard "if (!userData?.account_id) return;" prevents checkout flows that don't
need account_id; move the account_id check inside the portal branch so only
createClientPortalSession(userData.account_id) requires it. Concretely, update
the logic around isSubscribed and createClientPortalSession so that: when
isSubscribed is true, verify userData?.account_id and call
createClientPortalSession; when isSubscribed is false, proceed to the checkout
session flow (e.g., createCheckoutSession or equivalent) without requiring
account_id. Ensure error handling/logging for a missing account_id only applies
to the portal path.

Comment on lines +20 to +23
const accessToken = await getAccessToken();
if (!accessToken) return;

await createClientCheckoutSession(accessToken);
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 make auth and checkout failures silent no-ops.

If getAccessToken() returns nothing or createClientCheckoutSession() returns { error }, the click just does nothing. That makes subscribe failures hard to diagnose and easy to mistake for a broken button.

Example handling
+import { toast } from "sonner";
+
     const accessToken = await getAccessToken();
-    if (!accessToken) return;
+    if (!accessToken) {
+      toast.error("Unable to authenticate checkout.");
+      return;
+    }
 
-    await createClientCheckoutSession(accessToken);
+    const result = await createClientCheckoutSession(accessToken);
+    if (result?.error) {
+      toast.error("Unable to start checkout.");
+    }

As per coding guidelines, hooks/**/*.ts: "Handle edge cases and errors" and **/*.{tsx,ts,jsx,js}: "Use sonner for toast notifications".

📝 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 accessToken = await getAccessToken();
if (!accessToken) return;
await createClientCheckoutSession(accessToken);
import { toast } from "sonner";
const accessToken = await getAccessToken();
if (!accessToken) {
toast.error("Unable to authenticate checkout.");
return;
}
const result = await createClientCheckoutSession(accessToken);
if (result?.error) {
toast.error("Unable to start checkout.");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useSubscribeClick.ts` around lines 20 - 23, The click handler currently
swallows failures from getAccessToken() and createClientCheckoutSession(), so
update the logic in hooks/useSubscribeClick.ts (referencing getAccessToken and
createClientCheckoutSession) to surface errors instead of no-ops: if
getAccessToken() returns falsy, show a user-facing toast via sonner (e.g.,
toast.error) and log the situation; when createClientCheckoutSession(...)
returns a response containing an error, show a descriptive toast with the error
message and log it; ensure success and failure paths return/exit appropriately
so callers know the outcome (do not silently return on failures).

Comment on lines +23 to +28
const data: { url?: string } = await response.json();
if (!data.url) {
return { error: new Error("Checkout URL missing") };
}

const data = await response.json();
window.open(data.data.url, "__blank");
window.open(data.url, "_blank", "noopener,noreferrer");
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

Validate the returned checkout URL before opening it.

This trusts any url the backend returns and immediately sends the user there in a new tab. A bad or misconfigured response here becomes a client-side open redirect/phishing hop.

Suggested hardening
     const data: { url?: string } = await response.json();
     if (!data.url) {
       return { error: new Error("Checkout URL missing") };
     }
+
+    const checkoutUrl = new URL(data.url);
+    if (checkoutUrl.protocol !== "https:") {
+      return { error: new Error("Invalid checkout URL") };
+    }
 
-    window.open(data.url, "_blank", "noopener,noreferrer");
+    window.open(checkoutUrl.toString(), "_blank", "noopener,noreferrer");

If you know the exact checkout host(s), I'd also allowlist them here.

As per coding guidelines, **/*.{ts,tsx,js}: "Implement built-in security practices for authentication and data handling".

📝 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 data: { url?: string } = await response.json();
if (!data.url) {
return { error: new Error("Checkout URL missing") };
}
const data = await response.json();
window.open(data.data.url, "__blank");
window.open(data.url, "_blank", "noopener,noreferrer");
const data: { url?: string } = await response.json();
if (!data.url) {
return { error: new Error("Checkout URL missing") };
}
const checkoutUrl = new URL(data.url);
if (checkoutUrl.protocol !== "https:") {
return { error: new Error("Invalid checkout URL") };
}
window.open(checkoutUrl.toString(), "_blank", "noopener,noreferrer");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/stripe/createClientCheckoutSession.ts` around lines 23 - 28, The code
trusts data.url and opens it directly; validate the returned checkout URL before
calling window.open by parsing it with the URL constructor and checking scheme
is https and host is an allowlisted checkout host (or matches expected pattern),
returning an error if validation fails; update the logic around variable data
(from response.json()) to perform this validation and only call
window.open(data.url, "_blank", "noopener,noreferrer") when the URL passes
checks, otherwise return a descriptive Error indicating an invalid or disallowed
checkout URL.

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 4 files

Confidence score: 3/5

  • There is a concrete user-impact risk in hooks/useSubscribeClick.ts: gating the new checkout flow on userData.account_id can prevent valid unsubscribed users from initiating subscription.
  • Given the high confidence (9/10) and meaningful severity (7/10), this is more than a minor edge case and could cause a real conversion-blocking regression.
  • Pay close attention to hooks/useSubscribeClick.ts - checkout eligibility logic may incorrectly block legitimate users.
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="hooks/useSubscribeClick.ts">

<violation number="1" location="hooks/useSubscribeClick.ts:20">
P1: The new checkout flow is still gated by `userData.account_id`, which can block valid unsubscribed users from starting a subscription.</violation>
</file>

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

}
createClientCheckoutSession(userData.account_id);

const accessToken = await getAccessToken();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: The new checkout flow is still gated by userData.account_id, which can block valid unsubscribed users from starting a subscription.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useSubscribeClick.ts, line 20:

<comment>The new checkout flow is still gated by `userData.account_id`, which can block valid unsubscribed users from starting a subscription.</comment>

<file context>
@@ -1,20 +1,26 @@
     }
-    createClientCheckoutSession(userData.account_id);
+
+    const accessToken = await getAccessToken();
+    if (!accessToken) return;
+
</file context>

@sweetmantech sweetmantech merged commit 798cea0 into test May 6, 2026
4 checks passed
@sweetmantech sweetmantech deleted the feature/chat-subscribe-recoup-api-sessions branch May 6, 2026 13:34
sweetmantech added a commit that referenced this pull request May 6, 2026
* chore(chat): migrate YouTube connector to Composio (#1719)

* chore(chat): migrate YouTube connector to Composio

- Add youtube to allowedArtistConnectors so the YouTube card appears in
  the artist settings Connectors tab alongside TikTok/Instagram
- Rewrite useYoutubeChannel to call POST /api/connectors/actions with
  YOUTUBE_GET_CHANNEL_STATISTICS; consume the raw Google channels.list
  response (snippet/statistics/thumbnails) directly, no remapping
- Rewrite useYoutubeStatus to derive connection state from the Composio
  connectors list instead of the legacy channel-info endpoint
- Rewrite ConnectYouTubeButton + YoutubeLogoutButton to use the Composio
  authorize/disconnect flow already used by TikTok/Instagram
- Drop legacy chat-side OAuth surface: /api/youtube/*,
  /api/auth/callback/google, lib/youtube OAuth/token helpers,
  lib/supabase/youtube_tokens, types/youtube.ts, useYouTubeLoginSuccess,
  the four LLM-tool result components for login/channels/playlist/thumb
  (those tools now come from Composio's YouTube toolkit)

Keeps the get_youtube_revenue MCP tool dispatch in ToolComponents and the
matching VercelChat result components — Composio has no YouTube
Analytics action, so revenue stays custom on the api side.

* fix(chat): request snippet+statistics from YOUTUBE_GET_CHANNEL_STATISTICS

Composio's action defaults the `part` parameter to "statistics" only,
so the response had `snippet: null` and the UI rendered without title
or thumbnail. Pass `part: "snippet,statistics"` so the channel name,
description, and thumbnail come back too.

* chore(chat): remove unused ChatInputYoutubeButton + popover

Both components were orphaned — ChatInputYoutubeButton had zero callers,
which made ChatInputYoutubeButtonPopover and its StatCard / PopoverContent
unreachable too. Drop them entirely.

* fix(chat): unwrap Composio envelope in executeConnectorActionApi

api now passes Composio's ToolExecuteResponse through unchanged
({successful, data, error}). Consumer adapter unwraps once so all
chat-side callers get the underlying provider payload directly
(e.g. youtube.channels.list with `items`); upstream failures throw.

* fix(chat): address AI review feedback on youtube migration

- Share connector refresh across useConnectors instances so logout updates
  sibling status (P1: useYoutubeStatus stale after disconnect)
- Disable disconnect button while in-flight to prevent duplicate requests
- Add aria-label to icon-only disconnect button
- Add aria-label to ConnectYouTubeButton for dense (icon-only) mode
- Propagate connector errors from useYoutubeStatus instead of hard-coded null
- Use semantic <span> for static section caption in ChannelInfo
- Provide fallback message in YouTubeRevenueResult when error text missing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(chat): simplify executeConnectorActionApi envelope handling

The api endpoint always returns Composio's ToolExecuteResponse envelope
(`{ successful, data, error }`) — every Composio Vercel-AI-SDK tool returns
that shape. Drop the defensive non-envelope branch and read the typed shape
directly, removing the `as unknown` casts and inline type guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(chat): React Query for useConnectors + extract fetchYoutubeChannel

- useConnectors now uses React Query so all instances share one cache;
  disconnect/authorize invalidate the connectors key and sibling instances
  re-render automatically. Drops the module-level Set<()=>void> broadcast.
- Extract fetchYoutubeChannel + types to lib/youtube/fetchYoutubeChannel.ts,
  matching the fetchConnectorsApi / authorizeConnectorApi pattern. The hook
  is now ~20 lines and just wires React Query to the helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(chat): drop redundant queryFn return-type annotation

fetchConnectorsApi already returns Promise<ConnectorInfo[]>, so TS infers
the queryFn type correctly without the explicit annotation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): restore authorize catch to honor Promise<string | null> contract

Dropped during the React Query refactor — without the catch, an API failure
rejects the promise instead of returning null, which the only caller
(ConnectYouTubeButton) doesn't handle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): gate useYoutubeChannel on Privy authenticated flag

Matches the codebase convention (useTaskRunStatus, useCatalogs, useArtistPosts,
etc.) — without it, the query runs and immediately throws "Not authenticated"
when the user isn't signed in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(chat): use shared useConnectorHandlers in YouTube buttons

Address sweetman's DRY review feedback on PR #1719: both ConnectYouTubeButton
and YoutubeLogoutButton open-coded the connect/disconnect state machine that
useConnectorHandlers (and ConnectorCard) already provide. Add an optional
onDisconnectSuccess callback to the hook so the logout button can still
invalidate the youtube-channel-info query after a successful disconnect.

* fix(chat): display "YouTube" as one word in connectors UI

Why: the formatConnectorName fallback regex inserts a space before
each capital, turning the API's "YouTube" into "You Tube". Add
youtube to the explicit display-name map alongside the other
multi-cap brands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chat): add YouTube connector description

Without an explicit metadata entry, the YouTube card showed the
generic "Connect to enable this connector" placeholder. Match the
TikTok/Instagram pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com>

* Remove deprecated Stripe session handling and refactor checkout session creation to use access tokens. Update subscription handling in the useSubscribeClick hook to ensure proper authentication flow. (#1728)

---------

Co-authored-by: Arpit Gupta <arpitgupta1214@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: ahmednahima0-beep <ahmednahima0@gmail.com>
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.

2 participants