4.1 KiB
4.1 KiB
Invoicing Architecture
Final Structure
server/src/internal/billing/
├── invoicing/ # NEW - orchestration & Stripe ops
│ ├── createInvoice.ts # Create Stripe invoice
│ ├── finalizeInvoice.ts # Finalize invoice
│ ├── collectPayment.ts # Pay/collect invoice
│ ├── addLineItems.ts # Add items to invoice
│ └── scenarioLineItems/ # Scenario-specific item generation
│ ├── newProductLineItems.ts
│ ├── upgradeLineItems.ts
│ ├── downgradeLineItems.ts
│ └── quantityChangeLineItems.ts
shared/utils/billingUtils/
├── cycleUtils/ # ✅ Already exists
├── invoicingUtils/ # Empty - populate with pure calcs
│ ├── lineItemUtils/ # Line item calculations
│ │ ├── calculateLineItemAmount.ts
│ │ ├── tieredPriceUtils.ts
│ │ └── types.ts
│ ├── prorationUtils.ts # Proration math (uses cycleUtils)
│ └── invoiceDisplayUtils.ts # Formatting, descriptions
Layer 1: Pure Calculations (shared/billingUtils/invoicingUtils/)
1.1 lineItemUtils/calculateLineItemAmount.ts
Central amount calculation:
export const calculateLineItemAmount = ({
price,
quantity,
proration,
now,
}: {
price: Price;
quantity?: number;
proration?: { start: number; end: number };
now?: number;
}): number
1.2 lineItemUtils/tieredPriceUtils.ts
Tiered pricing calculation (extracted from getAmountForQuantity):
export const calculateTieredAmount = ({
tiers,
quantity,
billingUnits,
}: {
tiers: UsageTier[];
quantity: number;
billingUnits?: number;
}): number
1.3 prorationUtils.ts
Proration utilities integrated with cycleUtils:
import { getCycleStart, getCycleEnd } from "../cycleUtils";
export const getProrationPeriod = ({
anchor,
interval,
intervalCount,
now,
}: { ... }): { start: number; end: number }
export const applyProration = ({
amount,
periodStart,
periodEnd,
now,
}): number
1.4 invoiceDisplayUtils.ts
Formatting and description utilities:
export const formatLineItemDescription = ({ ... }): string
export const formatLineItemPrice = ({ ... }): string
Layer 2: Orchestration (server/billing/invoicing/)
2.1 createInvoice.ts
export const createInvoice = async ({
stripeCli,
customerId,
currency,
discounts,
memo,
collectionMethod,
}: { ... }): Promise<Stripe.Invoice>
2.2 addLineItems.ts
export const addLineItemsToInvoice = async ({
stripeCli,
invoiceId,
customerId,
lineItems,
}: {
stripeCli: Stripe;
invoiceId: string;
customerId: string;
lineItems: LineItemOutput[];
}): Promise<void>
2.3 collectPayment.ts
Clear replacement for payForInvoice:
export const collectPayment = async ({
stripeCli,
invoiceId,
paymentMethod,
options,
}: {
stripeCli: Stripe;
invoiceId: string;
paymentMethod?: Stripe.PaymentMethod;
options?: {
voidOnFail?: boolean;
errorOnFail?: boolean;
};
}): Promise<{ paid: boolean; invoice: Stripe.Invoice; error?: Error }>
2.4 scenarioLineItems/
Each scenario uses the pure utils from invoicingUtils:
// upgradeLineItems.ts
import { calculateLineItemAmount, applyProration } from "@autumn/shared";
export const getUpgradeLineItems = async ({
ctx,
curCusProduct,
newProduct,
sub,
}: { ... }): Promise<LineItemOutput[]>
Summary
| Location | Purpose | Dependencies |
|---|---|---|
shared/.../invoicingUtils/lineItemUtils/ |
Pure math (amounts, tiers) | None |
shared/.../invoicingUtils/prorationUtils.ts |
Proration math | cycleUtils |
shared/.../invoicingUtils/invoiceDisplayUtils.ts |
Formatting | None |
server/.../billing/invoicing/ |
Stripe ops, orchestration | invoicingUtils, Stripe |
server/.../billing/invoicing/scenarioLineItems/ |
Scenario-specific logic | invoicingUtils, Stripe |