Promo codes in Payments#1663
Conversation
|
@AddarshKS is attempting to deploy a commit to the Stack Auth Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPromo-code persistence, APIs, checkout, Stripe reconciliation, dashboard management, transaction adjustments, documentation, and end-to-end coverage are added. ChangesPromo code system
Sequence Diagram(s)sequenceDiagram
participant PurchasePageClient
participant ValidatePromoCodeRoute
participant PurchaseSessionRoute
participant Stripe
participant StripeWebhookRoute
participant ReturnClient
PurchasePageClient->>ValidatePromoCodeRoute: POST full_code, price_id, quantity, promo_code
ValidatePromoCodeRoute-->>PurchasePageClient: valid quote or error
PurchasePageClient->>PurchaseSessionRoute: POST purchase-session with promo_code
PurchaseSessionRoute->>Stripe: create payment intent or subscription
Stripe-->>StripeWebhookRoute: payment_intent.succeeded / failed / canceled
StripeWebhookRoute->>StripeWebhookRoute: mark or void promo redemption
Stripe-->>ReturnClient: client_secret or setup_intent_client_secret
ReturnClient->>Stripe: confirm payment or setup
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR adds a full promo code system to the payments flow: a new
Confidence Score: 3/5Two logic defects in the promo redemption lifecycle need to be fixed before merging: one can break checkout for zero-cost first-invoice subscriptions, the other causes Stripe coupon amounts to diverge from what was computed locally when an amount-off discount exceeds the product price.
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant UI as Checkout UI
participant API as purchase-session
participant DB as Prisma DB
participant Stripe as Stripe API
participant WH as Webhook Handler
UI->>API: POST /validate-promo-code
API->>DB: quotePromoCodeForPurchase (no lock)
DB-->>API: PromoCodeQuote
API-->>UI: "{ valid, discount, final_amount }"
UI->>API: POST /purchase-session (+ promo_code)
API->>DB: reservePromoCodeRedemption (FOR UPDATE CTE)
DB-->>API: ReservedPromoCodeRedemption
API->>Stripe: coupons.create(amount_off / percent_off)
Stripe-->>API: couponId
API->>Stripe: subscriptions.create(couponId, metadata.promoCodeRedemptionId)
Stripe-->>API: Subscription
par Webhook (async, can race)
Stripe->>WH: subscription.updated (active)
WH->>DB: markPromoCodeRedemptionApplied(redemptionId) → APPLIED
and Purchase session continues
API->>DB: "attachStripeSubscriptionToPromoRedemption (WHERE status=RESERVED)"
Note over API,DB: Race: if webhook ran first, status=APPLIED → updatedCount=0 → throws
end
API-->>UI: "{ client_secret, client_secret_type }"
UI->>Stripe: confirmSetup / confirmPayment
Stripe->>UI: redirect /purchase/return
UI->>UI: retrieveSetupIntent / retrievePaymentIntent → show success
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant UI as Checkout UI
participant API as purchase-session
participant DB as Prisma DB
participant Stripe as Stripe API
participant WH as Webhook Handler
UI->>API: POST /validate-promo-code
API->>DB: quotePromoCodeForPurchase (no lock)
DB-->>API: PromoCodeQuote
API-->>UI: "{ valid, discount, final_amount }"
UI->>API: POST /purchase-session (+ promo_code)
API->>DB: reservePromoCodeRedemption (FOR UPDATE CTE)
DB-->>API: ReservedPromoCodeRedemption
API->>Stripe: coupons.create(amount_off / percent_off)
Stripe-->>API: couponId
API->>Stripe: subscriptions.create(couponId, metadata.promoCodeRedemptionId)
Stripe-->>API: Subscription
par Webhook (async, can race)
Stripe->>WH: subscription.updated (active)
WH->>DB: markPromoCodeRedemptionApplied(redemptionId) → APPLIED
and Purchase session continues
API->>DB: "attachStripeSubscriptionToPromoRedemption (WHERE status=RESERVED)"
Note over API,DB: Race: if webhook ran first, status=APPLIED → updatedCount=0 → throws
end
API-->>UI: "{ client_secret, client_secret_type }"
UI->>Stripe: confirmSetup / confirmPayment
Stripe->>UI: redirect /purchase/return
UI->>UI: retrieveSetupIntent / retrievePaymentIntent → show success
Reviews (3): Last reviewed commit: "Merge branch 'dev' into PromoC_implement..." | Re-trigger Greptile |
There was a problem hiding this comment.
13 issues found and verified against the latest diff
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="docs-mintlify/openapi/client.json">
<violation number="1" location="docs-mintlify/openapi/client.json:4210">
P2: `validate-promo-code` request example is schema-invalid (`{}` despite required fields `full_code`, `price_id`, and `promo_code`).</violation>
</file>
<file name="apps/backend/src/lib/stripe.tsx">
<violation number="1" location="apps/backend/src/lib/stripe.tsx:343">
P1: Promo redemption is marked as applied without explicit payment-status checks in subscription sync path.</violation>
</file>
<file name="apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql">
<violation number="1" location="apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql:20">
P1: Missing CHECK constraints on polymorphic discount fields and numeric bounds in PromoCode</violation>
</file>
<file name="apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx">
<violation number="1" location="apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx:124">
P2: `loadRedemptions` is vulnerable to out-of-order async responses and does not clear stale redemption data on fetch failure. If a user switches between promo codes quickly, a slower earlier request can overwrite a newer one, and failed fetches leave prior redemption rows visible under the wrong promo code ID.</violation>
<violation number="2" location="apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx:146">
P2: Numeric promo fields are converted from raw text input without checking `Number.isFinite()`, which allows `NaN` to propagate into the promo code creation payload (where JSON serializes it as `null`).</violation>
</file>
<file name="apps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/redemptions/route.ts">
<violation number="1" location="apps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/redemptions/route.ts:43">
P1: Pagination is incomplete: the response schema defines `next_cursor` for paginated results, but the route always returns `next_cursor: null` and does not accept a cursor or offset query parameter. Combined with the 200-record limit, this makes it impossible for clients to retrieve redemptions beyond the first page, silently truncating admin-visible data.</violation>
</file>
<file name="apps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/route.ts">
<violation number="1" location="apps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/route.ts:14">
P2: Path parameter `promo_code_id` lacks UUID format validation and may leak malformed IDs to the database layer</violation>
</file>
<file name="apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx:638">
P2: The promo discount query does not enforce deterministic ordering or handle potential duplicate APPLIED redemptions for the same purchase, subscription, or invoice ID. Without a database unique constraint on these columns and without an ORDER BY clause, duplicate rows would cause nondeterministic discount values in the API response.</violation>
</file>
<file name="apps/backend/src/app/api/latest/integrations/stripe/webhooks/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/integrations/stripe/webhooks/route.tsx:277">
P1: `payment_intent.canceled` one-time purchases do not void reserved promo redemptions, leaving them stuck in a reserved state. The handler voids redemptions on `payment_intent.payment_failed` (added in this PR) but has no analogous branch for `payment_intent.canceled`, which is another terminal state for a PaymentIntent and can leave a reserved redemption unrecoverable.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (5)
apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts (2)
183-193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer inline snapshots for these contract-shaped assertions.
These large response payload checks are stable enough that
toMatchInlineSnapshotwould be easier to review and maintain than repeatedtoMatchObjectblocks.As per coding guidelines, "When writing tests, prefer .toMatchInlineSnapshot over other selectors when possible. Check snapshot-serializer.ts to understand how snapshots are formatted".
Also applies to: 326-335, 577-590, 751-764
🤖 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/promo-codes.test.ts` around lines 183 - 193, The contract-style payload assertions in the promo codes tests should use inline snapshots instead of repeated object matchers. Update the response checks in the promo-codes test cases that currently use toMatchObject on createRes.body and the other listed payload assertions to use toMatchInlineSnapshot, keeping the same expected fields and relying on the existing snapshot formatting conventions. Use the surrounding test blocks in promo-codes.test.ts to locate each assertion and convert them consistently.Source: Coding guidelines
167-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEncode route params instead of interpolating raw IDs into URLs.
These paths are repeated with plain template interpolation. A tiny helper around
encodeURIComponent()would match the repo convention and avoid reintroducing the same pattern elsewhere.As per coding guidelines, "Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs, for consistency".Suggested change
+function promoCodePath(promoCodeId: string) { + return `/api/latest/internal/payments/promo-codes/${encodeURIComponent(promoCodeId)}`; +} + async function listPromoRedemptions(promoCodeId: string) { - return await niceBackendFetch(`/api/latest/internal/payments/promo-codes/${promoCodeId}/redemptions`, { + return await niceBackendFetch(`${promoCodePath(promoCodeId)}/redemptions`, { accessType: "admin", }); } @@ - const detailRes = await niceBackendFetch(`/api/latest/internal/payments/promo-codes/${promoCodeId}`, { + const detailRes = await niceBackendFetch(promoCodePath(promoCodeId), {Also applies to: 202-204, 211-218, 226-229, 350-356, 393-396
🤖 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/promo-codes.test.ts` around lines 167 - 170, The URL builders in the promo-codes tests are interpolating raw route params directly, which should be encoded for consistency with repo guidance. Update the helpers and call sites that build these endpoints (for example listPromoRedemptions and the other repeated promo-code/promo-redemption URL constructions) to wrap dynamic IDs with encodeURIComponent, or route them through the existing urlString`` pattern if available. Keep the change localized to the URL construction symbols so the tests keep using the same endpoints with safely encoded params.Source: Coding guidelines
apps/backend/src/lib/payments/promo-codes.ts (1)
742-775: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRoute admin read-only raw queries through the replica path.
These list/get helpers are pure
SELECTs used by admin GET routes. Split transaction-capable helpers if needed, but the read-only route path should use$replica()rather than primary. As per coding guidelines, raw read-only backend queries should use$replica()unless they are inside transactions or contain writes.Also applies to: 859-872
🤖 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/backend/src/lib/payments/promo-codes.ts` around lines 742 - 775, The read-only helpers in promo-codes.ts are using the primary Prisma path for pure SELECTs; update listPromoCodes and getPromoCode so the admin GET path goes through $replica() instead of options.prisma unless they are inside a transaction. If these helpers are shared with transactional code, split or overload them so the replica-backed variant is used for read-only access while preserving the existing transaction-capable behavior.Source: Coding guidelines
apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql (1)
87-100: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAdd an index for invoice-based redemption updates.
markPromoCodeRedemptionAppliedcan locate redemptions bystripeInvoiceId, but this migration only indexes payment-intent, subscription, promo/customer, and reservation-status paths. Add a nullable/partial index such as(tenancyId, stripeInvoiceId)to avoid tenancy-wide scans during invoice webhook/sync handling.🤖 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/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql` around lines 87 - 100, Add the missing invoice lookup index for promo redemptions: the migration currently covers payment intent, subscription, promo/customer, and reservation/status access patterns, but not the `markPromoCodeRedemptionApplied` path that खोजes by `stripeInvoiceId`. Update the migration to add an index on `PromoCodeRedemption` using `tenancyId` and `stripeInvoiceId` (keeping it nullable/partial if needed for records without an invoice), so invoice webhook and sync updates can resolve redemptions without tenancy-wide scans.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx (1)
262-272: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAsync
onClickhandlers float their promises.
onClick={createPromoCode},onClick={loadPromoCodes}, and the row handlers (loadRedemptions,toggleDisabled,deletePromoCode) return promises that are not awaited. Errors are currently caught internally viaResult.fromPromise, so there's no unhandled rejection today, but per the project guideline these should be wrapped (e.g.onClick={() => runAsynchronously(createPromoCode())}) to stay robust if the handlers ever throw outside theResultboundary.Based on coding guidelines: "NEVER void a promise ... Use
runAsynchronouslyorrunAsynchronouslyWithAlertfor asynchronous operations instead".Also applies to: 371-374, 462-464
🤖 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/promo-codes/page-client.tsx around lines 262 - 272, The async click handlers in the promo codes page are returning floating promises instead of being run through the project’s async helpers. Update the relevant `onClick` handlers in `page-client.tsx`—including `createPromoCode`, `loadPromoCodes`, `loadRedemptions`, `toggleDisabled`, and `deletePromoCode`—to wrap their promise-returning calls with `runAsynchronously` or `runAsynchronouslyWithAlert` rather than passing the async function directly. Keep the existing handler logic intact, but ensure every async UI action is invoked through the approved helper pattern.Source: Coding guidelines
🤖 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/internal/payments/promo-codes/`[promo_code_id]/redemptions/route.ts:
- Around line 17-22: The request schema for the redemptions route is allowing an
unvalidated promo_code_id and an optional query that is later dereferenced
directly. Update the route’s validation to use the existing schema boundary
around promo_code_id so it is validated as a UUID before any cast to uuid, and
in the handler that parses limit, explicitly coalesce the query object with
query ?? {} before accessing its fields. Use the route handler and schema
definitions in this file to locate the fix.
In
`@apps/backend/src/app/api/latest/internal/payments/promo-codes/`[promo_code_id]/route.ts:
- Around line 13-15: The route currently accepts any string for promo_code_id in
paramsSchema, which lets malformed path values reach the database cast. Tighten
the boundary validation in paramsSchema in route.ts by making promo_code_id a
UUID-shaped value using the existing yupString validation helpers, so invalid
IDs are rejected with a controlled 4xx before the service call. Keep the fix
localized to the route handler’s parameter schema and preserve the existing
route flow.
In `@apps/backend/src/app/api/latest/internal/payments/promo-codes/route.ts`:
- Around line 17-34: The promo-codes route handler dereferences query even
though the request schema allows it to be omitted. Update the route’s query
handling in the handler to safely coalesce query to a default object before
reading include_deleted and limit, or make the schema return a defined default
via yupObject in this route. Keep the fix localized around the handler and the
promo-codes request schema so listPromoCodes always receives valid values.
In
`@apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx`:
- Around line 136-137: The Stripe amount calculation in the purchase session
flow is using raw floating-point cents, which can leave fractional values for
decimal prices. Update the `purchase-session` route logic around
`originalAmountCents`, `effectiveAmountCents`, and the two `unit_amount` usages
to round all minor-unit amounts to whole cents before passing them to Stripe.
Keep the promo-code path aligned with the same integer-cent handling so every
Stripe amount is a whole number.
In `@apps/backend/src/lib/payments/promo-codes.ts`:
- Around line 687-731: The promo code insert in the promo-codes flow currently
lets the unique tenancyId/codeHash constraint surface as a raw database error on
duplicates. Update the $queryRaw insert used in the promo code creation path to
handle conflicts with ON CONFLICT ... DO NOTHING RETURNING * and then check the
returned rows; if nothing was inserted, raise a controlled StatusError with a
409/400-style duplicate-promo-code response from the same create-promo-code
logic.
- Around line 355-419: The reservation flow in reservePromoCodeRedemption uses a
stale quote from quotePromoCodeForPurchase and only rechecks redemption limits
after acquiring the PromoCode lock, so concurrent admin changes can still allow
disabled, deleted, expired, or out-of-scope promos to be reserved. Update the
locked insert path in reservePromoCodeRedemption to revalidate the same
eligibility conditions that quotePromoCodeForPurchase enforces, or move the lock
before final validation so the PromoCode state is checked while holding the FOR
UPDATE lock.
- Around line 177-189: The quantity handling in calculateOriginalAmountUsdCents
currently coerces invalid values to at least 1 via Math.max(1,
options.quantity), which can hide bad input and skew quotes. Update the function
to explicitly validate options.quantity is a finite positive integer before
calculating cents, and throw a StatusError from calculateOriginalAmountUsdCents
when it is 0, negative, or fractional; then multiply by the validated quantity
directly.
- Around line 431-460: The Stripe attachment helpers do not verify that the
UPDATE actually matched a redemption, so checkout can continue even when no row
was linked. Update `attachStripePaymentIntentToPromoRedemption` and
`attachStripeSubscriptionToPromoRedemption` to check the affected-row count
returned by `prisma.$executeRaw`, and fail fast if it is not exactly one. Keep
the logic localized to these two helpers in `promo-codes.ts`, using the existing
`PrismaClientTransaction`/`$executeRaw` flow and the `PromoCodeRedemption`
status guard.
- Around line 545-548: The Stripe coupon creation in promo-codes.ts is using the
quote-specific discountAmountUsdCents, which can understate a permanent
amount-off coupon. Update the coupon-building logic in the relevant helper to
use amountOffUsdCents when setting amount_off, and keep discountAmountUsdCents
only for the current redemption/quote calculations. Preserve the existing
duration and currency behavior while ensuring the coupon amount reflects the
intended Stripe discount amount.
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx:
- Around line 139-162: The createPromoCode flow in page-client.tsx is allowing
free-text numeric inputs to become NaN, which can silently turn into null on
serialization and reach adminApp.createPromoCode with invalid optional fields.
Update createPromoCode to validate percentOff, amountOffUsd, and maxRedemptions
before building the PromoCodeCreate payload, and block submission with a clear
client-side error when any value is empty or non-numeric. Make sure the guard is
applied around the numeric conversion logic in createPromoCode so invalid input
fails fast instead of sending malformed data.
In `@apps/dashboard/src/app/`(main)/purchase/[code]/page-client.tsx:
- Around line 130-138: The Stripe Elements amount selection in `page-client.tsx`
is treating a promo that reduces the total to exactly zero as if no promo
applied, causing it to fall back to the original full price. Update the
`elementsAmountCents` `useMemo` guard around `promoQuote.final_amount_usd_cents`
so zero is accepted, and align the `isFreeForCheckout` logic used for one-time
purchases so both the displayed “Due now” amount and the charged amount stay
consistent when a quote reaches $0.00.
In `@apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts`:
- Around line 372-375: The time-window setup in the promo code E2E tests is too
close to the boundary and can flake under slow CI or clock skew. Update the
`createPercentPromoCode` calls in the affected promo-code test cases to use a
larger offset buffer for `expiresAtMillis` relative to `Date.now()`, and keep
the same adjustment consistent across the `expiredRes` and other time-window
assertions in `promo-codes.test.ts`.
- Around line 611-623: The promo-code test is too specific about which
validation message is returned when both redemption limits are exceeded. Update
the assertion in the promo code quoting test to accept either the global
redemption limit or the per-customer limit message from quotePromoCode, using
the existing limitedPromo and firstUserSecondCode flow so the test remains
correct regardless of validation order.
---
Nitpick comments:
In `@apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql`:
- Around line 87-100: Add the missing invoice lookup index for promo
redemptions: the migration currently covers payment intent, subscription,
promo/customer, and reservation/status access patterns, but not the
`markPromoCodeRedemptionApplied` path that खोजes by `stripeInvoiceId`. Update
the migration to add an index on `PromoCodeRedemption` using `tenancyId` and
`stripeInvoiceId` (keeping it nullable/partial if needed for records without an
invoice), so invoice webhook and sync updates can resolve redemptions without
tenancy-wide scans.
In `@apps/backend/src/lib/payments/promo-codes.ts`:
- Around line 742-775: The read-only helpers in promo-codes.ts are using the
primary Prisma path for pure SELECTs; update listPromoCodes and getPromoCode so
the admin GET path goes through $replica() instead of options.prisma unless they
are inside a transaction. If these helpers are shared with transactional code,
split or overload them so the replica-backed variant is used for read-only
access while preserving the existing transaction-capable behavior.
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx:
- Around line 262-272: The async click handlers in the promo codes page are
returning floating promises instead of being run through the project’s async
helpers. Update the relevant `onClick` handlers in `page-client.tsx`—including
`createPromoCode`, `loadPromoCodes`, `loadRedemptions`, `toggleDisabled`, and
`deletePromoCode`—to wrap their promise-returning calls with `runAsynchronously`
or `runAsynchronouslyWithAlert` rather than passing the async function directly.
Keep the existing handler logic intact, but ensure every async UI action is
invoked through the approved helper pattern.
In `@apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts`:
- Around line 183-193: The contract-style payload assertions in the promo codes
tests should use inline snapshots instead of repeated object matchers. Update
the response checks in the promo-codes test cases that currently use
toMatchObject on createRes.body and the other listed payload assertions to use
toMatchInlineSnapshot, keeping the same expected fields and relying on the
existing snapshot formatting conventions. Use the surrounding test blocks in
promo-codes.test.ts to locate each assertion and convert them consistently.
- Around line 167-170: The URL builders in the promo-codes tests are
interpolating raw route params directly, which should be encoded for consistency
with repo guidance. Update the helpers and call sites that build these endpoints
(for example listPromoRedemptions and the other repeated
promo-code/promo-redemption URL constructions) to wrap dynamic IDs with
encodeURIComponent, or route them through the existing urlString`` pattern if
available. Keep the change localized to the URL construction symbols so the
tests keep using the same endpoints with safely encoded params.
🪄 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: 50169d91-37da-4a17-bc64-192ba745b07f
📒 Files selected for processing (26)
apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sqlapps/backend/prisma/migrations/20260623000000_add_promo_codes/tests/promo-code-tables.tsapps/backend/prisma/schema.prismaapps/backend/src/app/api/latest/integrations/stripe/webhooks/route.tsxapps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/redemptions/route.tsapps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/route.tsapps/backend/src/app/api/latest/internal/payments/promo-codes/route.tsapps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsxapps/backend/src/app/api/latest/internal/payments/transactions/route.tsxapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/backend/src/app/api/latest/payments/purchases/validate-promo-code/route.tsapps/backend/src/lib/payments/promo-codes.test.tsapps/backend/src/lib/payments/promo-codes.tsapps/backend/src/lib/stripe.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page.tsxapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.tsdocs-mintlify/openapi/admin.jsondocs-mintlify/openapi/client.jsondocs-mintlify/openapi/server.jsonpackages/shared/src/interface/admin-interface.tspackages/shared/src/interface/crud/promo-codes.tspackages/template/src/lib/hexclave-app/apps/implementations/admin-app-impl.tspackages/template/src/lib/hexclave-app/apps/interfaces/admin-app.ts
|
@greptileai Could you please re-review my latest commit? |
There was a problem hiding this comment.
2 issues found across 13 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="apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx:77">
P1: Promo redemption is voided on any post-reservation error, including failures after a successful grant, causing possible entitlement/redemption state divergence.</violation>
</file>
<file name="apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx:182">
P1: `runSubscriptionWithPromoCouponCleanup` voids the reserved promo redemption on all thrown errors, including ambiguous transport failures (e.g. network timeout after Stripe already processed the request). No idempotency keys are used, so a retry cannot safely resolve ambiguity. This can leave Stripe with a subscription referencing a deleted coupon while the local redemption record is voided, causing downstream reconciliation issues.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx (1)
323-340: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winVoid reserved promo redemptions when PaymentIntent creation fails.
The subscription path cleans up reserved redemptions on Stripe create/update failure, but the one-time path does not. If
stripe.paymentIntents.createrejects after Line 132 reserved a promo, the redemption remainsRESERVEDwith no Stripe object to reconcile later.Suggested fix
- const paymentIntent = await stripe.paymentIntents.create({ + const paymentIntentResult = await Result.fromPromise(stripe.paymentIntents.create({ amount: amountCents, currency: "usd", customer: stripeCustomerId, automatic_payment_methods: { enabled: true }, metadata: { productId: data.productId || "", productVersionId, customerId: data.customerId, customerType: data.product.customerType, purchaseQuantity: String(quantity), purchaseKind: "ONE_TIME", tenancyId: data.tenancyId, priceId: price_id, ...(promoRedemption ? promoRedemptionMetadata(promoRedemption) : {}), }, ...(applicationFeeAmount > 0 ? { application_fee_amount: applicationFeeAmount } : {}), - }); + })); + if (paymentIntentResult.status === "error") { + await voidPromoRedemptionAfterSubscriptionFailure(promoRedemption, "stripe_payment_intent_create_failed"); + throw paymentIntentResult.error; + } + const paymentIntent = paymentIntentResult.data;🤖 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/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx` around lines 323 - 340, The one-time purchase flow in purchase-session/route.tsx leaves promo redemptions stuck in RESERVED if stripe.paymentIntents.create fails after a promo was reserved earlier in the handler. Update the payment-intent creation path to catch creation failures and explicitly void/release any reserved promo redemption using the same promo reservation data and helper logic used in the subscription flow, so the redemption cannot remain unreconciled.docs-mintlify/openapi/server.json (1)
5284-5299: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winMark the promo quote fields nullable in the generated OpenAPI schema.
packages/shared/src/interface/crud/promo-codes.tsdefinesdisplay_name,percent_off_bps, andamount_off_usd_centsas nullable, butdocs-mintlify/openapi/server.jsonstill documents them as plainstring/integerin/payments/purchases/validate-promo-code. Generated clients can reject valid responses.Update the schema generator and regenerate the OpenAPI artifact rather than editing the JSON directly.
🤖 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 `@docs-mintlify/openapi/server.json` around lines 5284 - 5299, The promo quote schema in the generated OpenAPI artifact is missing nullable metadata for fields that are nullable in `promo-codes` (`display_name`, `percent_off_bps`, and `amount_off_usd_cents`). Update the schema generation source used for `/payments/purchases/validate-promo-code` so these fields are emitted as nullable, then regenerate `server.json` from the generator instead of hand-editing the OpenAPI file; use the `promo-codes` interface definitions and the OpenAPI generation path as the places to fix.Source: Learnings
apps/dashboard/src/components/payments/checkout.tsx (1)
106-123: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSkip Stripe Elements validation for free promo checkouts.
With promo codes,
isFreecan now mean “no payment step required,” buthandleSubmitstill returns when Stripe Elements is unavailable and callselements.submit()before the free branch. A fully discounted checkout can therefore be blocked by Payment Element validation.Proposed fix
- if (!stripe || !elements) { + if (!isFree && (!stripe || !elements)) { return; } - const { error: submitError } = await elements.submit(); - if (submitError) { - return setMessage(submitError.message ?? "An unexpected error occurred."); + if (!isFree) { + const { error: submitError } = await elements.submit(); + if (submitError) { + return setMessage(submitError.message ?? "An unexpected error occurred."); + } }- disabled={!stripe || !elements || disabled || !chargesEnabled} + disabled={(!isFree && (!stripe || !elements)) || disabled || !chargesEnabled}Also applies to: 169-171
🤖 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/components/payments/checkout.tsx` around lines 106 - 123, Skip Stripe Elements validation for fully free promo checkouts in handleSubmit. Move the isFree branch in checkout.tsx before the early return on !stripe || !elements and before calling elements.submit(), so a no-payment purchase can continue without Payment Element availability or validation; keep setupSubscription and the Stripe redirect logic only for paid flows.
🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx (1)
230-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the project URL and async-handler helpers for the new promo action.
The new promo validation call uses a template-literal URL and wires an async function directly to
onClick. PreferurlString``/encodeURIComponent()and wrap the click handler withrunAsynchronously(...)`.As per coding guidelines, “Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs” and “Use runAsynchronously or runAsynchronouslyWithAlert for asynchronous operations instead.”
Also applies to: 479-484
🤖 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)/purchase/[code]/page-client.tsx around lines 230 - 239, The promo validation action in the purchase flow should use the project URL helper and async-handler wrapper instead of direct template-literal URL building and a raw async click callback. Update the promo request in the purchase page component to build the endpoint with urlString`` or encodeURIComponent(), and wrap the click handler with runAsynchronously(...) rather than wiring an async function directly to onClick. Apply the same pattern to the related promo action code paths referenced in the purchase page component so the new validation flow follows the project conventions.Source: Coding guidelines
🤖 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/internal/payments/test-mode-purchase-session/route.tsx`:
- Around line 77-112: The grant flow in test-mode purchase handling is treating
promo bookkeeping as part of the purchase grant, so a failure in
markPromoCodeRedemptionApplied can incorrectly trigger
voidExpiredOrFailedPromoCodeRedemption after grantProductToCustomer already
succeeded. Move the promo application step out of the same Result.fromPromise as
grantProductToCustomer in the test-mode purchase session route, and only void
the promo when grantProductToCustomer itself fails; keep the apply step
best-effort or handle its failure separately so an already-granted product is
not rolled back.
---
Outside diff comments:
In
`@apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx`:
- Around line 323-340: The one-time purchase flow in purchase-session/route.tsx
leaves promo redemptions stuck in RESERVED if stripe.paymentIntents.create fails
after a promo was reserved earlier in the handler. Update the payment-intent
creation path to catch creation failures and explicitly void/release any
reserved promo redemption using the same promo reservation data and helper logic
used in the subscription flow, so the redemption cannot remain unreconciled.
In `@apps/dashboard/src/components/payments/checkout.tsx`:
- Around line 106-123: Skip Stripe Elements validation for fully free promo
checkouts in handleSubmit. Move the isFree branch in checkout.tsx before the
early return on !stripe || !elements and before calling elements.submit(), so a
no-payment purchase can continue without Payment Element availability or
validation; keep setupSubscription and the Stripe redirect logic only for paid
flows.
In `@docs-mintlify/openapi/server.json`:
- Around line 5284-5299: The promo quote schema in the generated OpenAPI
artifact is missing nullable metadata for fields that are nullable in
`promo-codes` (`display_name`, `percent_off_bps`, and `amount_off_usd_cents`).
Update the schema generation source used for
`/payments/purchases/validate-promo-code` so these fields are emitted as
nullable, then regenerate `server.json` from the generator instead of
hand-editing the OpenAPI file; use the `promo-codes` interface definitions and
the OpenAPI generation path as the places to fix.
---
Nitpick comments:
In `@apps/dashboard/src/app/`(main)/purchase/[code]/page-client.tsx:
- Around line 230-239: The promo validation action in the purchase flow should
use the project URL helper and async-handler wrapper instead of direct
template-literal URL building and a raw async click callback. Update the promo
request in the purchase page component to build the endpoint with urlString`` or
encodeURIComponent(), and wrap the click handler with runAsynchronously(...)
rather than wiring an async function directly to onClick. Apply the same pattern
to the related promo action code paths referenced in the purchase page component
so the new validation flow follows the project conventions.
🪄 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: afe7bf9f-f04a-4f12-af51-5f702b4ac905
📒 Files selected for processing (13)
apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsxapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/backend/src/lib/payments.tsxapps/backend/src/lib/payments/promo-codes.tsapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/app/(main)/purchase/return/page-client.tsxapps/dashboard/src/app/(main)/purchase/return/page.tsxapps/dashboard/src/components/payments/checkout.tsxapps/dashboard/src/components/payments/stripe-elements-provider.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.tsdocs-mintlify/openapi/admin.jsondocs-mintlify/openapi/client.jsondocs-mintlify/openapi/server.json
🚧 Files skipped from review as they are similar to previous changes (4)
- docs-mintlify/openapi/admin.json
- docs-mintlify/openapi/client.json
- apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts
- apps/backend/src/lib/payments/promo-codes.ts
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs-mintlify/openapi/server.json (1)
5278-5325: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winSplit valid and invalid promo-code responses into separate schemas.
Only
validis required here, so generated clients will treat every quote field as optional even when validation succeeds. That weakens the public contract for an endpoint whose main purpose is returning a discount quote; representvalid: trueandvalid: falseas separate shapes (for example withoneOf) in the generator, then regenerate the artifact. Based on learnings, do not make manual edits to generated OpenAPI artifacts indocs-mintlify/openapi/*.json; update the schema generator logic and rerun the “Regenerate OpenAPI schemas” workflow instead.🤖 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 `@docs-mintlify/openapi/server.json` around lines 5278 - 5325, The promo-code response schema is currently modeled as one object with only valid required, which makes all quote fields look optional in generated clients. Update the schema generator logic for the promo-code validation response to emit separate shapes for valid true and valid false, ideally using oneOf or equivalent discriminated schemas, and then regenerate the OpenAPI artifact. Do not edit the generated JSON in docs-mintlify/openapi directly; fix the generator and rerun the Regenerate OpenAPI schemas workflow instead, referencing the promo-code response schema and its validity branch handling.Source: Learnings
🧹 Nitpick comments (1)
apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts (1)
169-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEncode the promo-code ID when building this URL.
The helper interpolates
promoCodeIddirectly into the path. Please switch this toencodeURIComponent()(or `urlString) so the helper follows the repo’s URL-construction rule consistently. As per coding guidelines, "Use urlStringor encodeURIComponent() instead of normal string interpolation for URLs, for consistency."🤖 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/promo-codes.test.ts` around lines 169 - 173, The listPromoRedemptions helper builds a URL path by interpolating promoCodeId directly, which violates the repo’s URL-construction rule. Update the URL construction in listPromoRedemptions to encode the promoCodeId segment using encodeURIComponent() or switch to urlString`` so the generated path is safe and consistent with the rest of the codebase.Source: Coding guidelines
🤖 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/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts`:
- Around line 691-715: The promo-code webhook tests are reading persisted state
before the asynchronous Stripe webhook handler has finished processing. Update
the affected checks around sendStripeWebhook() in the promo-codes tests to wait
for the expected database state instead of asserting immediately after the 200
response. Use the existing helpers and identifiers like listPromoRedemptions(),
sendStripeWebhook(), and the promo redemption status fields to poll until the
redemption becomes the expected post-condition (for example voided/applied),
then assert on the final result.
In `@docs-mintlify/openapi/server.json`:
- Around line 5258-5263: The example for the endpoint’s `price_id` field is
using an invalid shape (`monthly`) that conflicts with the existing
`purchase-session` contract, so update the source schema generator for this
field instead of editing the generated OpenAPI artifact directly. Align the
example in the generator with the real `price_id` format used by
`purchase-session` (the same identifier shape already documented there), then
rerun the “Regenerate OpenAPI schemas” workflow so the generated JSON stays
consistent.
---
Outside diff comments:
In `@docs-mintlify/openapi/server.json`:
- Around line 5278-5325: The promo-code response schema is currently modeled as
one object with only valid required, which makes all quote fields look optional
in generated clients. Update the schema generator logic for the promo-code
validation response to emit separate shapes for valid true and valid false,
ideally using oneOf or equivalent discriminated schemas, and then regenerate the
OpenAPI artifact. Do not edit the generated JSON in docs-mintlify/openapi
directly; fix the generator and rerun the Regenerate OpenAPI schemas workflow
instead, referencing the promo-code response schema and its validity branch
handling.
---
Nitpick comments:
In `@apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts`:
- Around line 169-173: The listPromoRedemptions helper builds a URL path by
interpolating promoCodeId directly, which violates the repo’s URL-construction
rule. Update the URL construction in listPromoRedemptions to encode the
promoCodeId segment using encodeURIComponent() or switch to urlString`` so the
generated path is safe and consistent with the rest of the codebase.
🪄 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: 27f98924-977f-41f4-8678-ae46db3fd6d9
📒 Files selected for processing (17)
apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sqlapps/backend/prisma/migrations/20260623000000_add_promo_codes/tests/promo-code-tables.tsapps/backend/src/app/api/latest/integrations/stripe/webhooks/route.tsxapps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/redemptions/route.tsapps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/route.tsapps/backend/src/app/api/latest/internal/payments/transactions/route.tsxapps/backend/src/app/api/latest/payments/purchases/validate-promo-code/route.tsapps/backend/src/lib/payments/promo-codes.tsapps/backend/src/lib/stripe.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.tsdocs-mintlify/openapi/admin.jsondocs-mintlify/openapi/client.jsondocs-mintlify/openapi/server.jsonpackages/shared/src/interface/admin-interface.tspackages/template/src/lib/hexclave-app/apps/implementations/admin-app-impl.tspackages/template/src/lib/hexclave-app/apps/interfaces/admin-app.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/shared/src/interface/admin-interface.ts
- packages/template/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts
- packages/template/src/lib/hexclave-app/apps/interfaces/admin-app.ts
- apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx
- apps/backend/src/app/api/latest/internal/payments/promo-codes/[promo_code_id]/route.ts
- apps/backend/src/app/api/latest/payments/purchases/validate-promo-code/route.ts
- apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/promo-codes/page-client.tsx
- apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql
There was a problem hiding this comment.
3 issues found across 17 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="apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx:77">
P1: Promo redemption is voided on any post-reservation error, including failures after a successful grant, causing possible entitlement/redemption state divergence.</violation>
</file>
<file name="apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx">
<violation number="1" location="apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx:182">
P1: `runSubscriptionWithPromoCouponCleanup` voids the reserved promo redemption on all thrown errors, including ambiguous transport failures (e.g. network timeout after Stripe already processed the request). No idempotency keys are used, so a retry cannot safely resolve ambiguity. This can leave Stripe with a subscription referencing a deleted coupon while the local redemption record is voided, causing downstream reconciliation issues.</violation>
</file>
<file name="docs-mintlify/openapi/admin.json">
<violation number="1" location="docs-mintlify/openapi/admin.json:5300">
P3: The `price_id` example value `"monthly"` is inconsistent with other endpoint examples in the OpenAPI spec (e.g., `"price_1234567890abcdef"` in the purchase-session endpoint). This may confuse API consumers about the expected format. Consider aligning the example with the existing convention or clarifying that both formats are accepted.</violation>
</file>
<file name="apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql">
<violation number="1" location="apps/backend/prisma/migrations/20260623000000_add_promo_codes/migration.sql:118">
P1: Subscription-level unique index on APPLIED redemptions may block valid per-invoice redemption rows for FOREVER subscription promos</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/backend/src/lib/payments/promo-codes.test.ts (1)
6-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNarrow the Prisma dependency for these attach helpers.
Both helpers only call
$executeRaw, so the test stub shouldn’t needas unknown as PromoRedemptionAttachmentPrisma. A small local{ $executeRaw: ... }type here would let the stub usesatisfiesand avoid bypassing the type system.🤖 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/backend/src/lib/payments/promo-codes.test.ts` around lines 6 - 11, The test stub for the attach helpers is over-typed and uses an unsafe double cast even though only $executeRaw is needed. In promo-codes.test.ts, narrow the local Prisma shape used by prismaWithExecuteRawResult and the related attach helper tests so the stub is typed as a small object with just $executeRaw, then use satisfies instead of as unknown as PromoRedemptionAttachmentPrisma. Keep the change scoped to the helper and the tests that invoke it so the attach helper symbols still type-check without depending on the full Prisma client.Source: Coding guidelines
🤖 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/backend/src/lib/payments/promo-codes.test.ts`:
- Around line 6-11: The test stub for the attach helpers is over-typed and uses
an unsafe double cast even though only $executeRaw is needed. In
promo-codes.test.ts, narrow the local Prisma shape used by
prismaWithExecuteRawResult and the related attach helper tests so the stub is
typed as a small object with just $executeRaw, then use satisfies instead of as
unknown as PromoRedemptionAttachmentPrisma. Keep the change scoped to the helper
and the tests that invoke it so the attach helper symbols still type-check
without depending on the full Prisma client.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c562cb1-809a-4399-bd2d-9bfaaa764635
📒 Files selected for processing (9)
apps/backend/src/app/api/latest/internal/payments/promo-codes/route.tsapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/backend/src/lib/payments/promo-codes.test.tsapps/backend/src/lib/payments/promo-codes.tsapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.tsdocs-mintlify/openapi/admin.jsondocs-mintlify/openapi/client.jsondocs-mintlify/openapi/server.json
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/backend/src/app/api/latest/internal/payments/promo-codes/route.ts
- docs-mintlify/openapi/client.json
- docs-mintlify/openapi/admin.json
- docs-mintlify/openapi/server.json
- apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
- apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx
- apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts
- apps/backend/src/lib/payments/promo-codes.ts
There was a problem hiding this comment.
2 issues found across 9 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
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/dashboard/src/components/payments/checkout.tsx`:
- Around line 117-132: The free checkout submit flow in handleSubmit needs
duplicate-request and rejection handling. Add a pending/loading guard around the
setupSubscription call so rapid clicks cannot create multiple sessions, and
ensure any rejection from setupSubscription is caught and mapped to an explicit
error/message state instead of bubbling as an unhandled async rejection. Update
the submit path and the DesignButton disabled state in checkout.tsx to reflect
this loading state, and keep the existing error handling consistent with the
sessionSecret check.
🪄 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: a4577d62-3677-4f06-a526-2e069d09ed8a
📒 Files selected for processing (9)
apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsxapps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsxapps/backend/src/app/api/latest/payments/purchases/validate-promo-code/route.tsapps/dashboard/src/app/(main)/purchase/[code]/page-client.tsxapps/dashboard/src/components/payments/checkout.tsxapps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.tsdocs-mintlify/openapi/admin.jsondocs-mintlify/openapi/client.jsondocs-mintlify/openapi/server.json
🚧 Files skipped from review as they are similar to previous changes (8)
- docs-mintlify/openapi/server.json
- apps/backend/src/app/api/latest/payments/purchases/validate-promo-code/route.ts
- docs-mintlify/openapi/client.json
- docs-mintlify/openapi/admin.json
- apps/backend/src/app/api/latest/internal/payments/test-mode-purchase-session/route.tsx
- apps/e2e/tests/backend/endpoints/api/v1/payments/promo-codes.test.ts
- apps/backend/src/app/api/latest/payments/purchases/purchase-session/route.tsx
- apps/dashboard/src/app/(main)/purchase/[code]/page-client.tsx
|
@greptileai Could you re-review the entire PR for me? |
Summary by cubic
Add promo codes across checkout and billing. Admins can manage codes, buyers can validate/apply them, and discounts are enforced server‑side and tracked on one‑time charges and subscriptions.
New Features
promo_codewith a validation endpoint; UI shows inline validation. Subscriptions support setup intents via Stripe Elements setup mode; return page handlessetup_intent_client_secret.@hexclave/sharedadds promo code CRUD types and admin methods.Migration
20260623000000_add_promo_codes; no changes required to existing payment flows.Written for commit a754e5e. Summary will update on new commits.
Summary by CodeRabbit