34 KiB
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:
subscription.status = invoice.status
Better design:
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:
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.
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.
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:
draft
↓
open
↓
payment_pending
↓
paid
Failure flow:
open
↓
payment_pending
↓
open
↓
past_due
↓
uncollectible
Void flow:
draft/open
↓
void
Refund flow:
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
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
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
paidand subscriptionactive. - If payment fails, mark invoice
openorpayment_pendingand subscriptionpayment_pending.
8.2 Renewal Invoice
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:
Generate renewal invoice at current_period_end.
Charge immediately.
Default B2B enterprise rule:
Generate invoice at period start.
Due date = invoice_date + net_terms_days.
8.3 Upgrade Invoice
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:
Downgrade takes effect at next billing cycle.
No immediate refund by default.
Alternative policy:
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:
admin_id
reason
line_items
approval if over threshold
audit event
9. Invoice Line Items
Every invoice must be composed of line items.
subscription_fee
seat_fee
usage_fee
setup_fee
discount
tax
credit
proration
refund_adjustment
manual_adjustment
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
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:
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:
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.
requires_payment_method
requires_confirmation
requires_action
processing
succeeded
failed
canceled
refunded
partially_refunded
Payment flow:
invoice.open
↓
payment_intent.created
↓
payment.processing
↓
payment.succeeded
↓
invoice.paid
Failure flow:
payment.failed
↓
invoice.open
↓
retry scheduled
↓
subscription payment_pending / past_due
12. Payment Methods
Supported payment methods:
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:
due_on_receipt
net_7
net_15
net_30
net_45
net_60
Default:
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
activewhile invoice isopen. - Once due date passes, invoice becomes
past_due. - Subscription state should only degrade after the configured grace policy.
Example:
{
"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:
downgrade credit
service credit
SLA credit
manual goodwill credit
overpayment
refund converted to credit
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()
);
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:
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:
{
"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.
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:
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.
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.
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.
chargeback.created
↓
invoice disputed
↓
subscription suspended
↓
admin/risk review
Rules:
- Chargeback should immediately flag billing account.
- Current subscription should become
suspendedunless 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.
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:
{
"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:
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:
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:
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
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
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
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
POST /billing/invoices
{
"billing_account_id": "ba_123",
"invoice_type": "manual",
"line_items": [
{
"type": "setup_fee",
"description": "Enterprise onboarding",
"quantity": 1,
"unit_amount": 500000
}
]
}
Finalize Invoice
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
POST /billing/invoices/{invoice_id}/pay
{
"payment_method_id": "pm_123"
}
Retry Payment
POST /billing/invoices/{invoice_id}/retry-payment
Void Invoice
POST /billing/invoices/{invoice_id}/void
{
"reason": "Duplicate invoice"
}
Issue Credit Note
POST /billing/invoices/{invoice_id}/credit-notes
{
"amount": 10000,
"reason": "Service credit"
}
Issue Refund
POST /billing/invoices/{invoice_id}/refunds
{
"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:
is_subscription_blocking
Example:
{
"invoice_type": "manual",
"is_subscription_blocking": false
}
32. Blocking vs Non-Blocking Invoices
Blocking invoices can affect subscription access:
subscription_initial
subscription_renewal
trial_conversion
subscription_upgrade
Non-blocking invoices should not automatically affect access:
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:
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:
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:
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:
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:
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.