Conversation
Adds a second copyable span below the API key showing: AUTUMN_SECRET_KEY="<key>" Uses the same component pattern with independent copy state.
When a coupon applies to specific products (not apply_to_all), include autumn_product_ids in the Stripe coupon metadata as a JSON array of Autumn product IDs. This maps the Stripe product IDs in applies_to back to their Autumn product counterparts. The product IDs are derived from the product that each price belongs to, deduplicated via Set.
Charlie/multi local hosting
feat: prepaid tiered entities
Bumps and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together. Updates `minimatch` from 3.1.2 to 3.1.5 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) Updates `minimatch` from 9.0.5 to 9.0.9 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect - dependency-name: minimatch dependency-version: 9.0.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
feat: refactor allocated invoice
… into custom-line-items
custom line items
multi attach same addon
…es/sdk/multi-770cfcd984 chore(deps): bump minimatch in /packages/sdk
feat: add autumn_product_ids metadata to Stripe coupons
feat: add env format copy option in Create API Key dialog
billing key
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Too many files changed for review. ( |
There was a problem hiding this comment.
8 issues found across 371 files
Confidence score: 2/5
- There are multiple high-confidence, user-impacting risks in core billing flows (sev 8/10), including
RequestBodySchema.parseinserver/src/internal/admin/handleGetInvoiceLineItems.tspotentially turning bad requests into 500s and a commented block inserver/src/external/stripe/webhookHandlers/handleSubCreated.tsthat can regress Stripe item cleanup behavior. server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.tsappears to request a non-expandable Stripe field (total_discount_amounts), which could cause runtime invoice retrieval failures rather than a minor edge-case issue.- There is also meaningful operational risk outside request handling:
scripts/dx.tscan select partially occupied slots and kill unrelated processes, andscripts/dev.tscan produce invalidNaNport configs from unvalidated--worktreeinput. - Pay close attention to
server/src/internal/admin/handleGetInvoiceLineItems.ts,server/src/external/stripe/webhookHandlers/handleSubCreated.ts,server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.ts,scripts/dx.ts,scripts/dev.ts- these areas combine concrete regression and runtime-failure risk.
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
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="server/src/internal/admin/handleGetInvoiceLineItems.ts">
<violation number="1" location="server/src/internal/admin/handleGetInvoiceLineItems.ts:15">
P1: Manual `RequestBodySchema.parse` inside the route handler causes invalid input to be handled as internal errors (500) instead of request validation errors (400). Use `createRoute` body validation and `c.req.valid("json")`.</violation>
</file>
<file name="server/src/external/stripe/webhookHandlers/handleSubCreated.ts">
<violation number="1" location="server/src/external/stripe/webhookHandlers/handleSubCreated.ts:144">
P1: Commenting out this block disables in-arrear item cleanup in `subscription.created`, causing a functional regression where matching Stripe subscription items are no longer deleted.</violation>
</file>
<file name=".opencode/plans/billing-key-plan.md">
<violation number="1" location=".opencode/plans/billing-key-plan.md:23">
P1: Relying only on application-level `billing_key` uniqueness checks introduces a race condition; concurrent attaches can create duplicate keys and break deterministic targeting. Add a database uniqueness constraint (or equivalent transactional lock) for persisted integrity.</violation>
</file>
<file name=".claude/skills/billing/SKILL.md">
<violation number="1" location=".claude/skills/billing/SKILL.md:99">
P2: The action template uses `billingPlan` before defining it, so the documented example is incorrect and will fail if copied.</violation>
</file>
<file name="server/src/internal/balances/utils/allocatedInvoice/compute/computeAllocatedInvoiceLineItems.ts">
<violation number="1" location="server/src/internal/balances/utils/allocatedInvoice/compute/computeAllocatedInvoiceLineItems.ts:95">
P2: Strict zero comparison on prorated monetary totals is brittle and can fail for near-zero floating results. Use a small epsilon threshold when deciding whether to suppress line items.</violation>
</file>
<file name="scripts/dx.ts">
<violation number="1" location="scripts/dx.ts:22">
P1: Free-slot detection only checks `serverPort`, so auto-selection can choose a slot with other occupied ports and then kill unrelated processes on `vitePort`/`checkoutPort`.</violation>
</file>
<file name="scripts/dev.ts">
<violation number="1" location="scripts/dev.ts:6">
P2: `--worktree` input is not validated, so non-numeric values produce `NaN` ports and invalid dev server/env configuration. Guard the parsed value and fall back to `1` unless it is a positive integer.</violation>
</file>
<file name="server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.ts">
<violation number="1" location="server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.ts:58">
P1: `expand` includes a non-expandable Stripe field (`total_discount_amounts`), which can cause invoice retrieval to fail at runtime.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const { db } = ctx; | ||
|
|
||
| const body = await c.req.json(); | ||
| const { invoice_ids } = RequestBodySchema.parse(body); |
There was a problem hiding this comment.
P1: Manual RequestBodySchema.parse inside the route handler causes invalid input to be handled as internal errors (500) instead of request validation errors (400). Use createRoute body validation and c.req.valid("json").
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/internal/admin/handleGetInvoiceLineItems.ts, line 15:
<comment>Manual `RequestBodySchema.parse` inside the route handler causes invalid input to be handled as internal errors (500) instead of request validation errors (400). Use `createRoute` body validation and `c.req.valid("json")`.</comment>
<file context>
@@ -0,0 +1,26 @@
+ const { db } = ctx;
+
+ const body = await c.req.json();
+ const { invoice_ids } = RequestBodySchema.parse(body);
+
+ const lineItems = await invoiceLineItemRepo.getByInvoiceIds({
</file context>
| } | ||
|
|
||
| await Promise.all(batchUpdate); | ||
| // const cusProds = await CusProductService.getByStripeSubId({ |
There was a problem hiding this comment.
P1: Commenting out this block disables in-arrear item cleanup in subscription.created, causing a functional regression where matching Stripe subscription items are no longer deleted.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/external/stripe/webhookHandlers/handleSubCreated.ts, line 144:
<comment>Commenting out this block disables in-arrear item cleanup in `subscription.created`, causing a functional regression where matching Stripe subscription items are no longer deleted.</comment>
<file context>
@@ -147,66 +141,66 @@ export const handleSubCreated = async ({
- }
-
- await Promise.all(batchUpdate);
+ // const cusProds = await CusProductService.getByStripeSubId({
+ // db,
+ // stripeSubId: subscription.id,
</file context>
| **File:** `shared/models/cusProductModels/cusProductTable.ts` | ||
|
|
||
| - Add `billing_key: text("billing_key")` column to the `customerProducts` table definition | ||
| - No database-level unique constraint (enforce at application level for flexibility) |
There was a problem hiding this comment.
P1: Relying only on application-level billing_key uniqueness checks introduces a race condition; concurrent attaches can create duplicate keys and break deterministic targeting. Add a database uniqueness constraint (or equivalent transactional lock) for persisted integrity.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .opencode/plans/billing-key-plan.md, line 23:
<comment>Relying only on application-level `billing_key` uniqueness checks introduces a race condition; concurrent attaches can create duplicate keys and break deterministic targeting. Add a database uniqueness constraint (or equivalent transactional lock) for persisted integrity.</comment>
<file context>
@@ -0,0 +1,291 @@
+**File:** `shared/models/cusProductModels/cusProductTable.ts`
+
+- Add `billing_key: text("billing_key")` column to the `customerProducts` table definition
+- No database-level unique constraint (enforce at application level for flexibility)
+
+### 1B. Add `billing_key` to CusProduct Zod schema
</file context>
| async function findFreeWorktree(): Promise<number> { | ||
| for (let n = 2; n <= 10; n++) { | ||
| const serverPort = 8080 + (n - 1) * 100; | ||
| if (!(await isPortInUse({ port: serverPort }))) return n; |
There was a problem hiding this comment.
P1: Free-slot detection only checks serverPort, so auto-selection can choose a slot with other occupied ports and then kill unrelated processes on vitePort/checkoutPort.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/dx.ts, line 22:
<comment>Free-slot detection only checks `serverPort`, so auto-selection can choose a slot with other occupied ports and then kill unrelated processes on `vitePort`/`checkoutPort`.</comment>
<file context>
@@ -0,0 +1,66 @@
+async function findFreeWorktree(): Promise<number> {
+ for (let n = 2; n <= 10; n++) {
+ const serverPort = 8080 + (n - 1) * 100;
+ if (!(await isPortInUse({ port: serverPort }))) return n;
+ }
+ console.error("No free worktree slots (2-10). All server ports in use.");
</file context>
| stripeClient: stripeCli, | ||
| invoiceId: event.data.object.id!, | ||
| expand: ["discounts.source.coupon"], | ||
| expand: ["discounts.source.coupon", "total_discount_amounts"], |
There was a problem hiding this comment.
P1: expand includes a non-expandable Stripe field (total_discount_amounts), which can cause invoice retrieval to fail at runtime.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.ts, line 58:
<comment>`expand` includes a non-expandable Stripe field (`total_discount_amounts`), which can cause invoice retrieval to fail at runtime.</comment>
<file context>
@@ -53,7 +55,7 @@ export const setupInvoiceCreatedContext = async ({
stripeClient: stripeCli,
invoiceId: event.data.object.id!,
- expand: ["discounts.source.coupon"],
+ expand: ["discounts.source.coupon", "total_discount_amounts"],
});
</file context>
| const stripeBillingPlan = await evaluateStripeBillingPlan({ ctx, billingContext, autumnBillingPlan }); | ||
|
|
||
| // 4. ERRORS — Validate before execution | ||
| handleAttachV2Errors({ ctx, billingContext, billingPlan, params }); |
There was a problem hiding this comment.
P2: The action template uses billingPlan before defining it, so the documented example is incorrect and will fail if copied.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/skills/billing/SKILL.md, line 99:
<comment>The action template uses `billingPlan` before defining it, so the documented example is incorrect and will fail if copied.</comment>
<file context>
@@ -1,93 +1,132 @@
+ const stripeBillingPlan = await evaluateStripeBillingPlan({ ctx, billingContext, autumnBillingPlan });
+
+ // 4. ERRORS — Validate before execution
+ handleAttachV2Errors({ ctx, billingContext, billingPlan, params });
+
+ if (preview) return { billingContext, billingPlan };
</file context>
| handleAttachV2Errors({ ctx, billingContext, billingPlan, params }); | |
| const billingPlan = { autumn: autumnBillingPlan, stripe: stripeBillingPlan }; | |
| handleAttachV2Errors({ ctx, billingContext, billingPlan, params }); |
| sumValues([ | ||
| previousLIneItem?.amountAfterDiscounts ?? 0, | ||
| newLineItem?.amountAfterDiscounts ?? 0, | ||
| ]) === 0 |
There was a problem hiding this comment.
P2: Strict zero comparison on prorated monetary totals is brittle and can fail for near-zero floating results. Use a small epsilon threshold when deciding whether to suppress line items.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/internal/balances/utils/allocatedInvoice/compute/computeAllocatedInvoiceLineItems.ts, line 95:
<comment>Strict zero comparison on prorated monetary totals is brittle and can fail for near-zero floating results. Use a small epsilon threshold when deciding whether to suppress line items.</comment>
<file context>
@@ -0,0 +1,101 @@
+ sumValues([
+ previousLIneItem?.amountAfterDiscounts ?? 0,
+ newLineItem?.amountAfterDiscounts ?? 0,
+ ]) === 0
+ ) {
+ return [];
</file context>
| const worktreeNum = | ||
| worktreeIdx !== -1 && process.argv[worktreeIdx + 1] | ||
| ? Number.parseInt(process.argv[worktreeIdx + 1], 10) | ||
| : 1; |
There was a problem hiding this comment.
P2: --worktree input is not validated, so non-numeric values produce NaN ports and invalid dev server/env configuration. Guard the parsed value and fall back to 1 unless it is a positive integer.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/dev.ts, line 6:
<comment>`--worktree` input is not validated, so non-numeric values produce `NaN` ports and invalid dev server/env configuration. Guard the parsed value and fall back to `1` unless it is a positive integer.</comment>
<file context>
@@ -2,9 +2,17 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
-const SERVER_PORT = 8080;
-const CHECKOUT_PORT = 3001;
+const worktreeIdx = process.argv.indexOf("--worktree");
+const worktreeNum =
+ worktreeIdx !== -1 && process.argv[worktreeIdx + 1]
+ ? Number.parseInt(process.argv[worktreeIdx + 1], 10)
</file context>
| const worktreeNum = | |
| worktreeIdx !== -1 && process.argv[worktreeIdx + 1] | |
| ? Number.parseInt(process.argv[worktreeIdx + 1], 10) | |
| : 1; | |
| const parsedWorktreeNum = | |
| worktreeIdx !== -1 && process.argv[worktreeIdx + 1] | |
| ? Number.parseInt(process.argv[worktreeIdx + 1], 10) | |
| : 1; | |
| const worktreeNum = | |
| Number.isInteger(parsedWorktreeNum) && parsedWorktreeNum > 0 | |
| ? parsedWorktreeNum | |
| : 1; |
Summary by cubic
Implements end-to-end storage of renewal invoice line items and unifies invoice upsert across Stripe events to improve discounting, refunds, and analytics. Also refactors allocated invoice/balance flows, adds custom line items in attach, and ships DX and package upgrades.
New Features
Refactors
Written for commit a0d26d8. Summary will update on new commits.