# 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.
