Skip to content

Migrate stripe subscription checkout sessions#493

Merged
sweetmantech merged 9 commits into
testfrom
feat/stripe-subscription-checkout-sessions
Apr 30, 2026
Merged

Migrate stripe subscription checkout sessions#493
sweetmantech merged 9 commits into
testfrom
feat/stripe-subscription-checkout-sessions

Conversation

@ahmednahima0-beep
Copy link
Copy Markdown
Collaborator

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


Summary by cubic

Adds POST /api/subscriptions/sessions to create Stripe subscription checkout sessions with CORS, strict input/auth validation, and safer error handling. Sessions use a fixed price with a 30‑day trial and return { id, url }; uses stripe and uuid and requires STRIPE_SK.

  • New Features

    • Next.js route with OPTIONS and POST; dynamic and no-cache.
    • Validates JSON body (successUrl URL only) and auth; accountId comes from auth, not the body.
    • Creates a subscription Checkout session with a fixed price and 30‑day trial; returns { id, url }.
    • Adds unit tests for the handler, validation, error mapping, and session creation.
  • Refactors

    • Added lib/stripe/client and STRIPE_SK env; throws if missing; updated .env.example.
    • Hardened errors: mask 5xx as "Internal server error" and return 500 for Stripe failures.
    • Renamed mapToSubscriptionSessionErrorResponse to mapToSubscriptionSessionError; enforce LF line endings via Prettier and .gitattributes.

Written for commit 9c25be8. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Launched Stripe subscription checkout flow with a 30-day trial and account linking.
    • Added a server endpoint to create subscription checkout sessions with strict request validation and standardized JSON error responses.
    • Enabled CORS preflight support for the subscription API to allow reliable cross-origin integration.

- Added "endOfLine": "lf" to the Prettier configuration for consistent line endings.
- Introduced "stripe" and "uuid" packages in package.json for enhanced functionality.
- Updated pnpm-lock.yaml to reflect the addition of new dependencies.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 29, 2026

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

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Apr 30, 2026 7:05pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a dynamic API route plus new Stripe helper modules to validate/authenticate requests, create Stripe Checkout subscription sessions (accountId as client_reference_id, accountId metadata, 30-day trial), and return CORS-enabled JSON responses.

Changes

Cohort / File(s) Summary
API Route
app/api/subscriptions/sessions/route.ts
New route exporting OPTIONS (CORS preflight) and POST handlers; POST delegates to createSubscriptionSessionHandler. Exports dynamic, fetchCache, and revalidate to force dynamic/no-cache behavior.
Stripe client
lib/stripe/client.ts
Adds Stripe singleton that throws if process.env.STRIPE_SK is missing and exports the initialized client.
Session factory
lib/stripe/createStripeSession.ts
Adds createStripeSession(accountId, successUrl) that builds a Checkout Session payload for a subscription: single price, client_reference_id set to accountId, metadata and subscription_data.metadata include accountId, trial_period_days from config, then calls Stripe SDK.
Handler & orchestration
lib/stripe/createSubscriptionSessionHandler.ts
Adds handler that validates request, creates Stripe session, and returns CORS-enabled JSON { id, url } or 400/500 error responses. Internal errors are logged with a consistent prefix.
Schemas & validation
lib/stripe/createSubscriptionSessionSchemas.ts, lib/stripe/validateCreateSubscriptionSessionRequest.ts
Adds strict Zod schema requiring successUrl (valid URL). Validator parses JSON, returns CORS 400 on parse/schema errors, calls validateAuthContext, maps auth failures via mapToSubscriptionSessionError, and returns { accountId, successUrl } on success. Exports ValidatedCreateSubscriptionSessionRequest type.
Error mapping util
lib/stripe/mapToSubscriptionSessionError.ts
Adds function to normalize an upstream NextResponse into a CORS-enabled JSON error body; collapses 5xx to a generic internal error and extracts error/message for others.
Config
lib/stripe/config.ts
Adds STRIPE_SUBSCRIPTION_PRICE_ID and STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS constants.

Sequence Diagram

sequenceDiagram
    participant Client
    participant APIRoute as "API Route\n(app/api/subscriptions/sessions/route.ts)"
    participant Handler as "createSubscriptionSessionHandler\n(lib/stripe)"
    participant Validator as "validateCreateSubscriptionSessionRequest\n(lib/stripe)"
    participant Auth as "validateAuthContext"
    participant Stripe as "Stripe Client\n(lib/stripe/client)"

    Client->>APIRoute: POST /api/subscriptions/sessions
    APIRoute->>Handler: delegate request
    Handler->>Validator: validateCreateSubscriptionSessionRequest(request)
    Validator->>Validator: parse JSON & zod validate
    Validator->>Auth: validateAuthContext(request)
    Auth-->>Validator: accountId OR NextResponse(error)
    Validator-->>Handler: { accountId, successUrl } OR NextResponse(error)
    Handler->>Stripe: createStripeSession(accountId, successUrl)
    Stripe-->>Handler: Session { id, url }
    Handler-->>Client: 200 { id, url } + CORS headers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

✨ Headers wave, the handler sings,
accountId rides the checkout wings.
Thirty days a gentle start,
Stripe returns the session heart. 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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.
Solid & Clean Code ✅ Passed PR demonstrates strong adherence to SOLID and clean code principles with appropriate function sizing, proper separation of concerns, and consistent architectural patterns.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stripe-subscription-checkout-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

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

Copy link
Copy Markdown

@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: 6

🧹 Nitpick comments (3)
lib/stripe/mapToSubscriptionSessionError.ts (1)

4-4: Rename the file to match the exported function.

lib/stripe/mapToSubscriptionSessionError.ts exports mapToSubscriptionSessionErrorResponse, which breaks the repo's file/function naming rule for lib/**/*.ts. This should live in lib/stripe/mapToSubscriptionSessionErrorResponse.ts.

As per coding guidelines, "The file name MUST match the exported function name."

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

In `@lib/stripe/mapToSubscriptionSessionError.ts` at line 4, The file name
lib/stripe/mapToSubscriptionSessionError.ts does not match the exported function
mapToSubscriptionSessionErrorResponse; rename the file to
mapToSubscriptionSessionErrorResponse.ts and update any imports that reference
the old path so they import from
lib/stripe/mapToSubscriptionSessionErrorResponse and continue exporting the same
function name (mapToSubscriptionSessionErrorResponse) to satisfy the repo's
file/function naming rule.
lib/stripe/createSubscriptionSessionSchemas.ts (1)

3-8: Put this body schema in a validate...Body file and export its inferred type.

This is request-body validation, but createSubscriptionSessionSchemas.ts doesn't follow the repo's validation-file convention and doesn't publish the inferred body type alongside the schema. Moving it to validateCreateSubscriptionSessionBody.ts would keep the contract and validator aligned.

Based on learnings, "Create validate functions in validate<EndpointName>Body.ts or validate<EndpointName>Query.ts files that export both the schema and inferred TypeScript type."

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

In `@lib/stripe/createSubscriptionSessionSchemas.ts` around lines 3 - 8, Move the
request-body schema into a new file named
validateCreateSubscriptionSessionBody.ts, export the schema as
createSubscriptionSessionBodySchema and also export its inferred TypeScript type
(e.g., export type CreateSubscriptionSessionBody = z.infer<typeof
createSubscriptionSessionBodySchema>); update any imports/usages that previously
referenced createSubscriptionSessionSchemas to import the schema/type from the
new validateCreateSubscriptionSessionBody.ts module and remove or repurpose the
original createSubscriptionSessionSchemas.ts so the repository follows the
validate<EndpointName>Body.ts convention.
lib/stripe/validateCreateSubscriptionSessionRequest.ts (1)

12-42: This validator is starting to take on too many responsibilities.

It now parses JSON, maps schema errors, invokes auth, remaps auth errors, and builds the validated payload. Splitting body parsing and auth/error mapping into small helpers would keep it under the repo's size threshold and make the branches easier to test.

As per coding guidelines, "Flag functions longer than 20 lines" and "Keep functions small and focused."

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

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts` around lines 12 - 42,
validateCreateSubscriptionSessionRequest is doing too much (JSON parsing, schema
validation error mapping, auth invocation and remapping, and payload
construction); extract responsibilities into small helpers: create a
parseJsonBody(request) helper that returns body or a NextResponse for invalid
JSON, a validateRequestSchema(body, createSubscriptionSessionBodySchema) helper
that returns parsed.data or a NextResponse for schema errors (use the same
first-issue mapping logic), and an authAndMap(request, accountId) helper that
wraps validateAuthContext and maps NextResponse via
mapToSubscriptionSessionErrorResponse; then have
validateCreateSubscriptionSessionRequest call these helpers in sequence and
return the final validated payload ({ accountId: authContext.accountId,
successUrl: parsed.data.successUrl })—keep references to
createSubscriptionSessionBodySchema, validateAuthContext, and
mapToSubscriptionSessionErrorResponse so the logic is easy to locate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/subscriptions/sessions/route.ts`:
- Around line 17-29: Add endpoint tests for the new billing route by exercising
the exported POST handler (route.ts -> POST) and its underlying
createSubscriptionSessionHandler: add a happy-path test that returns session
id/url, and tests for invalid JSON input, schema validation failures, auth
failure, and Stripe API failure; in each test mock/stub the relevant
dependencies (request body parsing, the schema validator used inside
createSubscriptionSessionHandler, auth/token check, and the Stripe client call)
to simulate the different responses and assert the proper HTTP status and error
payloads are returned. Ensure you include both success and all listed error
branches (invalid JSON, schema error, auth failure, Stripe failure) and use the
same handler symbols (POST, createSubscriptionSessionHandler) so tests remain
robust to refactors.

In `@lib/stripe/client.ts`:
- Line 3: The module currently silences TypeScript by using "as string" for
process.env.STRIPE_SK and instantiates stripeClient immediately; instead
validate that process.env.STRIPE_SK is defined at module init and fail fast if
not (e.g., read into a local stripeKey, if falsy throw a clear Error or log and
process.exit(1)), then pass that validated stripeKey into the Stripe constructor
when creating the stripeClient constant to avoid delayed runtime failures.

In `@lib/stripe/createStripeSession.ts`:
- Around line 11-25: Extract the hardcoded Stripe price ID and trial period into
a new config module and update session creation to use those getters: create a
lib/stripe/config-like module that exports constants and functions (e.g.,
getStripePriceId(), getStripeTrialDays()) which read from environment variables
and throw a clear error if missing, then replace the hardcoded
"price_1RyDFD00JObOnOb53PcVOeBz" and subscription_data.trial_period_days = 30 in
the sessionData object (and any duplicated test values) to call those getters so
sessionData (and any tests) use the centralized config values with proper error
handling.

In `@lib/stripe/createSubscriptionSessionHandler.ts`:
- Around line 27-30: In the catch block of createSubscriptionSessionHandler,
stop returning raw error.message and a 400; instead keep logging the full error
(e.g., console.error("[createSubscriptionSessionHandler]", error)) but return a
generic server-side response such as NextResponse.json({ error: "Failed to
create subscription session" }, { status: 502, headers: getCorsHeaders() }) to
avoid leaking provider/internal details and to correctly classify server errors;
update the return to use this generic message/status and ensure getCorsHeaders()
and NextResponse.json remain used for the response.

In `@lib/stripe/createSubscriptionSessionSchemas.ts`:
- Line 5: The successUrl field currently only checks URL syntax; update the Zod
schema (the successUrl validator in createSubscriptionSessionSchemas.ts) to
restrict destinations by either enforcing a relative path (e.g., must start with
"/") or by parsing the URL and validating its origin against a maintained
allowlist of trusted hosts/schemes before acceptance; implement the check as
part of the successUrl refinement or custom validator so any non-allowed
absolute URL is rejected and a clear validation error is returned.

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts`:
- Around line 31-33: The code is deriving accountId from the request body by
passing parsed.data.accountId into validateAuthContext; remove that override and
call validateAuthContext(request) so the account is derived from the
authentication token instead of the body, and then remove accountId from the
request body/schema (so validateCreateSubscriptionSessionRequest no longer
expects parsed.data.accountId). Update the call site (validateAuthContext in
validateCreateSubscriptionSessionRequest.ts) to omit the second argument and
adjust any related schema or parsing that included accountId.

---

Nitpick comments:
In `@lib/stripe/createSubscriptionSessionSchemas.ts`:
- Around line 3-8: Move the request-body schema into a new file named
validateCreateSubscriptionSessionBody.ts, export the schema as
createSubscriptionSessionBodySchema and also export its inferred TypeScript type
(e.g., export type CreateSubscriptionSessionBody = z.infer<typeof
createSubscriptionSessionBodySchema>); update any imports/usages that previously
referenced createSubscriptionSessionSchemas to import the schema/type from the
new validateCreateSubscriptionSessionBody.ts module and remove or repurpose the
original createSubscriptionSessionSchemas.ts so the repository follows the
validate<EndpointName>Body.ts convention.

In `@lib/stripe/mapToSubscriptionSessionError.ts`:
- Line 4: The file name lib/stripe/mapToSubscriptionSessionError.ts does not
match the exported function mapToSubscriptionSessionErrorResponse; rename the
file to mapToSubscriptionSessionErrorResponse.ts and update any imports that
reference the old path so they import from
lib/stripe/mapToSubscriptionSessionErrorResponse and continue exporting the same
function name (mapToSubscriptionSessionErrorResponse) to satisfy the repo's
file/function naming rule.

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts`:
- Around line 12-42: validateCreateSubscriptionSessionRequest is doing too much
(JSON parsing, schema validation error mapping, auth invocation and remapping,
and payload construction); extract responsibilities into small helpers: create a
parseJsonBody(request) helper that returns body or a NextResponse for invalid
JSON, a validateRequestSchema(body, createSubscriptionSessionBodySchema) helper
that returns parsed.data or a NextResponse for schema errors (use the same
first-issue mapping logic), and an authAndMap(request, accountId) helper that
wraps validateAuthContext and maps NextResponse via
mapToSubscriptionSessionErrorResponse; then have
validateCreateSubscriptionSessionRequest call these helpers in sequence and
return the final validated payload ({ accountId: authContext.accountId,
successUrl: parsed.data.successUrl })—keep references to
createSubscriptionSessionBodySchema, validateAuthContext, and
mapToSubscriptionSessionErrorResponse so the logic is easy to locate.
🪄 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: bf056a7f-5e18-47f1-af1a-bce3fc86954b

📥 Commits

Reviewing files that changed from the base of the PR and between 5487356 and 58685e5.

⛔ Files ignored due to path filters (8)
  • .gitattributes is excluded by none and included by none
  • .prettierrc is excluded by none and included by none
  • lib/stripe/__tests__/createStripeSession.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/stripe/__tests__/createSubscriptionSessionHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/stripe/__tests__/mapToSubscriptionSessionError.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/stripe/__tests__/validateCreateSubscriptionSessionRequest.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • package.json is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (7)
  • app/api/subscriptions/sessions/route.ts
  • lib/stripe/client.ts
  • lib/stripe/createStripeSession.ts
  • lib/stripe/createSubscriptionSessionHandler.ts
  • lib/stripe/createSubscriptionSessionSchemas.ts
  • lib/stripe/mapToSubscriptionSessionError.ts
  • lib/stripe/validateCreateSubscriptionSessionRequest.ts

Comment on lines +17 to +29
/**
* POST /api/subscriptions/sessions: creates a Stripe subscription checkout session.
*
* @param request - The incoming HTTP request.
* @returns A NextResponse with session `id` and `url`, or an error body.
*/
export async function POST(request: NextRequest) {
return createSubscriptionSessionHandler(request);
}

export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
export const revalidate = 0;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 29, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Please add endpoint coverage for this new billing route.

I don't see tests in this change for the happy path plus the invalid JSON, schema error, auth failure, and Stripe failure branches. For a new public payment endpoint, that leaves the main regression paths unguarded.

As per coding guidelines, "Write tests for new API endpoints covering all success and error paths."

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

In `@app/api/subscriptions/sessions/route.ts` around lines 17 - 29, Add endpoint
tests for the new billing route by exercising the exported POST handler
(route.ts -> POST) and its underlying createSubscriptionSessionHandler: add a
happy-path test that returns session id/url, and tests for invalid JSON input,
schema validation failures, auth failure, and Stripe API failure; in each test
mock/stub the relevant dependencies (request body parsing, the schema validator
used inside createSubscriptionSessionHandler, auth/token check, and the Stripe
client call) to simulate the different responses and assert the proper HTTP
status and error payloads are returned. Ensure you include both success and all
listed error branches (invalid JSON, schema error, auth failure, Stripe failure)
and use the same handler symbols (POST, createSubscriptionSessionHandler) so
tests remain robust to refactors.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Already implemented

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me verify the existing test coverage.


🏁 Script executed:

#!/bin/bash
# Find all test files related to the stripe subscription session work
fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | grep -i -E "(stripe|subscription|session)" || true

# Also search broadly for any test files referencing the handler or route
rg -rn "createSubscriptionSessionHandler\|POST.*subscription\|subscriptions/sessions" --type=ts -g "**/*.test.*" -g "**/*.spec.*" -l 2>/dev/null || true

# List all test files in the lib/stripe directory
fd -e test.ts -e spec.ts . lib/stripe 2>/dev/null || true

# Look for test files anywhere mentioning the key symbols
rg -rn "createSubscriptionSessionHandler\|validateCreateSubscriptionSessionRequest\|mapToSubscriptionSessionError\|createStripeSession" --type=ts -l 2>/dev/null | head -30

Length of output: 0


Let me cast a wider net.


🏁 Script executed:

#!/bin/bash
# Find ALL test/spec files in the repo
echo "=== All test/spec files ==="
fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | head -50

# Search for test files with any naming pattern
echo "=== Files with .test. or .spec. in name ==="
find . -name "*.test.*" -o -name "*.spec.*" | grep -v node_modules | grep -v ".next" | head -50

# Check __tests__ directories
echo "=== __tests__ directories ==="
find . -type d -name "__tests__" | grep -v node_modules | head -20

# Check if there's a tests or test directory at root
echo "=== test/tests directories ==="
find . -maxdepth 3 -type d -name "test" -o -type d -name "tests" | grep -v node_modules | head -20

# List files in lib/stripe
echo "=== lib/stripe contents ==="
ls lib/stripe/ 2>/dev/null || find . -path "*/lib/stripe*" -not -path "*/node_modules/*" | head -30

Length of output: 343


@ahmednahima0-beep, I scanned the entire branch for test files (.test.ts, .spec.ts, __tests__/ directories, etc.) and found none. The lib/stripe/ directory only contains the six implementation files:

lib/stripe/client.ts
lib/stripe/createStripeSession.ts
lib/stripe/createSubscriptionSessionHandler.ts
lib/stripe/createSubscriptionSessionSchemas.ts
lib/stripe/mapToSubscriptionSessionError.ts
lib/stripe/validateCreateSubscriptionSessionRequest.ts

No corresponding test files exist for these modules or for the app/api/subscriptions/sessions/route.ts route handler.

Per the coding guidelines, tests are required for new API endpoints covering all success and error paths. For this endpoint, the missing test cases are:

Branch What to assert
✅ Happy path Returns { id, url } with HTTP 200
❌ Invalid JSON body Returns { error: "Invalid JSON body" } with HTTP 400
❌ Schema validation failure Returns first Zod issue message with HTTP 400
❌ Auth failure Passes auth error through mapToSubscriptionSessionError, preserving original status
❌ Stripe session missing url Returns { error: "Checkout session URL missing" } with HTTP 400
❌ Stripe API throws Returns { error: <message or "failed"> } with HTTP 400

Could you please add the test file(s)? If you'd like, I can generate a starter test suite for all six branches. Just let me know!


🧠 Learnings used
Learnt from: CR
Repo: recoupable/api PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-17T19:09:32.948Z
Learning: Applies to app/api/**/route.ts : Write tests for new API endpoints covering all success and error paths

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Already added all tests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

Let me verify the tests are present on the branch.


🏁 Script executed:

#!/bin/bash
echo "=== All test/spec files in repo ==="
find . -name "*.test.*" -o -name "*.spec.*" | grep -v node_modules | grep -v ".next" | grep -v ".git" | sort

echo ""
echo "=== __tests__ directories ==="
find . -type d -name "__tests__" | grep -v node_modules | grep -v ".git"

echo ""
echo "=== lib/stripe directory contents ==="
find . -path "*/lib/stripe*" -not -path "*/node_modules/*" -not -path "*/.git/*" | sort

echo ""
echo "=== Any file referencing createSubscriptionSessionHandler ==="
rg -rn "createSubscriptionSessionHandler" --type=ts -l 2>/dev/null

echo ""
echo "=== Any file referencing subscriptions/sessions route ==="
rg -rn "subscriptions/sessions\|POST.*session\|createStripeSession\|mapToSubscriptionSessionError\|validateCreateSubscriptionSession" --type=ts -l 2>/dev/null | head -30

Length of output: 588


@ahmednahima0-beep, I re-scanned the branch and the results are the same as before — no test files exist anywhere in the repository:

=== All test/spec files in repo ===
(empty)

=== __tests__ directories ===
(empty)

=== lib/stripe directory contents ===
./lib/stripe/client.ts
./lib/stripe/createStripeSession.ts
./lib/stripe/createSubscriptionSessionHandler.ts
./lib/stripe/createSubscriptionSessionSchemas.ts
./lib/stripe/mapToSubscriptionSessionError.ts
./lib/stripe/validateCreateSubscriptionSessionRequest.ts

It's possible the tests were added locally but not yet pushed to the branch. Could you push the commits with the test files so they're visible on the PR?

In the meantime, if it would help, I can generate a complete starter test suite covering all the required branches:

Branch File under test
✅ Happy path — returns { id, url } / HTTP 200 route.tsPOST
❌ Invalid JSON body — HTTP 400 validateCreateSubscriptionSessionRequest.ts
❌ Schema validation failure (missing/invalid successUrl) — HTTP 400 createSubscriptionSessionSchemas.ts
❌ Auth failure — preserves original status via mapToSubscriptionSessionError validateCreateSubscriptionSessionRequest.ts
❌ Stripe session missing url — HTTP 400 createSubscriptionSessionHandler.ts
❌ Stripe API throws — HTTP 500 createSubscriptionSessionHandler.ts

Just say the word and I'll scaffold the test files!


🧠 Learnings used
Learnt from: CR
Repo: recoupable/api PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-17T19:09:32.948Z
Learning: Applies to app/api/**/route.ts : Write tests for new API endpoints covering all success and error paths

Comment thread lib/stripe/client.ts Outdated
Comment thread lib/stripe/createStripeSession.ts
Comment thread lib/stripe/createSubscriptionSessionHandler.ts Outdated

export const createSubscriptionSessionBodySchema = z
.object({
successUrl: z.string().min(1, "successUrl is required").url("successUrl must be a valid URL"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restrict successUrl to trusted destinations.

This only checks that successUrl is syntactically a URL, so any authenticated caller can create a checkout flow that redirects to an arbitrary site after payment. For a billing endpoint, that is an open-redirect/phishing risk. Prefer a relative path or enforce an allowlist of approved hosts/schemes before sending it to Stripe.

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

In `@lib/stripe/createSubscriptionSessionSchemas.ts` at line 5, The successUrl
field currently only checks URL syntax; update the Zod schema (the successUrl
validator in createSubscriptionSessionSchemas.ts) to restrict destinations by
either enforcing a relative path (e.g., must start with "/") or by parsing the
URL and validating its origin against a maintained allowlist of trusted
hosts/schemes before acceptance; implement the check as part of the successUrl
refinement or custom validator so any non-allowed absolute URL is rejected and a
clear validation error is returned.

Comment thread lib/stripe/validateCreateSubscriptionSessionRequest.ts Outdated
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.

5 issues found across 15 files

Confidence score: 2/5

  • Merge risk is high because there are multiple high-severity, high-confidence issues in Stripe error handling and initialization paths, with potential user-facing failures and internal detail exposure.
  • lib/stripe/createSubscriptionSessionHandler.ts and lib/stripe/mapToSubscriptionSessionError.ts currently return raw upstream/internal error text for server-side failures; this can leak implementation details and should be replaced with a fixed 500-safe message.
  • lib/stripe/client.ts constructs the Stripe client at module load with an asserted env var, which can fail at import/build time when STRIPE_SK is missing; this can break runtime startup rather than just a single request path.
  • Pay close attention to lib/stripe/createSubscriptionSessionHandler.ts, lib/stripe/mapToSubscriptionSessionError.ts, lib/stripe/client.ts, and lib/stripe/createStripeSession.ts - sanitize 5xx responses, avoid import-time client failures, and move the hardcoded price ID to configuration.
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/createSubscriptionSessionHandler.ts">

<violation number="1" location="lib/stripe/createSubscriptionSessionHandler.ts:29">
P1: Use a generic 500 response in this catch block instead of returning `error.message` with status 400.

(Based on your team's feedback about avoiding raw exception text in API error responses.) [FEEDBACK_USED]</violation>
</file>

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

<violation number="1" location="lib/stripe/mapToSubscriptionSessionError.ts:4">
P2: Custom agent: **Module should export a single primary function whose name matches the filename**

Primary exported function name does not match the filename (mapToSubscriptionSessionError vs mapToSubscriptionSessionErrorResponse).</violation>

<violation number="2" location="lib/stripe/mapToSubscriptionSessionError.ts:12">
P1: Do not forward upstream `error/message` text for 5xx responses; return a fixed "Internal server error" message to avoid leaking internal details.

(Based on your team's feedback about masking internal details in 500 responses.) [FEEDBACK_USED]</violation>
</file>

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

<violation number="1" location="lib/stripe/createStripeSession.ts:14">
P2: Avoid hardcoding the Stripe Price ID in code; load it from configuration so checkout sessions don’t break across environments or price changes.</violation>
</file>

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

<violation number="1" location="lib/stripe/client.ts:3">
P1: Avoid top-level Stripe client construction with an asserted env var; on stripe-node v17+ this can fail at import/build time when `STRIPE_SK` is unavailable.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client
    participant Route as API Route (Next.js)
    participant Handler as Session Handler
    participant Auth as Auth Service
    participant Stripe as Stripe API

    Note over Client,Stripe: NEW: Stripe Subscription Checkout Flow

    Client->>Route: OPTIONS (CORS Preflight)
    Route-->>Client: 200 OK + CORS Headers

    Client->>Route: POST /api/subscriptions/sessions
    Route->>Handler: createSubscriptionSessionHandler(req)

    Handler->>Handler: NEW: Validate JSON & Zod Schema
    alt Invalid Input
        Handler-->>Client: 400 Bad Request { error }
    end

    Handler->>Auth: validateAuthContext(request, accountId)
    
    alt Auth Failure
        Auth-->>Handler: NextResponse (Error)
        Handler->>Handler: NEW: mapToSubscriptionSessionErrorResponse()
        Handler-->>Client: 4xx Error { error: message }
    else Auth Success
        Auth-->>Handler: AuthContext (accountId)
    end

    Handler->>Stripe: NEW: Create Checkout Session (STRIPE_SK)
    Note right of Stripe: Sets 30-day trial,<br/>fixed price ID,<br/>and metadata.accountId
    
    alt Stripe Success
        Stripe-->>Handler: Session Object (id, url)
        Handler-->>Client: 200 OK { id, url }
    else Stripe Error / URL Missing
        Stripe-->>Handler: Error / Null URL
        Handler-->>Client: 400 Bad Request { error }
    end
Loading

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

Comment thread lib/stripe/createSubscriptionSessionHandler.ts Outdated
Comment thread lib/stripe/mapToSubscriptionSessionError.ts
Comment thread lib/stripe/client.ts Outdated
Comment thread lib/stripe/createStripeSession.ts Outdated
Comment thread lib/stripe/mapToSubscriptionSessionError.ts Outdated
…ToSubscriptionSessionError

- Updated the function name from `mapToSubscriptionSessionErrorResponse` to `mapToSubscriptionSessionError` for consistency and clarity.
- Adjusted import statements and references in related files to reflect the new function name.
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
lib/stripe/validateCreateSubscriptionSessionRequest.ts (1)

31-33: ⚠️ Potential issue | 🟠 Major

Remove request-body account override from auth validation.

Passing parsed.data.accountId into validateAuthContext reintroduces body-driven account selection; account identity should be derived from auth context only for this endpoint.

Suggested fix
-  const authContext = await validateAuthContext(request, {
-    accountId: parsed.data.accountId,
-  });
+  const authContext = await validateAuthContext(request);

Based on learnings, "Applies to app/api/**/route.ts : Never use account_id in request bodies or tool schemas; always derive the account ID from authentication."

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

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts` around lines 31 - 33,
The auth validation currently overrides the account identity by passing
parsed.data.accountId into validateAuthContext; remove that override so account
ID is derived solely from the authentication context — locate the call to
validateAuthContext in validateCreateSubscriptionSessionRequest.ts (where
authContext is assigned) and change it to call validateAuthContext without the
second argument (remove parsed.data.accountId reference and any related logic
that treats the request body as the source of account identity).
🧹 Nitpick comments (2)
lib/stripe/validateCreateSubscriptionSessionRequest.ts (2)

4-10: Align validate-file structure with the validateBody/validateQuery convention.

For lib/**/validate*.ts, keep the validation schema and inferred type exported together in a validate<EndpointName>Body.ts/validate<EndpointName>Query.ts file instead of splitting schema/type across separate files.

As per coding guidelines, "Create validate functions in validate<EndpointName>Body.ts or validate<EndpointName>Query.ts files that export both the schema and inferred TypeScript type."

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

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts` around lines 4 - 10,
The validateCreateSubscriptionSessionRequest.ts file splits the validation
schema and inferred type; align with the convention by consolidating them into a
validateCreateSubscriptionSessionBody.ts file that exports
createSubscriptionSessionBodySchema and an inferred TypeScript type (replace
ValidatedCreateSubscriptionSessionRequest) together; update the export name to
something like CreateSubscriptionSessionBody (or
CreateSubscriptionSessionBodyType) inferred from
createSubscriptionSessionBodySchema using z.infer, remove the standalone
ValidatedCreateSubscriptionSessionRequest, and update any imports that
referenced the old file to import both the schema and the inferred type from
validateCreateSubscriptionSessionBody.ts (you can keep
mapToSubscriptionSessionError usage elsewhere unchanged).

12-42: Split the validator into smaller helpers (parse, schema, auth).

This function is over 20 lines and mixes multiple responsibilities; extracting helpers will improve readability and testability.

As per coding guidelines, "Flag functions longer than 20 lines ... Keep functions small and focused."

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

In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts` around lines 12 - 42,
The validateCreateSubscriptionSessionRequest function is doing parsing, schema
validation, and auth checks in one long block; split it into three small
helpers: a parseJson(request) helper that reads request.json() and returns
either parsed body or a NextResponse error, a
validateCreateSubscriptionSessionBody(body) helper that calls
createSubscriptionSessionBodySchema.safeParse and returns typed data or a
NextResponse with the first issue message, and an
authorizeCreateSubscriptionSession(request, accountId) helper that wraps
validateAuthContext and mapToSubscriptionSessionError to return either an
authContext or a NextResponse; then refactor
validateCreateSubscriptionSessionRequest to call these three helpers in sequence
and return the final validated object (using symbols
validateCreateSubscriptionSessionRequest,
createSubscriptionSessionBodySchema.safeParse, validateAuthContext,
mapToSubscriptionSessionError to locate code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts`:
- Around line 31-33: The auth validation currently overrides the account
identity by passing parsed.data.accountId into validateAuthContext; remove that
override so account ID is derived solely from the authentication context —
locate the call to validateAuthContext in
validateCreateSubscriptionSessionRequest.ts (where authContext is assigned) and
change it to call validateAuthContext without the second argument (remove
parsed.data.accountId reference and any related logic that treats the request
body as the source of account identity).

---

Nitpick comments:
In `@lib/stripe/validateCreateSubscriptionSessionRequest.ts`:
- Around line 4-10: The validateCreateSubscriptionSessionRequest.ts file splits
the validation schema and inferred type; align with the convention by
consolidating them into a validateCreateSubscriptionSessionBody.ts file that
exports createSubscriptionSessionBodySchema and an inferred TypeScript type
(replace ValidatedCreateSubscriptionSessionRequest) together; update the export
name to something like CreateSubscriptionSessionBody (or
CreateSubscriptionSessionBodyType) inferred from
createSubscriptionSessionBodySchema using z.infer, remove the standalone
ValidatedCreateSubscriptionSessionRequest, and update any imports that
referenced the old file to import both the schema and the inferred type from
validateCreateSubscriptionSessionBody.ts (you can keep
mapToSubscriptionSessionError usage elsewhere unchanged).
- Around line 12-42: The validateCreateSubscriptionSessionRequest function is
doing parsing, schema validation, and auth checks in one long block; split it
into three small helpers: a parseJson(request) helper that reads request.json()
and returns either parsed body or a NextResponse error, a
validateCreateSubscriptionSessionBody(body) helper that calls
createSubscriptionSessionBodySchema.safeParse and returns typed data or a
NextResponse with the first issue message, and an
authorizeCreateSubscriptionSession(request, accountId) helper that wraps
validateAuthContext and mapToSubscriptionSessionError to return either an
authContext or a NextResponse; then refactor
validateCreateSubscriptionSessionRequest to call these three helpers in sequence
and return the final validated object (using symbols
validateCreateSubscriptionSessionRequest,
createSubscriptionSessionBodySchema.safeParse, validateAuthContext,
mapToSubscriptionSessionError to locate code).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2a914f53-62a7-4169-b5a4-c1bd3dff1328

📥 Commits

Reviewing files that changed from the base of the PR and between 58685e5 and 50d3073.

⛔ Files ignored due to path filters (2)
  • app/api/subscriptions/sessions/__tests__/route.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by app/**
  • lib/stripe/__tests__/mapToSubscriptionSessionError.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • lib/stripe/mapToSubscriptionSessionError.ts
  • lib/stripe/validateCreateSubscriptionSessionRequest.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/stripe/mapToSubscriptionSessionError.ts

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 (changes from recent commits).

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="app/api/subscriptions/sessions/__tests__/route.test.ts">

<violation number="1" location="app/api/subscriptions/sessions/__tests__/route.test.ts:1">
P2: Custom agent: **Enforce Clear Code Style and Maintainability Practices**

New test file exceeds the 100-line file size limit required by the maintainability rule.</violation>
</file>

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

Comment thread app/api/subscriptions/sessions/__tests__/route.test.ts Outdated
- Changed the ACCOUNT constant in route.test.ts from "123e4567-e89b-12d3-a456-426614174000" to "123e4567-e89b-12d3-a456-426614174001" to ensure consistency in test cases.
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 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 5 unresolved issues from previous reviews.

…ndling

- Introduced the STRIPE_SK environment variable in .env.example to ensure proper configuration for Stripe integration.
- Updated the Stripe client initialization to throw an error if STRIPE_SK is not set, enhancing robustness.
- Modified error handling in createSubscriptionSessionHandler to return a 500 status with a generic error message for internal server errors.
- Adjusted validation logic in validateCreateSubscriptionSessionRequest to remove the optional accountId field, simplifying the request structure.
- Enhanced mapToSubscriptionSessionError to mask upstream error messages for 5xx responses, improving security.
- Updated tests to reflect changes in error handling and validation logic.
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 14 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

…ent access

- Merged shared and artist toolkits into the actions catalog to ensure all executable actions are listed, reflecting the full access available to the chat agent.
- Updated the executeConnectorAction function to also utilize the merged toolkit, ensuring that all actions listed in the catalog are executable.
- Extracted ConnectorActionNotFoundError into a separate file for cleaner error handling and improved test organization.
- Rewritten tests to mock the new toolkit structure, ensuring comprehensive coverage of the updated functionality.
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 (changes from recent commits).

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/__tests__/client.test.ts">

<violation number="1" location="lib/stripe/__tests__/client.test.ts:16">
P2: Environment restoration is unsafe: assigning `saved` back directly can leave `STRIPE_SK` in an incorrect state when the original value was undefined. Restore by deleting the key when `saved` is undefined.</violation>
</file>

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

Comment thread lib/stripe/__tests__/client.test.ts
…ants

- Updated createStripeSession to utilize STRIPE_SUBSCRIPTION_PRICE_ID and STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS constants for better maintainability and configuration management.
- Enhanced tests for createStripeSession to reflect these changes, ensuring consistency in the subscription parameters.
- Improved error handling in client tests by refining the management of the STRIPE_SK environment variable.
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
lib/stripe/createStripeSession.ts (1)

9-33: ⚡ Quick win

Refactor createStripeSession into a slimmer orchestrator function.

createStripeSession is currently >20 lines (Line 9–33). Extract payload construction to a local helper so the exported function stays focused on orchestration.

♻️ Proposed refactor
 import type Stripe from "stripe";
 import { v4 as uuidV4 } from "uuid";
 import stripeClient from "@/lib/stripe/client";
 import {
   STRIPE_SUBSCRIPTION_PRICE_ID,
   STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS,
 } from "@/lib/stripe/config";
 
+function buildStripeSubscriptionSessionData(
+  accountId: string,
+  successUrl: string,
+): Stripe.Checkout.SessionCreateParams {
+  const metadata = { accountId };
+
+  return {
+    line_items: [
+      {
+        price: STRIPE_SUBSCRIPTION_PRICE_ID,
+        quantity: 1,
+      },
+    ],
+    mode: "subscription",
+    client_reference_id: uuidV4(),
+    metadata,
+    subscription_data: {
+      metadata,
+      trial_period_days: STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS,
+    },
+    success_url: successUrl,
+  };
+}
+
 export async function createStripeSession(
   accountId: string,
   successUrl: string,
 ): Promise<Stripe.Checkout.Session> {
-  const metadata = { accountId };
-
-  const sessionData: Stripe.Checkout.SessionCreateParams = {
-    line_items: [
-      {
-        price: STRIPE_SUBSCRIPTION_PRICE_ID,
-        quantity: 1,
-      },
-    ],
-    mode: "subscription",
-    client_reference_id: uuidV4(),
-    metadata,
-    subscription_data: {
-      metadata,
-      trial_period_days: STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS,
-    },
-    success_url: successUrl,
-  };
-
-  return stripeClient.checkout.sessions.create(sessionData);
+  return stripeClient.checkout.sessions.create(
+    buildStripeSubscriptionSessionData(accountId, successUrl),
+  );
 }

As per coding guidelines, "Flag functions longer than 20 lines" and "Keep functions small and focused."

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

In `@lib/stripe/createStripeSession.ts` around lines 9 - 33, The
createStripeSession function assembles the Stripe payload inline making the
exported function >20 lines; extract the payload construction into a small local
helper (e.g., buildStripeSessionParams or createStripePayload) that accepts
accountId and successUrl and returns a Stripe.Checkout.SessionCreateParams
object (containing line_items using STRIPE_SUBSCRIPTION_PRICE_ID,
client_reference_id: uuidV4(), metadata, subscription_data with
trial_period_days, and success_url), then reduce createStripeSession to simply
call that helper and pass its result to
stripeClient.checkout.sessions.create(sessionParams) and return the created
session; keep the helper unexported and colocated in the same file so the
exported createStripeSession remains a slim orchestrator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/stripe/createStripeSession.ts`:
- Around line 9-33: The createStripeSession function assembles the Stripe
payload inline making the exported function >20 lines; extract the payload
construction into a small local helper (e.g., buildStripeSessionParams or
createStripePayload) that accepts accountId and successUrl and returns a
Stripe.Checkout.SessionCreateParams object (containing line_items using
STRIPE_SUBSCRIPTION_PRICE_ID, client_reference_id: uuidV4(), metadata,
subscription_data with trial_period_days, and success_url), then reduce
createStripeSession to simply call that helper and pass its result to
stripeClient.checkout.sessions.create(sessionParams) and return the created
session; keep the helper unexported and colocated in the same file so the
exported createStripeSession remains a slim orchestrator.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06455125-b5b7-453b-ab81-981ada4a5302

📥 Commits

Reviewing files that changed from the base of the PR and between 9c25be8 and a2874df.

⛔ Files ignored due to path filters (4)
  • app/api/subscriptions/sessions/__tests__/route.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by app/**
  • lib/stripe/__tests__/client.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/stripe/__tests__/createStripeSession.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/stripe/__tests__/createSubscriptionSessionSchemas.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • lib/stripe/config.ts
  • lib/stripe/createStripeSession.ts
✅ Files skipped from review due to trivial changes (1)
  • lib/stripe/config.ts

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 4 files (changes from recent commits).

Requires human review: This PR introduces a new Stripe subscription checkout API endpoint. Changes to payment flows and subscription logic are high-impact and require human review.

Comment thread .gitattributes Outdated
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.

KISS - please delete this file.

Comment thread .prettierrc
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.

Why are you making changes to our prettier rules in a pr for stripe endpoint implementation?

- Deleted the .gitattributes file to simplify project configuration.
- Updated .prettierrc by removing the "endOfLine" setting for cleaner formatting consistency.
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 introduces a new Stripe subscription checkout flow and API endpoint. Changes involving payments and business logic for billing require human architectural review.

YAGNI: client_reference_id is a soft-convenience pointer string with no
readers in either api or chat — never round-trips, never queried via the
sessions.list API (it isn't a filterable param). The migration was
generating a throwaway random uuid that mapped to nothing internally.

Setting it to accountId instead:
- removes the uuid dep entirely (only callsite in this repo)
- makes the Stripe dashboard's "Client reference" column actually
  useful for support/debugging
- matches metadata.accountId so reconciliation is consistent

Drop the uuid mock from createStripeSession.test.ts and flip the
not.toBe("acc-1") assertion to toBe("acc-1") — encoding the new
contract instead of the old "value must be a random UUID" stance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 4 files (changes from recent commits).

Requires human review: Adds a new API endpoint for Stripe subscriptions. Payment and billing logic are critical business functions that require human architectural and security review.

@sweetmantech
Copy link
Copy Markdown
Contributor

Manual verification on preview deployment

Tested POST /api/subscriptions/sessions against https://api-git-feat-stripe-subscription-checkout-sessions-recoup.vercel.app. All 7 cases pass.

Setup

  • Created a fresh agent on the preview deployment itself via POST /api/agents/signup (per the docs, with a random agent+pr493-…@recoupable.com email) so auth used a key issued by the same env as the endpoint under test
  • account_id for that agent: 077c4e87-f8a6-4329-b310-f66c5e754bca

Test cases

# Case Expected Actual
1 No API key 401 auth error 401 {"error":"Exactly one of x-api-key or Authorization must be provided"}
2 Bad JSON body 400 Invalid JSON body 400 {"error":"Invalid JSON body"}
3 Missing successUrl 400 zod error 400 {"error":"Invalid input: expected string, received undefined"}
4 Empty successUrl: "" 400 successUrl is required 400 {"error":"successUrl is required"}
5 Non-URL string 400 successUrl must be a valid URL 400 {"error":"successUrl must be a valid URL"}
6 Strict schema (extra prop) 400 unrecognized key 400 {"error":"Unrecognized key: \"extra\""}
7 Happy path: {successUrl: "https://chat.recoupable.com/success"} 200 {id, url} 200 with real Stripe live-mode session ✓

Stripe session created (T7) — for review

You can validate via the Stripe Dashboard search for either the session id or client_reference_id = 077c4e87-f8a6-4329-b310-f66c5e754bca.

What this confirms

  • ✅ Validator + handler chain (validateCreateSubscriptionSessionRequestcreateStripeSession) works end-to-end
  • ✅ Auth gate enforced via validateAuthContext (api key issued by POST /api/agents/signup accepted)
  • ✅ Strict zod schema rejects extra props
  • ✅ The YAGNI follow-up (drop uuid dep, set client_reference_id: accountId per fa45d0dc) reaches Stripe successfully — the live API accepted the accountId UUID string as a client_reference_id with no error

⚠️ Environment note (not a PR issue)

The cs_live_ prefix and pay.recoupable.com domain reveal the preview deployment is wired to LIVE Stripe API keys, not Stripe test mode. So this verification's session sits in the production Stripe dashboard. It will expire on its own without harm, but worth flagging as a deployment-env config item for follow-up — preview deploys probably should hit Stripe's test mode (STRIPE_SK set to a sk_test_… key in preview env) so smoke tests don't pollute the live dashboard.

@sweetmantech sweetmantech merged commit 7d4611c into test Apr 30, 2026
6 checks passed
@sweetmantech sweetmantech deleted the feat/stripe-subscription-checkout-sessions branch April 30, 2026 19:15
sweetmantech added a commit that referenced this pull request Apr 30, 2026
* chore: update Prettier configuration and add new dependencies

- Added "endOfLine": "lf" to the Prettier configuration for consistent line endings.
- Introduced "stripe" and "uuid" packages in package.json for enhanced functionality.
- Updated pnpm-lock.yaml to reflect the addition of new dependencies.

* refactor(stripe): rename mapToSubscriptionSessionErrorResponse to mapToSubscriptionSessionError

- Updated the function name from `mapToSubscriptionSessionErrorResponse` to `mapToSubscriptionSessionError` for consistency and clarity.
- Adjusted import statements and references in related files to reflect the new function name.

* fix(tests): update ACCOUNT UUID in route.test.ts for consistency

- Changed the ACCOUNT constant in route.test.ts from "123e4567-e89b-12d3-a456-426614174000" to "123e4567-e89b-12d3-a456-426614174001" to ensure consistency in test cases.

* feat(stripe): add STRIPE_SK environment variable and improve error handling

- Introduced the STRIPE_SK environment variable in .env.example to ensure proper configuration for Stripe integration.
- Updated the Stripe client initialization to throw an error if STRIPE_SK is not set, enhancing robustness.
- Modified error handling in createSubscriptionSessionHandler to return a 500 status with a generic error message for internal server errors.
- Adjusted validation logic in validateCreateSubscriptionSessionRequest to remove the optional accountId field, simplifying the request structure.
- Enhanced mapToSubscriptionSessionError to mask upstream error messages for 5xx responses, improving security.
- Updated tests to reflect changes in error handling and validation logic.

* fix(connectors): unify action catalog and execution paths for consistent access

- Merged shared and artist toolkits into the actions catalog to ensure all executable actions are listed, reflecting the full access available to the chat agent.
- Updated the executeConnectorAction function to also utilize the merged toolkit, ensuring that all actions listed in the catalog are executable.
- Extracted ConnectorActionNotFoundError into a separate file for cleaner error handling and improved test organization.
- Rewritten tests to mock the new toolkit structure, ensuring comprehensive coverage of the updated functionality.

* feat(stripe): refactor createStripeSession to use configuration constants

- Updated createStripeSession to utilize STRIPE_SUBSCRIPTION_PRICE_ID and STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS constants for better maintainability and configuration management.
- Enhanced tests for createStripeSession to reflect these changes, ensuring consistency in the subscription parameters.
- Improved error handling in client tests by refining the management of the STRIPE_SK environment variable.

* chore: remove .gitattributes and update Prettier configuration

- Deleted the .gitattributes file to simplify project configuration.
- Updated .prettierrc by removing the "endOfLine" setting for cleaner formatting consistency.

* chore(stripe): drop uuid dep, use accountId as client_reference_id

YAGNI: client_reference_id is a soft-convenience pointer string with no
readers in either api or chat — never round-trips, never queried via the
sessions.list API (it isn't a filterable param). The migration was
generating a throwaway random uuid that mapped to nothing internally.

Setting it to accountId instead:
- removes the uuid dep entirely (only callsite in this repo)
- makes the Stripe dashboard's "Client reference" column actually
  useful for support/debugging
- matches metadata.accountId so reconciliation is consistent

Drop the uuid mock from createStripeSession.test.ts and flip the
not.toBe("acc-1") assertion to toBe("acc-1") — encoding the new
contract instead of the old "value must be a random UUID" stance.



---------

Co-authored-by: ahmednahima0-beep <ahmednahima0@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sweetmantech added a commit that referenced this pull request Apr 30, 2026
* Migrate stripe subscription checkout sessions (#493)

* chore: update Prettier configuration and add new dependencies

- Added "endOfLine": "lf" to the Prettier configuration for consistent line endings.
- Introduced "stripe" and "uuid" packages in package.json for enhanced functionality.
- Updated pnpm-lock.yaml to reflect the addition of new dependencies.

* refactor(stripe): rename mapToSubscriptionSessionErrorResponse to mapToSubscriptionSessionError

- Updated the function name from `mapToSubscriptionSessionErrorResponse` to `mapToSubscriptionSessionError` for consistency and clarity.
- Adjusted import statements and references in related files to reflect the new function name.

* fix(tests): update ACCOUNT UUID in route.test.ts for consistency

- Changed the ACCOUNT constant in route.test.ts from "123e4567-e89b-12d3-a456-426614174000" to "123e4567-e89b-12d3-a456-426614174001" to ensure consistency in test cases.

* feat(stripe): add STRIPE_SK environment variable and improve error handling

- Introduced the STRIPE_SK environment variable in .env.example to ensure proper configuration for Stripe integration.
- Updated the Stripe client initialization to throw an error if STRIPE_SK is not set, enhancing robustness.
- Modified error handling in createSubscriptionSessionHandler to return a 500 status with a generic error message for internal server errors.
- Adjusted validation logic in validateCreateSubscriptionSessionRequest to remove the optional accountId field, simplifying the request structure.
- Enhanced mapToSubscriptionSessionError to mask upstream error messages for 5xx responses, improving security.
- Updated tests to reflect changes in error handling and validation logic.

* fix(connectors): unify action catalog and execution paths for consistent access

- Merged shared and artist toolkits into the actions catalog to ensure all executable actions are listed, reflecting the full access available to the chat agent.
- Updated the executeConnectorAction function to also utilize the merged toolkit, ensuring that all actions listed in the catalog are executable.
- Extracted ConnectorActionNotFoundError into a separate file for cleaner error handling and improved test organization.
- Rewritten tests to mock the new toolkit structure, ensuring comprehensive coverage of the updated functionality.

* feat(stripe): refactor createStripeSession to use configuration constants

- Updated createStripeSession to utilize STRIPE_SUBSCRIPTION_PRICE_ID and STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS constants for better maintainability and configuration management.
- Enhanced tests for createStripeSession to reflect these changes, ensuring consistency in the subscription parameters.
- Improved error handling in client tests by refining the management of the STRIPE_SK environment variable.

* chore: remove .gitattributes and update Prettier configuration

- Deleted the .gitattributes file to simplify project configuration.
- Updated .prettierrc by removing the "endOfLine" setting for cleaner formatting consistency.

* chore(stripe): drop uuid dep, use accountId as client_reference_id

YAGNI: client_reference_id is a soft-convenience pointer string with no
readers in either api or chat — never round-trips, never queried via the
sessions.list API (it isn't a filterable param). The migration was
generating a throwaway random uuid that mapped to nothing internally.

Setting it to accountId instead:
- removes the uuid dep entirely (only callsite in this repo)
- makes the Stripe dashboard's "Client reference" column actually
  useful for support/debugging
- matches metadata.accountId so reconciliation is consistent

Drop the uuid mock from createStripeSession.test.ts and flip the
not.toBe("acc-1") assertion to toBe("acc-1") — encoding the new
contract instead of the old "value must be a random UUID" stance.

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

---------

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

* feat(api): migrate GET /api/ai/models (#491)

* feat(api): migrate misc-reads endpoints (email, ai/models, youtube/channel-info)

Adds api parity for chat's /api/email, /api/ai/models, /api/youtube/channel-info
ahead of the chat-side cutover.

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

* fix(api): address AI review on misc-reads PR

- updateContact: switch to loops v6 object-arg signature (fixes Vercel build)
- handlers: stop leaking raw exception text in 500 responses
- loopsClient: convert singleton to factory function matching filename
- youtubeErrors: align export name with filename
- getYouTubeChannelHandler: explicit boolean discriminator narrowing for build

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

* fix(api): change /api/email from GET to POST

Email tracking upserts a Loops contact — a write — so it should not be
exposed as GET with email in query params (PII in URLs, cacheable
responses). Switches to POST with `{ email }` JSON body.

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

* revert(api): drop /api/email endpoint

Loops is no longer used; the email tracking endpoint is removed
from this migration entirely.

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

* refactor(api): simplify youtube channel-info flow

- Fold token validation into validateYouTubeChannelInfoRequest so the
  handler is a thin fetch+respond orchestrator
- Rename validateYouTubeChannelQuery → validateYouTubeChannelInfoRequest
  (matches validateArtistRequest / validateChatRequest convention)
- Drop buildYouTubeUtilityError + error code catalog: every token-level
  failure collapses to the same {success:false, tokenStatus:"invalid"}
  response, so the typed codes carried no information out

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

* refactor(api): throw from youtube libs, single catch at validator

- refreshStoredYouTubeToken now throws on every failure (no more
  catch-log-return-null); the deep invalid_grant type guard is gone
  since the general console.error already logs the full error
- validateYouTubeTokens lets errors propagate; null is reserved for
  the legitimate "no tokens row / no refresh_token" cases
- validateYouTubeChannelInfoRequest is the single catch boundary;
  thrown errors and null both collapse to the same tokenStatus:invalid
  response, matching chat parity

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

* refactor(api): drop redundant explicit return types from misc-reads libs

TS inference already produces the same types for these helpers; explicit
annotations were noise. Kept annotations on discriminated-union returns
(validators, route handlers) and anywhere inference would widen.

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

* refactor(api): drop more redundant return types from misc-reads libs

Continues 7f64027 — TS inference produces the same types for these
helpers; explicit annotations were noise. Kept the discriminated-union
annotation on validateYouTubeChannelInfoRequest where it documents an
intentional contract.

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

* refactor(api): slim youtube channel-info response and validator

- drop response fields that no chat consumer reads
- validateYouTubeTokens throws on every unusable case; the request
  validator's catch is the single failure boundary (no more let/null)
- drop now-unnecessary explicit return types

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

* refactor(api): trim youtube tests, jsdocs, type duplication, response

- Drop `success` from /api/youtube/channel-info; conform to the
  `{ status: "success", channels }` envelope used across the rest of
  the api (lib/networking/successResponse.ts, songs, posts, accounts,
  chats, artists). Clients infer "needs re-auth" from
  `channels === null`.
- Keep only validator + handler tests; cover no-token, expired-no-refresh,
  refresh-failure, and db-update-failure cases at the validator boundary
  via it.each on the thrown-error contract.
- Trim jsdocs to information-bearing lines only; drop stale
  "mirrors chat" notes and self-evident @param/@returns lines on lib
  helpers.
- Dedupe YouTubeTokensRow: keep the export in validateYouTubeTokens.ts
  (the contract owner) and import it in refreshStoredYouTubeToken.ts.

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

* fix(api): return proper status codes for youtube channel-info errors

- 401 + {status:"error",message} when stored YouTube tokens can't be
  validated/refreshed (re-auth needed)
- 502 + {status:"error",message} for upstream YouTube API failures
- Drops the inconsistent "200 status:success channels:null" shape

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

* refactor(api): drop YouTubeTokensRow alias and return type

validateYouTubeTokens lets TS infer the return type; the alias is gone
(only refreshStoredYouTubeToken used it, now inlined as Tables<>).

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

* fix(api): bypass @ai-sdk/gateway SDK for model catalog

The SDK's getAvailableModels() rejects valid responses with a Zod error
("expected error.object"), which silenced 194 models behind an empty
array. Match chat's direct fetch to /v1/ai/config — same approach,
same payload, same parity.

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

* fix(api): pin @ai-sdk/gateway to 2.x for catalog reads

The 3.x SDK requires specificationVersion:"v3" via a strict Zod literal,
but the live /v1/ai/config endpoint still emits "v2" descriptors. Pin
to 2.0.83 (latest 2.x, expects "v2") so gateway.getAvailableModels()
works as designed. This is a catalog-read-only consumer; api never uses
gateway() as a model factory, so 2.x's LanguageModelV2 surface doesn't
collide with the rest of the codebase on @ai-sdk/provider@3.

Restore the SDK call site (no more direct fetch hack); a comment
flags when to bump back to 3.x.

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

* refactor(api): drop redundant jsdoc on getAvailableModels

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

* refactor(api): restore original jsdoc on getAvailableModels

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

* feat(api): gate /api/youtube/channel-info behind auth + artist access

Layers in validateAuthContext + checkAccountArtistAccess so anonymous
callers can no longer probe stored YouTube tokens by guessing UUIDs.
Returns 401 on missing/invalid auth, 403 when the authed caller has no
access to the requested artist.

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

* revert(api): drop /api/youtube/channel-info from this migration

YouTube channel-info will migrate via Composio in a separate PR.
Reduces Group 1 scope to just /api/ai/models.

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>

---------

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