Skip to content

[Fix]: Payments App Sundry Fixes#1455

Merged
nams1570 merged 10 commits into
devfrom
payments-ui
May 21, 2026
Merged

[Fix]: Payments App Sundry Fixes#1455
nams1570 merged 10 commits into
devfrom
payments-ui

Conversation

@nams1570
Copy link
Copy Markdown
Collaborator

@nams1570 nams1570 commented May 20, 2026

Summary of Changes

You can now edit items on a product view.
The "Make free" button is less obtuse, and it clearly tells you what it's going to do.
Additionally, we found out while working on this PR that you cannot create a paymentIntent on stripe that is < 0.5$. So, you can't create an OTP for a "free" product. We add safeguards to protect against that.
Also, 0 dollar subscriptions don't create a subscription invoice. Additionally, the old code relied on being able to fetch the stripe client secret, which would be null for a 0 dollar subscription so we create a carve out.

Summary by CodeRabbit

  • New Features

    • Better free-product checkout handling: $0 subscriptions return an empty success response without a payment client secret; non-free subscriptions include client secret when needed.
    • UI: “Make free” flow, “Free · {amount}” with price ID, per-price checkout error indicators/tooltips, and an alert for products with invalid prices.
    • Client- and server-side Stripe one-time minimum checks.
  • Bug Fixes

    • Included-item dialog now resets form state when opened to avoid stale values.
  • Documentation

    • OpenAPI: clarified client_secret may be omitted when no customer confirmation is required.
  • Tests

    • Added end-to-end tests covering $0 purchase-session flows.

Review Change Stack

Copilot AI review requested due to automatic review settings May 20, 2026 20:49
@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 21, 2026 2:14am
stack-auth-mcp Ready Ready Preview, Comment May 21, 2026 2:14am
stack-auth-skills Ready Ready Preview, Comment May 21, 2026 2:14am
stack-backend Ready Ready Preview, Comment May 21, 2026 2:14am
stack-dashboard Ready Ready Preview, Comment May 21, 2026 2:14am
stack-demo Ready Ready Preview, Comment May 21, 2026 2:14am
stack-docs Ready Ready Preview, Comment May 21, 2026 2:14am
stack-preview-backend Ready Ready Preview, Comment May 21, 2026 2:14am
stack-preview-dashboard Ready Ready Preview, Comment May 21, 2026 2:14am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afd461d9-6b59-4f2e-ba9b-3a3d9538d3d8

📥 Commits

Reviewing files that changed from the base of the PR and between f43423a and f0bae8c.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts

📝 Walkthrough

Walkthrough

Adds free ($0) subscription handling: models free prices as monthly recurring, enforces billing-interval and Stripe one-time minimums, returns empty JSON (no client_secret) for free purchases, updates dashboard UI and editor validation, adds shared Stripe limits, updates OpenAPI, and adds e2e tests.

Changes

Free Subscription Feature

Layer / File(s) Summary
OpenAPI documentation updates
docs-mintlify/openapi/admin.json, docs-mintlify/openapi/client.json, docs-mintlify/openapi/server.json
Clarifies client_secret response description to note Stripe Elements usage and that the field may be omitted when no customer confirmation is required.
Shared Stripe one-time limits
packages/stack-shared/src/payments/stripe-limits.ts
Adds STRIPE_ONE_TIME_MIN_AMOUNT_BY_CURRENCY and getStripeOneTimeMinAmount helper for per-currency one-time minimums.
Free price model and utilities
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts
formatPriceDisplay adds "one-time" suffix for non-recurring prices. createFreePrice models free prices as $0 monthly recurring. isFreePrices allows interval presence while excluding serverOnly/freeTrial.
Backend purchase-session route logic
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
Adds upfront price validation (finite non-negative USD), enforces billing interval for $0 products, applies Stripe one-time minimum checks, makes client_secret optional, revokes purchase verification codes and returns {} for free purchases, and extracts/validates subscription client_secret for paid flows.
Dashboard pricing UI and dialog state sync
apps/dashboard/.../pricing-section.tsx, product-price-row.tsx, included-item-dialog.tsx, .../page-client.tsx, .../page-client-list-view.tsx
PricingSection computes isFree internally, shows formatted free price and freePriceId, wraps "Make Free" actions with tooltips; ProductPriceRow surfaces checkout errors; IncludedItemDialog resets form state on open and disables item-selection tooltip while editing; product list view shows invalid-prices alert.
Price editor validation & Save gating
apps/dashboard/.../price-edit-dialog.tsx
Adds validateEditingPriceAmount delegating to getPriceCheckoutError, shows inline error, disables Save and blocks onSave when the amount is invalid.
E2E test coverage for free subscriptions
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
Adds three tests: $0 recurring purchase returns 200 with empty body; $0 one-time purchase returns 400 requiring billing interval; switching from paid to free returns 200 with empty body.

Sequence Diagram

sequenceDiagram
  participant Client
  participant PurchaseSessionRoute
  participant StripeAPI
  participant PurchaseCodeStore
  Client->>PurchaseSessionRoute: POST /payments/purchases/purchase-session (price)
  PurchaseSessionRoute->>PurchaseSessionRoute: Validate price, interval, one-time min
  alt Free price (USD === 0)
    PurchaseSessionRoute->>PurchaseCodeStore: Revoke purchase verification code
    PurchaseSessionRoute->>Client: Return {}
  else Paid price
    PurchaseSessionRoute->>StripeAPI: Create/update subscription/payment
    StripeAPI-->>PurchaseSessionRoute: Respond (may include client_secret)
    PurchaseSessionRoute->>PurchaseCodeStore: Revoke purchase verification code
    PurchaseSessionRoute->>Client: Return { client_secret }
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • hexclave/stack-auth#1378: Also modifies the purchase-session handler; related but focuses on application-fee injection vs $0 handling and client_secret behavior.

Suggested reviewers

  • mantrakp04
  • BilalG1
  • N2D4

Poem

🐰 I hopped through code with cheer,
Modeled free months for all to see,
No secret sent when billing's nil,
Tests confirm the switch is free,
Dashboard hums — prices still. 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '[Fix]: Payments App Sundry Fixes' is vague and uses non-descriptive language ('Sundry Fixes') that fails to convey what the PR actually does, despite multiple substantive changes across payment handling, UI improvements, and validation. Replace with a specific title reflecting the main change, such as '[Fix]: Add Stripe payment validation and handle $0 subscriptions' or narrow to the most critical change.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers the main objectives (edit items, update UI copy, add Stripe minimum safeguards, handle $0 subscriptions) and provides reasonable context, though it could be more structured and detailed about implementation specifics.
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 payments-ui

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.

nams1570 added 4 commits May 20, 2026 13:50
Stripe doesn't produce a PaymentIntent for $0 invoices, so the
purchase-session route 500'd on "No client secret returned from Stripe
for subscription" for every free product. Stripe itself happily activates
$0 subs synchronously (status=active, invoice=paid) — the only thing
broken was our post-call read of latest_invoice.confirmation_secret.
For $0 prices we now skip the client-secret read on both the create and
switch-within-product-line paths and return an empty body, signalling the
client to skip mounting Stripe Elements. $0 one-time prices are rejected
up front with a 400 so they never reach Stripe's amount_too_small.
- response: client_secret is now optional; OpenAPI description tightened
  so we don't leak webhook names or DB internals on the public surface
- new e2e tests for $0 recurring (create), $0 one-time (rejection), and
  paid -> free switch in the same product line
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the payments purchase flow and dashboard UX around “free” products by modeling free tiers as $0 recurring subscriptions (instead of $0 one-time purchases), and by documenting/handling cases where no Stripe client secret is produced.

Changes:

  • Backend: make client_secret optional in purchase-session responses; return {} for $0 subscriptions and reject $0 one-time prices with a clear 400.
  • Dashboard: improve price display (always indicates recurring vs one-time), update “Make free” to create a $0 monthly recurring price, and improve “Make free” UX with tooltips and better free-tier visibility.
  • Tests/docs: add e2e coverage for $0 subscription and $0 one-time rejection; update OpenAPI schemas to reflect optional client_secret.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
docs-mintlify/openapi/server.json Marks client_secret as optional and clarifies when it’s omitted.
docs-mintlify/openapi/client.json Same OpenAPI clarification for client schema.
docs-mintlify/openapi/admin.json Same OpenAPI clarification for admin schema.
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts Adds e2e coverage for $0 subscription flow and $0 one-time rejection.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts Adjusts free-tier creation to be recurring; improves price display text.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx Improves free-tier UI and “Make free” tooltips; surfaces underlying free price row.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/included-item-dialog.tsx Fixes dialog state reset/sync across opens; disables item selection tooltip while editing.
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx Makes client_secret optional; adds $0 safeguards and a $0 subscription carve-out.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR adds robust support for $0 (free) recurring subscriptions and enforces Stripe's per-currency one-time minimum ($0.50 USD) at both the server and dashboard levels. It also fixes the included-item dialog retaining stale values when reopened.

  • Backend route: Validates price amount up-front (finite, non-negative), rejects $0 one-time prices with a clean 400, and returns an empty {} body (no client_secret) for $0 recurring subs, since Stripe auto-activates them without a payment intent.
  • Dashboard UX: isFreePrices is widened to accept $0 prices with an interval; createFreePrice now emits interval: [1, 'month']; a shared getPriceCheckoutError function drives inline warning icons in the price list, the price-edit dialog's save-button gate, and a new ProductsWithInvalidPricesAlert banner on the products list page.
  • OpenAPI/docs: client_secret is now marked optional with an updated description.

Confidence Score: 5/5

Safe to merge; all new paths are well-validated and changes are well-scoped to the payments feature.

The backend validation is symmetric across both the conflict-update and new-subscription branches. Price amount validation fires before any Stripe call, and the $0 recurring fast-path correctly skips client_secret extraction. The shared stripe-limits module prevents frontend/backend drift. The two findings are both non-blocking documentation and UX gaps.

utils.ts has a displaced JSDoc comment worth cleaning up.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx Adds price validation and a $0 recurring fast-path that revokes the code and returns {} without a client_secret. Logic is symmetric across both conflict and new-subscription branches.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts Adds getPriceCheckoutError and updates isFreePrices/createFreePrice to allow intervals on $0 prices. The old isFreePrices JSDoc comment is displaced above getPriceCheckoutError.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx Derives isFree internally, shows inline warning icons for problematic prices, wraps Make Free button with tooltip. The free card branch doesn't surface checkout errors for legacy $0 one-time prices.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/price-edit-dialog.tsx Adds validateEditingPriceAmount which gates the Save button and shows inline errors; delegates to getPriceCheckoutError so dialog and banner stay in sync.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx Adds ProductsWithInvalidPricesAlert that surfaces existing products with Stripe-rejected prices using the shared getPriceCheckoutError validator.
packages/stack-shared/src/payments/stripe-limits.ts New shared module with USD one-time minimum ($0.50); used by both backend and dashboard so the two can't drift apart.
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts Three new e2e tests: $0 recurring sub (no client_secret), $0 one-time rejected with 400, and switching from paid to $0 subscription in the same product line.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as purchase-session API
    participant Stripe
    participant Webhook as invoice.paid webhook

    Client->>API: POST /purchase-session
    API->>API: Validate price (finite, non-negative)
    alt $0 + no interval
        API-->>Client: 400 Free products must have a billing interval
    else "one-time 0 < amount < $0.50"
        API-->>Client: 400 One-time prices must be at least $0.50
    else $0 recurring subscription
        API->>Stripe: "subscriptions.create/update (unit_amount=0)"
        Stripe-->>API: "sub (status=active, no confirmation_secret)"
        API->>API: revokeCode
        API-->>Client: "200 {}"
        Stripe->>Webhook: invoice.paid
    else paid recurring subscription
        API->>Stripe: subscriptions.create/update
        Stripe-->>API: sub + confirmation_secret
        API->>API: revokeCode
        API-->>Client: "200 { client_secret }"
    else "one-time payment >= $0.50"
        API->>Stripe: paymentIntents.create
        Stripe-->>API: client_secret
        API->>API: revokeCode
        API-->>Client: "200 { client_secret }"
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts:167-173
The JSDoc that was written for `isFreePrices` is now orphaned above `getPriceCheckoutError`. When the two functions were reordered, the docblock ended up documenting the wrong function. `isFreePrices` is left without any JSDoc while `getPriceCheckoutError` has two stacked comments — the first of which describes a completely different function.

```suggestion
 * We accept both `'0'` and `'0.00'` for backward-compatibility with rows written
 * before we standardized on `createFreePrice()` (which emits `'0.00'`). All three
 * product pages (list, edit, create) call this so the "Free" indicator and the
 * "Make free" / "Make paid" toggles stay in sync.
 */
export function isFreePrices(prices: PricesObject): boolean {
  const entries = Object.values(prices);
  if (entries.length !== 1) return false;
  const [price] = entries;
  return (price.USD === '0' || price.USD === '0.00')
    && !price.freeTrial
    && !price.serverOnly;
}

/**
 * Returns a human-readable error string if this price would be rejected by
```

### Issue 2 of 2
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx:437
**Free card hides checkout errors for legacy $0 one-time prices**

`isFreePrices` now returns `true` for both new-style `{ USD: '0.00', interval: [1,'month'] }` prices and legacy `{ USD: '0', serverOnly: false }` (no interval) prices, since `!price.interval` was removed from the check. When a legacy price matches, this branch renders the "Free" card — which never calls `getPriceCheckoutError` — so the admin sees no inline warning. The `ProductsWithInvalidPricesAlert` on the list page will still flag it, but on the product edit/create form there is no visual indication that the saved price will be rejected at checkout.

Consider passing `checkoutError` to the free-card branch the same way the price-list rows do, e.g. rendering a `WarningIcon` next to "Free" when `getPriceCheckoutError(freePrice)` is non-null.

Reviews (4): Last reviewed commit: "fix: add warning for users with invalid ..." | Re-trigger Greptile

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

🧹 Nitpick comments (2)
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (2)

1158-1166: 🏗️ Heavy lift

Don't leave the Stripe-backed update branch review-only.

This note makes it explicit that the free-switch case through stripe.subscriptions.update(...) is still untested. Since this PR changed both the create and update free-subscription branches, add a narrower mocked route-level test for the Stripe-backed conflict path so regressions do not depend on flaky webhook e2e coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts`
around lines 1158 - 1166, The Stripe-backed update branch
(stripe.subscriptions.update) is untested by the current e2e note; add a narrow
mocked route-level test in purchase-session.test.ts that simulates the
Stripe-backed conflict path exercised in route.tsx: mock
stripe.subscriptions.update to return an active Stripe-backed subscription (or
throw the same conflict response shape), call the endpoint route that triggers
the update branch, and assert the same patch/response behavior as the DB-only
cancel branch (status and payload expectations). Ensure the test isolates only
the route-level logic (avoid flaky webhook flows), uses the same identifiers as
the existing create-path test, and restores/clears the Stripe mock after the
test.

1048-1098: ⚡ Quick win

Use inline snapshots for these new happy-path responses.

These two tests split assertions across status and body, which loses the file's established NiceResponse snapshot style.

Suggested change
-  expect(response.status).toBe(200);
-  expect(response.body).toEqual({});
+  expect(response).toMatchInlineSnapshot(`
+    NiceResponse {
+      "status": 200,
+      "body": {},
+      "headers": Headers { <some fields may have been hidden> },
+    }
+  `);
-  expect(switchRes.status).toBe(200);
-  expect(switchRes.body).toEqual({});
+  expect(switchRes).toMatchInlineSnapshot(`
+    NiceResponse {
+      "status": 200,
+      "body": {},
+      "headers": Headers { <some fields may have been hidden> },
+    }
+  `);

As per coding guidelines, "**/*.test.{ts,tsx}: When writing tests, prefer .toMatchInlineSnapshot over other selectors in Vitest tests."

Also applies to: 1154-1228

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts`
around lines 1048 - 1098, The test "creates a $0 recurring subscription without
requiring a payment intent" (and the similar test around lines 1154-1228) splits
assertions into expect(response.status).toBe(200) and
expect(response.body).toEqual({}), breaking the file's NiceResponse
inline-snapshot style; change these to a single inline snapshot assertion using
Vitest's toMatchInlineSnapshot on the full response object returned by
niceBackendFetch (e.g., replace the two assertions with
expect(response).toMatchInlineSnapshot(/* update snapshot here */)), and do the
same for the corresponding test at 1154-1228 so both follow the established
NiceResponse snapshot pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx`:
- Around line 82-88: The current check only rejects exactly-zero prices via
isFreePrice; update the validation around selectedPrice (e.g., where isFreePrice
is computed and before calling paymentIntents.create) to also reject one-time
amounts less than $0.50 by converting Number(selectedPrice.USD) and, if
selectedPrice.interval is falsy and amount < 0.5, throw a StatusError(400,
"One-time prices must be at least $0.50") so sub-$0.50 one-time prices are
rejected with a stable 400 before contacting Stripe.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx:
- Around line 154-156: The isFree branch assumes exactly one backing price but
only checks for zero; update the guard to assert the invariant that
Object.entries(prices) has length === 1 and throw (e.g., via throwErr) if not,
then destructure [freePriceId, freePrice] from the single entry; specifically
validate the invariant before using isFree and the prices object (referencing
isFree, prices, and throwErr) so multiple-price cases fail fast instead of
silently using the first entry.

In `@docs-mintlify/openapi/client.json`:
- Around line 6175-6177: The description change in the generated OpenAPI
artifact must not be edited directly; instead update the source schema metadata
that produces that property (the model/schema that defines the Stripe client
secret description in your Yup/OpenAPI generator), commit the change, and then
run the “Regenerate OpenAPI schemas” workflow to produce a new
docs-mintlify/openapi/*.json; ensure you modify the schema file or generator
function that defines the client secret property (the schema field for the
Stripe client secret) so the updated description is applied during regeneration.

---

Nitpick comments:
In `@apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts`:
- Around line 1158-1166: The Stripe-backed update branch
(stripe.subscriptions.update) is untested by the current e2e note; add a narrow
mocked route-level test in purchase-session.test.ts that simulates the
Stripe-backed conflict path exercised in route.tsx: mock
stripe.subscriptions.update to return an active Stripe-backed subscription (or
throw the same conflict response shape), call the endpoint route that triggers
the update branch, and assert the same patch/response behavior as the DB-only
cancel branch (status and payload expectations). Ensure the test isolates only
the route-level logic (avoid flaky webhook flows), uses the same identifiers as
the existing create-path test, and restores/clears the Stripe mock after the
test.
- Around line 1048-1098: The test "creates a $0 recurring subscription without
requiring a payment intent" (and the similar test around lines 1154-1228) splits
assertions into expect(response.status).toBe(200) and
expect(response.body).toEqual({}), breaking the file's NiceResponse
inline-snapshot style; change these to a single inline snapshot assertion using
Vitest's toMatchInlineSnapshot on the full response object returned by
niceBackendFetch (e.g., replace the two assertions with
expect(response).toMatchInlineSnapshot(/* update snapshot here */)), and do the
same for the corresponding test at 1154-1228 so both follow the established
NiceResponse snapshot pattern.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83a9c0d8-177c-4541-9855-c8d56332431b

📥 Commits

Reviewing files that changed from the base of the PR and between 104f347 and 304ba48.

📒 Files selected for processing (8)
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/included-item-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • docs-mintlify/openapi/admin.json
  • docs-mintlify/openapi/client.json
  • docs-mintlify/openapi/server.json

Comment thread docs-mintlify/openapi/client.json
@nams1570
Copy link
Copy Markdown
Collaborator Author

@greptileai please do a full rereview

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx`:
- Around line 95-99: selectedPrice.USD may be NaN or negative which bypasses the
one-time price guard; update the validation around priceAmount (derived from
selectedPrice.USD) to first assert Number.isFinite(priceAmount) and priceAmount
>= 0 and throw a StatusError(400, ...) for invalid values before calling
getStripeOneTimeMinAmount('USD') and comparing against stripeOneTimeMin; adjust
the logic in the same block that currently uses priceAmount, selectedPrice and
getStripeOneTimeMinAmount so invalid or non-finite USD values return a clear 400
route error rather than allowing Stripe errors downstream.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c214cfc4-853b-4cc0-8a7c-8813a757ad82

📥 Commits

Reviewing files that changed from the base of the PR and between 304ba48 and 17e3b61.

📒 Files selected for processing (6)
  • apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/included-item-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/price-edit-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • packages/stack-shared/src/payments/stripe-limits.ts

Comment thread apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx Outdated
@nams1570
Copy link
Copy Markdown
Collaborator Author

@greptileai please rereview

@nams1570
Copy link
Copy Markdown
Collaborator Author

@greptileai please rereview

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.

🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts (1)

172-202: ⚡ Quick win

Move the free-price JSDoc so it documents the right function.

Line 172 introduces getPriceCheckoutError, but the isFreePrices doc block above it is now orphaned. Please move that block directly above isFreePrices to avoid misleading maintenance.

Suggested cleanup
-/**
- * Returns true if `prices` represents a "free" product: exactly one price entry
- * whose USD amount is `'0'` or `'0.00'` and which has no free-trial or
- * server-only flag set (either would change the semantics meaningfully).
- *
- * An interval IS allowed (and expected, post-update) because a free product is
- * modelled as a $0 recurring subscription — see `createFreePrice()` for the
- * rationale.
- *
- * We accept both `'0'` and `'0.00'` for backward-compatibility with rows written
- * before we standardized on `createFreePrice()` (which emits `'0.00'`). All three
- * product pages (list, edit, create) call this so the "Free" indicator and the
- * "Make free" / "Make paid" toggles stay in sync.
- */
 /**
  * Returns a human-readable error string if this price would be rejected by
  * Stripe at checkout time, or `null` if it's valid. Used both at price-edit
@@
 export function getPriceCheckoutError(price: Price): string | null {
@@
 }
 
+/**
+ * Returns true if `prices` represents a "free" product: exactly one price entry
+ * whose USD amount is `'0'` or `'0.00'` and which has no free-trial or
+ * server-only flag set (either would change the semantics meaningfully).
+ *
+ * An interval IS allowed (and expected, post-update) because a free product is
+ * modelled as a $0 recurring subscription — see `createFreePrice()` for the
+ * rationale.
+ *
+ * We accept both `'0'` and `'0.00'` for backward-compatibility with rows written
+ * before we standardized on `createFreePrice()` (which emits `'0.00'`). All three
+ * product pages (list, edit, create) call this so the "Free" indicator and the
+ * "Make free" / "Make paid" toggles stay in sync.
+ */
 export function isFreePrices(prices: PricesObject): boolean {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/utils.ts
around lines 172 - 202, The JSDoc describing free-price behavior is currently
placed above getPriceCheckoutError and should be moved to document isFreePrices
instead; find the orphaned JSDoc (the block that talks about $0/recurring vs
one-time free prices) and relocate it directly above the isFreePrices function
declaration so it documents isFreePrices rather than getPriceCheckoutError,
leaving getPriceCheckoutError with only its own Stripe validation JSDoc.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/products/utils.ts:
- Around line 172-202: The JSDoc describing free-price behavior is currently
placed above getPriceCheckoutError and should be moved to document isFreePrices
instead; find the orphaned JSDoc (the block that talks about $0/recurring vs
one-time free prices) and relocate it directly above the isFreePrices function
declaration so it documents isFreePrices rather than getPriceCheckoutError,
leaving getPriceCheckoutError with only its own Stripe validation JSDoc.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4155b72d-6b37-4d68-812e-ee2487f93c01

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcc1eb and f43423a.

📒 Files selected for processing (5)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/price-edit-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/pricing-section.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/product-price-row.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/utils.ts

@nams1570 nams1570 merged commit 0e85b05 into dev May 21, 2026
35 checks passed
@nams1570 nams1570 deleted the payments-ui branch May 21, 2026 02:33
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.

3 participants