Billing Endpoints Guide
When to Use This Skill
- Debugging billing issues (double charges, missing invoices, wrong subscription items)
- Adding new billing endpoints
- Understanding how Autumn state maps to Stripe
- Fixing subscription update/cancel/attach flows
- Working with subscription schedules (future changes)
Endpoint Quick Reference
| Operation | Handler | Architecture | Notes |
|---|---|---|---|
| Attach product | billing/attach/handleAttach.ts | Legacy | Adds product to customer |
| Checkout | billing/checkout/handleCheckoutV2.ts | Legacy | Creates Stripe checkout session |
| Cancel | customers/cancel/handleCancel.ts | Legacy | Cancels subscription |
| Update subscription | billing/v2/updateSubscription/handleUpdateSubscription.ts | V2 | Quantity/plan changes |
All new billing endpoints MUST use V2 architecture.
V2 Architecture: The 4-Layer Pattern
Every V2 billing endpoint follows this exact pattern. Copy this template:
// From: billing/v2/updateSubscription/handleUpdateSubscription.ts export const handleUpdateSubscription = createRoute({ body: UpdateSubscriptionV0ParamsSchema, handler: async (c) => { const ctx = c.get("ctx"); const body = c.req.valid("json"); // 1. SETUP - Fetch all context needed for billing operation const billingContext = await setupUpdateSubscriptionBillingContext({ ctx, params: body, }); logUpdateSubscriptionContext({ ctx, billingContext }); // 2. COMPUTE - Determine Autumn state changes const autumnBillingPlan = await computeUpdateSubscriptionPlan({ ctx, billingContext, params: body, }); logUpdateSubscriptionPlan({ ctx, plan: autumnBillingPlan, billingContext }); // 3. ERROR HANDLING - Validate before execution await handleUpdateSubscriptionErrors({ ctx, billingContext, autumnBillingPlan, params: body, }); // 4. EVALUATE - Map Autumn changes to Stripe changes (UNIFIED) const stripeBillingPlan = await evaluateStripeBillingPlan({ ctx, billingContext, autumnBillingPlan, }); logStripeBillingPlan({ ctx, stripeBillingPlan, billingContext }); // 5. EXECUTE - Run Stripe actions, then Autumn DB updates const billingResult = await executeBillingPlan({ ctx, billingContext, billingPlan: { autumn: autumnBillingPlan, stripe: stripeBillingPlan, }, }); const response = billingResultToResponse({ billingContext, billingResult }); return c.json(response, 200); }, });
Key principle: evaluateStripeBillingPlan and executeBillingPlan are UNIFIED across all endpoints. Rarely modify them.
See V2 Four-Layer Pattern Deep Dive for detailed explanation.
Two Critical Stripe Mappings
Getting billing right means getting these two mappings right:
1. Subscription Items (Immediate Changes)
When: Updating a subscription right now (add/remove/change items immediately)
Key function: buildStripeSubscriptionItemsUpdate
Flow:
FullCusProduct[]
β filter by subscription ID
β filter by active statuses
β customerProductToStripeItemSpecs()
β diff against current subscription
β Stripe.SubscriptionUpdateParams.Item[]
See Stripe Subscription Items Reference for details.
2. Schedule Phases (Future Changes)
When: Scheduling changes for the future (downgrades at cycle end, scheduled cancellations)
Key function: buildStripePhasesUpdate
Flow:
FullCusProduct[]
β normalize timestamps to seconds
β buildTransitionPoints() (find all start/end times)
β for each period: filter active products
β customerProductsToPhaseItems()
β Stripe.SubscriptionScheduleUpdateParams.Phase[]
Test reference: tests/unit/billing/stripe/subscription-schedules/build-schedule-phases.spec.ts
See Stripe Schedule Phases Reference for details.
Stripe Invoice Decision Tree
Critical: Stripe sometimes forces invoice creation. If you also create a manual invoice, customer gets double-charged.
Does Stripe force-create an invoice?
βββ Creating a new subscription?
β βββ YES β Stripe creates invoice. DO NOT create manual invoice.
β
βββ Removing trial from subscription? (isTrialing && !willBeTrialing)
β βββ YES β Stripe creates invoice. DO NOT create manual invoice.
β
βββ Otherwise
βββ NO β We create manual invoice using buildStripeInvoiceAction()
Key functions:
shouldCreateManualStripeInvoice()- Returns true if WE should create invoicewillStripeSubscriptionUpdateCreateInvoice()- Returns true if STRIPE will create invoice
See Stripe Invoice Rules Reference for full decision tree.
Common Issues & Fixes
| Symptom | Likely Cause | Quick Fix |
|---|---|---|
| Double invoice charge | Created manual invoice when Stripe already did | Check shouldCreateManualStripeInvoice() |
| Subscription items wrong | customerProductToStripeItemSpecs output incorrect | Debug spec generation, check quantity rules |
| Schedule phases wrong | Transition points incorrect | Check buildTransitionPoints, run schedule phases tests |
| Trial not ending | trialContext not set up correctly | Check setupTrialContext |
| Quantities wrong | Metered vs licensed confusion | undefined = metered, 0 = entity placeholder, N = licensed |
See Common Bugs Reference for detailed debugging steps.
Adding a New Billing Endpoint
-
Create setup function:
setup/setupXxxBillingContext.ts- Extend
BillingContextinterface if needed - Fetch customer, products, Stripe state, timestamps
- Extend
-
Create compute function:
compute/computeXxxPlan.ts- Return
AutumnBillingPlanwith insertCustomerProducts, deleteCustomerProduct, lineItems
- Return
-
Create error handler:
errors/handleXxxErrors.ts- Validate before execution
-
Wire up handler:
handleXxx.ts- Use the 4-layer template above
-
DO NOT modify
evaluateStripeBillingPlanorexecuteBillingPlanunless absolutely necessary
See V2 Four-Layer Pattern for detailed guidance.
Invoicing Utilities (Pure Calculations)
The shared/utils/billingUtils/ folder contains pure calculation functions that determine what customers are charged. These are the foundation of all billing operations.
Key utilities:
| Function | Location | Purpose |
|---|---|---|
priceToLineAmount | invoicingUtils/lineItemUtils/ | Calculate charge amount for a price |
tiersToLineAmount | invoicingUtils/lineItemUtils/ | Calculate tiered/usage-based amounts |
applyProration | invoicingUtils/prorationUtils/ | Calculate partial period charges |
buildLineItem | invoicingUtils/lineItemBuilders/ | Core line item builder |
fixedPriceToLineItem | invoicingUtils/lineItemBuilders/ | Build line item for fixed prices |
usagePriceToLineItem | invoicingUtils/lineItemBuilders/ | Build line item for usage prices |
getCycleEnd | cycleUtils/ | Calculate billing cycle end |
getCycleStart | cycleUtils/ | Calculate billing cycle start |
Key concepts:
LineItem.amountis positive for charges, negative for refundscontext.directioncontrols the sign ("charge"vs"refund")- Proration is applied automatically when
billingPeriodis provided - Consumable prices don't prorate (usage is charged as-is)
See Invoicing Utilities Reference for detailed documentation.
Key File Locations
V2 Billing (server/src/internal/billing/v2/)
| Layer | Key Files |
|---|---|
| Setup | setup/setupFullCustomerContext.ts, setup/setupTrialContext.ts, providers/stripe/setup/setupStripeBillingContext.ts |
| Compute | updateSubscription/compute/computeUpdateSubscriptionPlan.ts, compute/computeAutumnUtils/buildAutumnLineItems.ts |
| Evaluate | providers/stripe/actionBuilders/evaluateStripeBillingPlan.ts, providers/stripe/actionBuilders/buildStripeSubscriptionAction.ts |
| Execute | execute/executeBillingPlan.ts, providers/stripe/execute/executeStripeBillingPlan.ts |
Stripe Mapping Utilities
| Purpose | File |
|---|---|
| Customer product β Stripe item specs | providers/stripe/utils/subscriptionItems/customerProductToStripeItemSpecs.ts |
| Build subscription items update | providers/stripe/utils/subscriptionItems/buildStripeSubscriptionItemsUpdate.ts |
| Build schedule phases | providers/stripe/utils/subscriptionSchedules/buildStripePhasesUpdate.ts |
| Build transition points | providers/stripe/utils/subscriptionSchedules/buildTransitionPoints.ts |
| Check if Stripe creates invoice | providers/stripe/utils/invoices/shouldCreateManualStripeInvoice.ts |
Types
| Type | Location | Purpose |
|---|---|---|
BillingContext | billingContext.ts | Customer, products, Stripe state, timestamps |
AutumnBillingPlan | types/autumnBillingPlan.ts | Autumn state changes (inserts, deletes, line items) |
StripeBillingPlan | types/stripeBillingPlan/stripeBillingPlan.ts | Stripe actions (subscription, invoice, schedule) |
Invoicing Utilities (shared/utils/billingUtils/)
| Purpose | File |
|---|---|
| Amount calculations | invoicingUtils/lineItemUtils/priceToLineAmount.ts, tiersToLineAmount.ts |
| Line item builders | invoicingUtils/lineItemBuilders/buildLineItem.ts, fixedPriceToLineItem.ts, usagePriceToLineItem.ts |
| Proration | invoicingUtils/prorationUtils/applyProration.ts |
| Billing cycles | cycleUtils/getCycleEnd.ts, getCycleStart.ts |
Tests
| What | Location |
|---|---|
| Schedule phases | tests/unit/billing/stripe/subscription-schedules/build-schedule-phases.spec.ts |
Reference Files
Load these on-demand for detailed information:
- V2 Four-Layer Pattern - Deep dive on each layer
- Stripe Subscription Items - Immediate changes mapping
- Stripe Schedule Phases - Future changes mapping
- Stripe Invoice Rules - Invoice decision tree
- Invoicing Utilities - Pure calculation functions for charges
- Common Bugs - Debugging guide with solutions