# B2B Billing and Invoice Execution Plan ## 1. Objective Build a billing and invoice system that manages the financial lifecycle behind B2B subscriptions. The system must support: - Subscription invoices - Trial conversion invoices - Renewal invoices - One-time invoices - Usage-based invoices - Manual invoices - Payment collection - Payment retries - Credits - Refunds - Taxes - Discounts - Invoice adjustments - Failed payments - Invoice reconciliation - Revenue auditability - Admin billing operations The billing system must integrate with the subscription policy system but remain logically separate from subscription status. --- ## 2. Core Principle Subscription state and invoice state are related, but they are not the same thing. Bad design: ```text subscription.status = invoice.status ``` Better design: ```text subscription.status = active invoice.status = open payment.status = failed entitlement.access_level = full ``` A customer can have an active subscription and an unpaid invoice during a grace period. A customer can have a canceled subscription and still owe an unpaid invoice. A customer can have a paid invoice and no active subscription if the invoice was for a one-time service. Do not merge these concepts. That is how billing systems become haunted. --- ## 3. Core Billing Entities The billing system should use these primary objects: ```text billing_account subscription invoice invoice_line_item payment_intent payment_attempt payment_method credit_balance credit_note refund tax_record billing_event ``` | Entity | Purpose | |---|---| | `billing_account` | Represents the paying customer or company | | `subscription` | Represents recurring service agreement | | `invoice` | Represents money owed | | `invoice_line_item` | Represents individual charges or credits | | `payment_intent` | Represents attempt to collect invoice payment | | `payment_attempt` | Represents each actual payment try | | `payment_method` | Represents card, ACH, bank transfer, invoice terms, etc. | | `credit_balance` | Tracks customer prepaid or credited amount | | `credit_note` | Reduces or reverses invoice amount | | `refund` | Returns money after payment | | `tax_record` | Stores calculated tax details | | `billing_event` | Immutable audit trail | --- ## 4. Billing Account Model A billing account represents the organization or legal entity responsible for payment. ```sql CREATE TABLE billing_accounts ( id UUID PRIMARY KEY, workspace_id UUID NOT NULL, customer_id UUID NOT NULL, legal_name TEXT NOT NULL, billing_email TEXT NOT NULL, billing_address_line1 TEXT, billing_address_line2 TEXT, billing_city TEXT, billing_state TEXT, billing_postal_code TEXT, billing_country TEXT, tax_id TEXT, tax_exempt BOOLEAN DEFAULT FALSE, default_currency TEXT NOT NULL DEFAULT 'USD', default_payment_method_id UUID, invoice_terms TEXT NOT NULL DEFAULT 'due_on_receipt', net_terms_days INTEGER DEFAULT 0, provider_customer_id TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` Billing account rules: - A workspace should have one primary billing account. - Enterprise customers may have multiple billing accounts if departmental billing is supported. - A billing account owns invoices, payment methods, and credit balance. - Billing contact information must be snapshotted on finalized invoices. - Invoices should preserve historical billing details and must not dynamically read current billing address after creation. --- ## 5. Invoice Lifecycle Invoices should have their own state machine. ```text draft open paid partially_paid payment_pending past_due void uncollectible refunded partially_refunded ``` | Status | Meaning | |---|---| | `draft` | Invoice is being prepared and is not payable yet | | `open` | Invoice is finalized and awaiting payment | | `payment_pending` | Payment has been initiated but not completed | | `partially_paid` | Some amount has been paid, but balance remains | | `paid` | Invoice is fully paid | | `past_due` | Invoice due date has passed | | `void` | Invoice was canceled before payment | | `uncollectible` | Invoice is written off as bad debt | | `refunded` | Fully paid invoice was fully refunded | | `partially_refunded` | Paid invoice was partially refunded | --- ## 6. Invoice State Transitions Happy path: ```text draft ↓ open ↓ payment_pending ↓ paid ``` Failure flow: ```text open ↓ payment_pending ↓ open ↓ past_due ↓ uncollectible ``` Void flow: ```text draft/open ↓ void ``` Refund flow: ```text paid ↓ partially_refunded ↓ refunded ``` | Current Status | Event | New Status | Notes | |---|---|---|---| | `draft` | Invoice finalized | `open` | Invoice becomes payable | | `draft` | Invoice canceled | `void` | No payment expected | | `open` | Payment started | `payment_pending` | Payment is in progress | | `open` | Full payment received | `paid` | Invoice closed | | `open` | Partial payment received | `partially_paid` | Balance remains | | `open` | Due date passed | `past_due` | Trigger dunning | | `payment_pending` | Payment succeeded | `paid` | Invoice closed | | `payment_pending` | Payment failed | `open` | Retry may continue | | `partially_paid` | Remaining balance paid | `paid` | Invoice closed | | `partially_paid` | Due date passed | `past_due` | Balance overdue | | `past_due` | Payment received | `paid` | Restore account if needed | | `past_due` | Written off | `uncollectible` | Bad debt | | `paid` | Partial refund issued | `partially_refunded` | Access review may be needed | | `paid` | Full refund issued | `refunded` | Access review required | | `open` | Admin voids invoice | `void` | Audit required | --- ## 7. Invoice Types ```text subscription_initial subscription_renewal trial_conversion subscription_upgrade subscription_downgrade_credit usage_based one_time manual correction cancellation_fee ``` | Invoice Type | Description | |---|---| | `subscription_initial` | First paid invoice for a new subscription | | `subscription_renewal` | Recurring invoice for next billing period | | `trial_conversion` | Invoice generated when trial converts to paid | | `subscription_upgrade` | Prorated invoice for plan upgrade | | `subscription_downgrade_credit` | Credit issued for downgrade | | `usage_based` | Usage charges for metered products | | `one_time` | Non-recurring charge | | `manual` | Admin-created invoice | | `correction` | Accounting correction | | `cancellation_fee` | Fee after contract cancellation, if applicable | --- ## 8. Invoice Creation Policy ### 8.1 Trial Conversion Invoice ```text trialing subscription ↓ generate trial_conversion invoice ↓ attempt payment ``` Rules: - Generate invoice at trial end. - Include subscription plan line item. - Include tax if applicable. - Apply credits if available. - Apply discounts if valid. - Attempt payment immediately if payment method exists. - If payment succeeds, mark invoice `paid` and subscription `active`. - If payment fails, mark invoice `open` or `payment_pending` and subscription `payment_pending`. ### 8.2 Renewal Invoice ```text active subscription ↓ generate renewal invoice ↓ finalize invoice ↓ attempt payment ``` | Billing Model | Invoice Timing | |---|---| | Credit card / auto-pay | Generate and charge at renewal time | | ACH / bank debit | Generate 3-5 days before due date | | Net terms | Generate at period start, due later | | Enterprise contract | Generate according to contract schedule | Default SaaS rule: ```text Generate renewal invoice at current_period_end. Charge immediately. ``` Default B2B enterprise rule: ```text Generate invoice at period start. Due date = invoice_date + net_terms_days. ``` ### 8.3 Upgrade Invoice ```text active subscription ↓ calculate prorated charge ↓ generate upgrade invoice ↓ attempt payment ``` Recommended policy: - Upgrade takes effect immediately. - Charge prorated amount immediately. - If payment succeeds, apply upgraded entitlements. - If payment fails, keep existing plan active. - Do not remove already-paid access because an upgrade payment failed. ### 8.4 Downgrade Invoice or Credit Recommended policy: ```text Downgrade takes effect at next billing cycle. No immediate refund by default. ``` Alternative policy: ```text Immediate downgrade creates account credit. ``` Preferred default: | Action | Policy | |---|---| | Downgrade | Effective next billing period | | Refund | No automatic refund | | Credit | Optional, admin-controlled | | Entitlement reduction | At period end | ### 8.5 Manual Invoice Admins may create manual invoices for: - Custom services - Enterprise onboarding - Extra seats - Support packages - Contracted fees - Usage corrections - Migration adjustments Manual invoices require: ```text admin_id reason line_items approval if over threshold audit event ``` --- ## 9. Invoice Line Items Every invoice must be composed of line items. ```text subscription_fee seat_fee usage_fee setup_fee discount tax credit proration refund_adjustment manual_adjustment ``` ```sql CREATE TABLE invoice_line_items ( id UUID PRIMARY KEY, invoice_id UUID NOT NULL, subscription_id UUID, plan_id UUID, type TEXT NOT NULL, description TEXT NOT NULL, quantity NUMERIC NOT NULL DEFAULT 1, unit_amount INTEGER NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL, period_start TIMESTAMP, period_end TIMESTAMP, metadata JSONB, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` Rules: - Invoice totals must be derived from line items. - Never manually mutate invoice total without a corresponding line item. - Negative line items are allowed for discounts, credits, and adjustments. - Tax should be represented as a separate line item or tax record. - Historical line item descriptions should not change after invoice finalization. --- ## 10. Invoice Amount Calculation ```text subtotal = sum(charge line items) discount_total = sum(discount line items) credit_total = sum(credit line items) tax_total = calculated tax total = subtotal - discount_total - credit_total + tax_total amount_due = total - amount_paid ``` Constraints: ```text amount_due cannot be less than 0 paid invoice cannot have amount_due > 0 void invoice cannot accept payments draft invoice cannot accept payments ``` If credits exceed invoice total: ```text invoice amount_due = 0 remaining credit stays on billing account invoice may be marked paid ``` --- ## 11. Payment Lifecycle Payments should be tracked separately from invoices. ```text requires_payment_method requires_confirmation requires_action processing succeeded failed canceled refunded partially_refunded ``` Payment flow: ```text invoice.open ↓ payment_intent.created ↓ payment.processing ↓ payment.succeeded ↓ invoice.paid ``` Failure flow: ```text payment.failed ↓ invoice.open ↓ retry scheduled ↓ subscription payment_pending / past_due ``` --- ## 12. Payment Methods Supported payment methods: ```text card ach_debit bank_transfer wire_transfer manual_invoice purchase_order ``` | Customer Type | Payment Method | |---|---| | Self-serve SaaS | Card | | Mid-market | Card or ACH | | Enterprise | Invoice terms / ACH / wire | | High-risk customer | Card or upfront payment only | Rules: - Each billing account may have one default payment method. - Payment methods must be tokenized through the provider. - Do not store raw card or bank details. - Expired cards should trigger notification before renewal. - Failed payment method should not be retried indefinitely. --- ## 13. Net Terms Policy Supported terms: ```text due_on_receipt net_7 net_15 net_30 net_45 net_60 ``` Default: ```text Self-serve: due_on_receipt B2B standard: net_15 or net_30 Enterprise: contract-specific ``` Rules: - Net terms require approval above a revenue threshold. - Net terms customers may remain `active` while invoice is `open`. - Once due date passes, invoice becomes `past_due`. - Subscription state should only degrade after the configured grace policy. Example: ```json { "invoice_date": "2026-06-01T00:00:00Z", "net_terms_days": 30, "due_at": "2026-07-01T00:00:00Z" } ``` --- ## 14. Dunning and Payment Recovery Dunning is triggered by unpaid invoices. | Day | Invoice Status | Subscription Status | Action | |---:|---|---|---| | 0 | `open` | `active` or `payment_pending` | Notify billing owner | | 3 | `open` | `payment_pending` | Retry payment | | 7 | `past_due` | `past_due` | Limit access | | 14 | `past_due` | `suspended` | Suspend account | | 30 | `uncollectible` or `void` | `canceled` | Cancel subscription | Rules: - Dunning is invoice-driven. - Subscription status reacts to unpaid invoice state. - A customer may have multiple unpaid invoices. - The oldest unpaid invoice should control escalation. - Payment of all blocking invoices should restore subscription. - Admins may pause dunning for enterprise customers. --- ## 15. Credits and Credit Balance Credits should reduce future invoice amounts. Credit sources: ```text downgrade credit service credit SLA credit manual goodwill credit overpayment refund converted to credit ``` ```sql CREATE TABLE credit_balances ( id UUID PRIMARY KEY, billing_account_id UUID NOT NULL, currency TEXT NOT NULL, balance_amount INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` ```sql CREATE TABLE credit_ledger_entries ( id UUID PRIMARY KEY, billing_account_id UUID NOT NULL, invoice_id UUID, type TEXT NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL, reason TEXT NOT NULL, created_by TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` Credit rules: - Credits must be ledgered. - Credit balance cannot change without a ledger entry. - Credits should be currency-specific. - Credits apply before payment collection. - Credits should not silently expire unless terms clearly say so. - Enterprise credits may require approval. --- ## 16. Discounts and Coupons Discount types: ```text percentage fixed_amount trial_extension contractual promotional manual ``` Rules: - Discounts must have start and end dates. - Discounts must define scope: invoice, subscription, plan, or account. - Discounts should not apply to taxes unless legally permitted. - Expired discounts must not apply to new invoices. - Manual discounts require admin reason. Example: ```json { "discount_type": "percentage", "percent_off": 20, "applies_to": "subscription", "starts_at": "2026-06-01T00:00:00Z", "ends_at": "2026-12-01T00:00:00Z" } ``` --- ## 17. Tax Policy Tax calculation should happen at invoice finalization. Requirements: - Use billing address for tax jurisdiction. - Use tax exemption status if present. - Store tax calculation result. - Store tax rate and jurisdiction. - Do not recalculate finalized invoice taxes unless issuing correction. - Support reverse charge or VAT if applicable. ```sql CREATE TABLE tax_records ( id UUID PRIMARY KEY, invoice_id UUID NOT NULL, jurisdiction TEXT, tax_rate NUMERIC, tax_amount INTEGER NOT NULL, tax_type TEXT, tax_exempt BOOLEAN DEFAULT FALSE, exemption_reason TEXT, provider_tax_id TEXT, metadata JSONB, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` Recommended: integrate tax calculation through a provider if selling across jurisdictions. Tax logic is not the place to be brave. --- ## 18. Refund Policy Refunds reverse payments, not invoices. Refund types: ```text full partial goodwill duplicate_payment service_issue fraud chargeback ``` Rules: - Refunds require a paid invoice. - Refunds must reference the original payment. - Refunds must create a refund event. - Full refund may require subscription cancellation or entitlement review. - Partial refund may leave subscription active. - Refund does not delete the invoice. - Refund does not erase revenue history. ```sql CREATE TABLE refunds ( id UUID PRIMARY KEY, invoice_id UUID NOT NULL, payment_attempt_id UUID NOT NULL, billing_account_id UUID NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL, reason TEXT NOT NULL, status TEXT NOT NULL, provider_refund_id TEXT, created_by TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` | Refund Event | Invoice Impact | Subscription Impact | |---|---|---| | Partial refund | `partially_refunded` | Usually no change | | Full refund current period | `refunded` | Review access | | Fraud refund | `refunded` | Suspend or cancel | | Duplicate payment refund | Keep `paid` | No change | | SLA credit as refund | `partially_refunded` | No change | --- ## 19. Credit Notes Credit notes reduce the amount owed on an invoice. Use credit notes when: - Invoice was wrong - Customer received a contractual credit - Customer downgraded and receives credit - Tax correction is needed - Admin adjusts invoice before collection Do not use refunds when money was never collected. ```sql CREATE TABLE credit_notes ( id UUID PRIMARY KEY, invoice_id UUID NOT NULL, billing_account_id UUID NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL, reason TEXT NOT NULL, status TEXT NOT NULL, created_by TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` --- ## 20. Chargeback Policy Chargebacks are high-risk and should override normal happy-path billing. ```text chargeback.created ↓ invoice disputed ↓ subscription suspended ↓ admin/risk review ``` Rules: - Chargeback should immediately flag billing account. - Current subscription should become `suspended` unless enterprise exception applies. - Disable automatic reactivation until resolved. - Require admin review to restore. - Track dispute outcome. | Outcome | Action | |---|---| | Won | Restore invoice/payment state | | Lost | Keep invoice unpaid or refunded | | Withdrawn | Restore access after review | | Under review | Keep account restricted | --- ## 21. Reconciliation Policy The billing system must reconcile local records with the payment provider. Daily reconciliation should compare: - Provider customer records - Provider subscriptions - Provider invoices - Provider payments - Provider refunds - Provider disputes | Mismatch | Action | |---|---| | Provider invoice paid, local invoice open | Mark local invoice paid | | Provider payment failed, local payment processing | Mark failed | | Provider subscription canceled, local active | Flag for review | | Local invoice exists, provider missing | Flag critical | | Provider refund exists, local missing | Import refund | Rules: - Do not blindly overwrite local admin overrides. - Create reconciliation events. - Alert on high-risk mismatches. - Store provider snapshot for audit. --- ## 22. Billing Events Every financial action must create an immutable event. ```text billing_account.created billing_account.updated invoice.created invoice.finalized invoice.sent invoice.payment_started invoice.payment_failed invoice.payment_succeeded invoice.partially_paid invoice.paid invoice.past_due invoice.voided invoice.marked_uncollectible payment_intent.created payment_attempt.created payment_attempt.succeeded payment_attempt.failed payment_method.updated credit.created credit.applied credit.expired credit.reversed discount.applied discount.expired tax.calculated tax.exempt_applied refund.created refund.succeeded refund.failed chargeback.created chargeback.won chargeback.lost billing.reconciled billing.reconciliation_failed admin.billing_override_created ``` --- ## 23. Admin Billing Console Admins need operational tools, but every button should leave fingerprints. Capabilities: - View billing account - View invoices - View invoice line items - View payments - View payment attempts - View credit balance - View credit ledger - View refunds - View chargebacks - Retry payment - Send invoice - Void invoice - Mark invoice uncollectible - Issue credit note - Issue refund - Apply discount - Update billing email - Update tax ID - Update billing address - Change net terms - Pause dunning - Resume dunning Audit fields: ```json { "admin_id": "admin_123", "action": "void_invoice", "invoice_id": "inv_123", "reason": "Duplicate invoice generated during migration", "previous_status": "open", "new_status": "void", "created_at": "2026-06-15T00:00:00Z" } ``` Require approval for: - Refunds above threshold - Manual credits above threshold - Changing enterprise net terms - Marking invoice uncollectible - Voiding paid invoices - Removing tax - Applying large discounts --- ## 24. Customer Billing Portal Customers should be able to: - View current plan - View billing account details - View invoices - Download invoices as PDF - Update payment method - Pay open invoices - View payment history - View credit balance - Update billing email - Update tax ID - Update billing address - Cancel subscription if permitted - Request plan change Do not expose internal statuses like: ```text uncollectible dunning_paused provider_mismatch ``` Map them to customer-friendly labels. --- ## 25. Customer-Facing Invoice Labels | Internal Status | Customer Label | |---|---| | `draft` | Preparing | | `open` | Due | | `payment_pending` | Payment processing | | `paid` | Paid | | `partially_paid` | Partially paid | | `past_due` | Past due | | `void` | Canceled | | `uncollectible` | Contact support | | `refunded` | Refunded | | `partially_refunded` | Partially refunded | --- ## 26. Billing Notifications | Event | Recipient | Notification | |---|---|---| | Invoice finalized | Billing owner | Invoice available | | Invoice due soon | Billing owner | Upcoming due date | | Payment succeeded | Billing owner | Receipt | | Payment failed | Billing owner + admins | Payment failed | | Invoice past due | Billing owner + admins | Payment overdue | | Account suspended | Billing owner + admins | Access restricted | | Invoice paid after failure | Billing owner + admins | Payment recovered | | Refund issued | Billing owner | Refund confirmation | | Credit applied | Billing owner | Credit applied | Rules: - Every notification must include invoice number. - Payment failure notifications must include update-payment link. - Past due notifications must include deadline. - Enterprise invoices should include PO number if available. - Receipts should include amount paid and payment method summary. --- ## 27. Invoice Numbering Invoice numbers must be sequential and immutable. Recommended format: ```text INV-2026-000001 ``` Rules: - Invoice number assigned at finalization. - Draft invoices may not need invoice numbers. - Invoice numbers must never be reused. - Voided invoices keep their invoice numbers. - Region-specific sequencing may be needed for tax compliance. Fields: ```sql invoice_number TEXT UNIQUE invoice_sequence INTEGER UNIQUE ``` Do not generate invoice numbers using random UUIDs. Accounting teams will hate you, and this time they will be right. --- ## 28. Data Model Summary ### Invoices Table ```sql CREATE TABLE invoices ( id UUID PRIMARY KEY, billing_account_id UUID NOT NULL, subscription_id UUID, workspace_id UUID NOT NULL, invoice_number TEXT UNIQUE, invoice_type TEXT NOT NULL, status TEXT NOT NULL, currency TEXT NOT NULL, subtotal_amount INTEGER NOT NULL DEFAULT 0, discount_amount INTEGER NOT NULL DEFAULT 0, credit_amount INTEGER NOT NULL DEFAULT 0, tax_amount INTEGER NOT NULL DEFAULT 0, total_amount INTEGER NOT NULL DEFAULT 0, amount_paid INTEGER NOT NULL DEFAULT 0, amount_due INTEGER NOT NULL DEFAULT 0, invoice_date TIMESTAMP, due_at TIMESTAMP, finalized_at TIMESTAMP, paid_at TIMESTAMP, voided_at TIMESTAMP, marked_uncollectible_at TIMESTAMP, billing_name TEXT, billing_email TEXT, billing_address JSONB, provider_invoice_id TEXT, metadata JSONB, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` ### Payment Attempts Table ```sql CREATE TABLE payment_attempts ( id UUID PRIMARY KEY, invoice_id UUID NOT NULL, billing_account_id UUID NOT NULL, provider_payment_id TEXT, payment_method_id UUID, status TEXT NOT NULL, amount INTEGER NOT NULL, currency TEXT NOT NULL, failure_code TEXT, failure_message TEXT, attempted_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` ### Billing Events Table ```sql CREATE TABLE billing_events ( id UUID PRIMARY KEY, billing_account_id UUID, invoice_id UUID, subscription_id UUID, workspace_id UUID, event_type TEXT NOT NULL, source TEXT NOT NULL, payload JSONB NOT NULL, occurred_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); ``` --- ## 29. API Requirements ### Create Invoice ```http POST /billing/invoices ``` ```json { "billing_account_id": "ba_123", "invoice_type": "manual", "line_items": [ { "type": "setup_fee", "description": "Enterprise onboarding", "quantity": 1, "unit_amount": 500000 } ] } ``` ### Finalize Invoice ```http POST /billing/invoices/{invoice_id}/finalize ``` Behavior: - Assign invoice number. - Calculate totals. - Calculate tax. - Apply credits. - Set status to `open`. - Send invoice if configured. ### Pay Invoice ```http POST /billing/invoices/{invoice_id}/pay ``` ```json { "payment_method_id": "pm_123" } ``` ### Retry Payment ```http POST /billing/invoices/{invoice_id}/retry-payment ``` ### Void Invoice ```http POST /billing/invoices/{invoice_id}/void ``` ```json { "reason": "Duplicate invoice" } ``` ### Issue Credit Note ```http POST /billing/invoices/{invoice_id}/credit-notes ``` ```json { "amount": 10000, "reason": "Service credit" } ``` ### Issue Refund ```http POST /billing/invoices/{invoice_id}/refunds ``` ```json { "amount": 10000, "reason": "Customer requested partial refund" } ``` --- ## 30. Scheduled Jobs | Job | Frequency | Purpose | |---|---:|---| | Invoice generation job | Hourly/Daily | Generate renewal invoices | | Invoice finalization job | Hourly | Finalize pending invoices | | Payment collection job | Hourly | Charge open invoices | | Payment retry job | Hourly | Retry failed payments | | Invoice due reminder job | Daily | Notify customers before due date | | Past due job | Hourly | Mark overdue invoices as past due | | Dunning escalation job | Daily | Escalate unpaid invoices | | Credit application job | On invoice finalization | Apply available credits | | Reconciliation job | Daily | Match provider and local state | | Tax sync job | Daily/On demand | Validate tax records | | Invoice PDF generation job | On finalization | Generate downloadable invoices | --- ## 31. Subscription Integration Rules Billing should drive subscription changes only through defined events. | Billing Event | Subscription Reaction | |---|---| | `invoice.paid` for trial conversion | `trialing → active` | | `invoice.payment_failed` for renewal | `active → payment_pending` | | `invoice.past_due` | `payment_pending → past_due` | | `invoice.marked_uncollectible` | `suspended → canceled` or admin review | | `refund.succeeded` full current period | Entitlement review | | `chargeback.created` | `active → suspended` | Important: not every invoice should affect subscription status. A manual invoice for onboarding should not cancel the subscription if unpaid unless policy says it is blocking. Add this field: ```text is_subscription_blocking ``` Example: ```json { "invoice_type": "manual", "is_subscription_blocking": false } ``` --- ## 32. Blocking vs Non-Blocking Invoices Blocking invoices can affect subscription access: ```text subscription_initial subscription_renewal trial_conversion subscription_upgrade ``` Non-blocking invoices should not automatically affect access: ```text manual setup_fee professional_services correction one_time ``` | Invoice Type | Blocks Subscription? | |---|---| | `subscription_initial` | Yes | | `trial_conversion` | Yes | | `subscription_renewal` | Yes | | `subscription_upgrade` | Usually no | | `manual` | Configurable | | `one_time` | No by default | | `usage_based` | Configurable | | `setup_fee` | No by default | | `cancellation_fee` | No, subscription already ending | --- ## 33. Accounting Rules ### Immutability Once finalized: - Invoice number cannot change. - Invoice date cannot change. - Line items cannot be edited directly. - Totals cannot be edited directly. - Billing address snapshot cannot be edited directly. Corrections require: ```text credit note adjustment invoice refund void if unpaid ``` ### Revenue Integrity - Paid invoices remain in history. - Refunded invoices remain in history. - Voided invoices remain in history. - Deleted invoices should not exist in production. - Admin changes must be audited. --- ## 34. Testing Plan ### Unit Tests Test: - Invoice total calculation - Tax calculation hooks - Credit application - Discount application - Invoice state transitions - Payment state transitions - Refund rules - Credit note rules - Net terms due date logic - Blocking invoice logic ### Integration Tests Test: - Create invoice - Finalize invoice - Pay invoice - Failed payment - Retry payment - Apply credit - Issue refund - Void invoice - Reconcile provider payment - Handle duplicate webhook - Handle out-of-order webhook ### End-to-End Tests Test: ```text Trial ends → invoice created → payment succeeds → subscription active Trial ends → invoice created → payment fails → subscription payment_pending Renewal invoice fails → dunning starts → subscription past_due Past due invoice paid → subscription restored Enterprise invoice created with net_30 → due date passes → past_due Upgrade invoice fails → existing subscription remains active Full refund current period → entitlement review triggered Chargeback created → subscription suspended ``` --- ## 35. Monitoring and Alerts Track: ```text invoice_created_count invoice_finalized_count invoice_paid_count invoice_failed_count invoice_past_due_count invoice_voided_count invoice_uncollectible_count payment_attempt_count payment_success_rate payment_failure_rate refund_count credit_note_count reconciliation_mismatch_count average_days_to_payment accounts_receivable_total past_due_amount_total ``` Alert on: ```text Payment failure spike Invoices stuck in payment_pending Invoices overdue beyond policy Provider/local invoice mismatch Provider/local payment mismatch Refund spike Credit abuse Chargeback created Invoice finalization failure Tax calculation failure ``` --- ## 36. Rollout Plan ### Phase 1: Invoice Foundation Deliver: - Billing account model - Invoice model - Invoice line items - Invoice statuses - Invoice total calculation - Basic invoice admin view Acceptance criteria: - Admin can create draft invoice. - Admin can finalize invoice. - Finalized invoice gets immutable number. - Invoice totals are derived from line items. ### Phase 2: Payment Collection Deliver: - Payment method support - Payment intent model - Payment attempt model - Payment success handling - Payment failure handling Acceptance criteria: - Open invoice can be paid. - Failed payment creates payment attempt. - Successful payment marks invoice paid. - Payment event is auditable. ### Phase 3: Subscription Billing Integration Deliver: - Trial conversion invoices - Renewal invoices - Initial subscription invoices - Blocking invoice logic - Subscription reaction events Acceptance criteria: - Trial conversion payment activates subscription. - Renewal failure moves subscription to payment_pending. - Invoice payment restores subscription when applicable. - Non-blocking invoices do not affect subscription. ### Phase 4: Dunning and Net Terms Deliver: - Due dates - Net terms - Past due logic - Payment retries - Dunning notifications - Subscription escalation rules Acceptance criteria: - Net terms invoices become past due after due date. - Failed payments retry on schedule. - Blocking unpaid invoices escalate subscription status. - Admin can pause dunning. ### Phase 5: Credits, Refunds, Adjustments Deliver: - Credit balance ledger - Credit note support - Refund support - Discount support - Adjustment invoice support Acceptance criteria: - Credits apply to future invoices. - Credit balance is ledgered. - Refunds reference original payments. - Finalized invoices are corrected through credit notes or adjustments. ### Phase 6: Tax and Compliance Deliver: - Tax calculation integration - Tax records - Tax exemption handling - Invoice PDF generation - Invoice address snapshots Acceptance criteria: - Tax calculated at finalization. - Tax result is stored. - Finalized invoice preserves billing details. - Customer can download invoice PDF. ### Phase 7: Reconciliation and Hardening Deliver: - Provider reconciliation - Webhook replay - Dead-letter queue - Mismatch alerts - Admin correction workflows Acceptance criteria: - Provider and local states stay aligned. - Failed webhooks are replayable. - Mismatches create alerts. - Reconciliation does not overwrite valid admin decisions. --- ## 37. Final Default Billing Policy Use this as the baseline B2B billing policy: ```text Invoices are generated from subscriptions, usage, or admin action. Invoices start as draft and become payable only after finalization. Invoice numbers are assigned only at finalization. Finalized invoices are immutable. Subscription invoices are blocking by default. Manual invoices are non-blocking by default. Credits are applied before payment collection. Tax is calculated during finalization. Payment failures create payment attempts and dunning events. Payment retries follow configured schedule. Open invoices become past_due after due date. Blocking past_due invoices affect subscription status. Refunds reverse payments, not invoices. Credit notes reduce invoice balances before or after finalization depending on accounting rules. All billing changes are event-driven and audited. Daily reconciliation compares local billing state with payment provider state. ``` --- ## 38. Definition of Done The billing and invoice system is complete when: - Billing accounts exist and own invoices. - Invoices have a clear lifecycle. - Invoice totals are line-item driven. - Finalized invoices are immutable. - Invoice numbers are sequential and permanent. - Payment attempts are tracked. - Failed payments trigger dunning. - Paid invoices update subscription status when appropriate. - Non-blocking invoices do not affect access. - Credits and refunds are ledgered. - Admin billing actions are audited. - Customers can view and pay invoices. - Provider reconciliation is operational. - Tests cover invoice, payment, refund, and subscription integration flows.