1671 lines
34 KiB
Markdown
1671 lines
34 KiB
Markdown
# 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.
|