Skip to content

dev#865

Merged
johnyeocx merged 51 commits intomainfrom
dev
Mar 3, 2026
Merged

dev#865
johnyeocx merged 51 commits intomainfrom
dev

Conversation

@johnyeocx
Copy link
Collaborator

@johnyeocx johnyeocx commented Mar 3, 2026

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

    • Store renewal invoice line items (ENG-940): generate in-advance + arrear items, persist on invoice.created/finalized, and add an admin API to fetch line items.
    • Centralized upsertAutumnInvoice used by invoice.created, invoice.finalized, and invoice.paid.
    • Allocated invoice flow for usage-based balances with proration and replaceables; integrated into deduction updates.
    • Custom line items in attach with strict validation and line item override support.
    • Sync subscription item metadata after Checkout; expand discount data in line item fetches; new invoice.finalized handler with Vercel marketplace submission.
    • Stripe coupons include autumn_product_ids in metadata.
    • New @autumn/ksuid package; helper ops for Stripe invoice/subscription items.
  • Refactors

    • UpdateSubscription auto-resolves target product; plan_id optional; subscription_id passthrough + duplicate checks in attach/multiAttach.
    • Compute/finalize line items tightened (supports overrides; quantity updates billed in-advance).
    • DX: multi-worktree ports and dx script; keepInternalFields header to bypass response filtering; Autumn CLI supports it.
    • Test authoring docs/rules overhauled; legacy opencode plans removed.
    • Fixed type errors after latest merge.

Written for commit a0d26d8. Summary will update on new commits.

atmn and others added 30 commits February 19, 2026 12:22
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.
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>
@johnyeocx johnyeocx requested a review from ay-rod as a code owner March 3, 2026 10:09
@vercel
Copy link

vercel bot commented Mar 3, 2026

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

Project Deployment Actions Updated (UTC)
autumn-vite Building Building Mar 3, 2026 10:17am
autumn-vite (dev) Ready Ready Preview, Comment Mar 3, 2026 10:17am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 3, 2026

Too many files changed for review. (371 files found, 100 file limit)

@johnyeocx johnyeocx merged commit 5b3f922 into main Mar 3, 2026
6 of 10 checks passed
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

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.parse in server/src/internal/admin/handleGetInvoiceLineItems.ts potentially turning bad requests into 500s and a commented block in server/src/external/stripe/webhookHandlers/handleSubCreated.ts that can regress Stripe item cleanup behavior.
  • server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.ts appears 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.ts can select partially occupied slots and kill unrelated processes, and scripts/dev.ts can produce invalid NaN port configs from unvalidated --worktree input.
  • 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);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

}

await Promise.all(batchUpdate);
// const cusProds = await CusProductService.getByStripeSubId({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

**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)
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

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;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

stripeClient: stripeCli,
invoiceId: event.data.object.id!,
expand: ["discounts.source.coupon"],
expand: ["discounts.source.coupon", "total_discount_amounts"],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

const stripeBillingPlan = await evaluateStripeBillingPlan({ ctx, billingContext, autumnBillingPlan });

// 4. ERRORS — Validate before execution
handleAttachV2Errors({ ctx, billingContext, billingPlan, params });
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Suggested change
handleAttachV2Errors({ ctx, billingContext, billingPlan, params });
const billingPlan = { autumn: autumnBillingPlan, stripe: stripeBillingPlan };
handleAttachV2Errors({ ctx, billingContext, billingPlan, params });
Fix with Cubic

sumValues([
previousLIneItem?.amountAfterDiscounts ?? 0,
newLineItem?.amountAfterDiscounts ?? 0,
]) === 0
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with Cubic

Comment on lines +6 to +9
const worktreeNum =
worktreeIdx !== -1 && process.argv[worktreeIdx + 1]
? Number.parseInt(process.argv[worktreeIdx + 1], 10)
: 1;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 3, 2026

Choose a reason for hiding this comment

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

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>
Suggested change
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;
Fix with Cubic

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