Premium Billing
The @openinsure/billing package handles the full billing lifecycle: installment schedule creation, invoice generation, Stripe payment collection, NSF handling, commission accounting, premium financing, and fiduciary trust account management.
Installment Plan Types
Section titled “Installment Plan Types”When a policy is bound, the billing module creates an installment schedule based on the selected plan:
| Plan | Code | Down Payment | Remaining Payments | Service Fee |
|---|---|---|---|---|
| Paid in Full | PIF | 100% | None | None |
| 2-Pay | TWO_PAY | 50% | 1 × 50% at 6 months | None |
| Quarterly | QUARTERLY | 25% | 3 × 25% quarterly | $5/installment |
| Monthly (10-pay) | MONTHLY_10 | 20% (2 months) | 8 × 10% monthly | $8/installment |
| Monthly (12-pay) | MONTHLY_12 | ~8.33% | 11 × 8.33% monthly | $8/installment |
| Premium Finance | PREMIUM_FINANCE | Down per PFA | Per PFA schedule | Per PFA rate |
The schedule is stored in the billing_schedules table. Each installment has a due_date, amount, status (pending, invoiced, paid, overdue, waived), and a stripe_payment_intent_id when collected.
Create Installment Schedule
Section titled “Create Installment Schedule”The schedule is automatically created when POST /v1/submissions/:id/bind succeeds. You can also create or modify it:
POST /v1/policies/:id/billing-scheduleAuthorization: Bearer <token>Content-Type: application/json
{ "plan": "MONTHLY_10", "downPaymentDate": "2025-06-01", "paymentMethod": "pm_1Nk..." // Stripe PaymentMethod ID}Stripe Integration
Section titled “Stripe Integration”OpenInsure uses Stripe PaymentIntents for all premium collection. The integration is in packages/billing/src/stripe/.
Payment Flow
Section titled “Payment Flow”1. Invoice becomes due │ ▼2. POST /v1/invoices/:id/payment-intent → Creates Stripe PaymentIntent with amount, currency, metadata → Stores payment_intent_id on invoice │ ▼3. Frontend confirms PaymentIntent (card UI, ACH, or saved payment method) │ ▼4. Stripe webhook: payment_intent.succeeded → Worker processes event → Invoice marked `paid` → Receipt generated → Commission calculated and posted to ledgerCreating a PaymentIntent
Section titled “Creating a PaymentIntent”POST /v1/invoices/:id/payment-intentAuthorization: Bearer <token>
# Response:{ "clientSecret": "pi_3Nk_secret_...", "paymentIntentId": "pi_3NkX...", "amount": 142500, "currency": "usd"}Stripe Webhook Events Handled
Section titled “Stripe Webhook Events Handled”| Event | Action |
|---|---|
payment_intent.succeeded | Mark invoice paid, post commission, send receipt |
payment_intent.payment_failed | Mark invoice failed, trigger dunning |
charge.dispute.created | Flag policy for review, notify UW |
charge.refunded | Post return premium to ledger |
setup_intent.succeeded | Store verified payment method for recurring billing |
Webhook Endpoint
Section titled “Webhook Endpoint”Stripe webhooks are received at POST /v1/webhooks/stripe. The Worker validates the Stripe-Signature header before processing:
const event = stripe.webhooks.constructEvent( rawBody, request.headers.get('Stripe-Signature'), env.STRIPE_WEBHOOK_SECRET);Premium Financing
Section titled “Premium Financing”For commercial accounts where the full annual premium is large, OpenInsure integrates with Premium Finance Agreements (PFAs). The insured borrows the net premium from a premium finance company (PFC), pays the PFC in installments, and the MGA receives the full premium upfront.
PFA Workflow
Section titled “PFA Workflow”- Producer selects “Premium Finance” as the installment plan.
- The portal generates a PFA quote via the configured PFC integration (e.g., AFCO, First Insurance Funding).
- Insured signs the PFA electronically.
- PFC wires the net premium to the MGA trust account.
- Billing module records
PIFpayment against the policy invoice. - If the insured defaults, the PFC sends a
PREMIUM_FINANCE_CANCELLATIONnotice — the system triggers a 10-day notice of cancellation workflow.
Commission Accounting
Section titled “Commission Accounting”Every policy transaction posts commission entries to the commission_ledger table.
Commission Types
Section titled “Commission Types”| Type | Description | Timing |
|---|---|---|
PRODUCER_COMMISSION | Percentage of gross premium to the producer | Earned pro-rata with premium |
MGA_OVERRIDE | MGA’s retained spread above producer commission | Earned pro-rata |
CARRIER_NET | Net premium remitted to carrier | Earned pro-rata |
CONTINGENT | Profit-sharing based on loss ratio | Calculated at year-end |
MGAFEE | Flat policy fee retained by MGA | 100% earned at inception |
Earned vs. Unearned Premium
Section titled “Earned vs. Unearned Premium”Commission is earned on a pro-rata basis across the policy term. If a policy is cancelled:
- Earned commission = Commission × (days in force / total days)
- Unearned commission must be returned to the carrier
The billing module automatically calculates the return commission amount when a cancellation is processed and creates a corresponding debit entry in the commission ledger.
Commission Dashboard API
Section titled “Commission Dashboard API”GET /v1/analytics/:orgId/commissions?period=2025-Q1
# Response:{ "period": "2025-Q1", "grossWrittenPremium": 1250000, "producerCommissions": 125000, # 10% "mgaOverride": 62500, # 5% "carrierNet": 1062500, # 85% "unearned": 410000, # estimated at period end "earned": 840000}Fiduciary Trust Account Management
Section titled “Fiduciary Trust Account Management”MGAs are fiduciaries for premium they collect on behalf of carriers. OpenInsure maintains a shadow trust ledger that reconciles against the actual bank account.
Trust Account Entries
Section titled “Trust Account Entries”Every premium receipt, commission deduction, carrier remittance, and refund is posted to the trust_ledger table:
CREATE TABLE trust_ledger ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), org_id uuid NOT NULL, entry_type text NOT NULL, -- 'RECEIPT' | 'COMMISSION' | 'REMITTANCE' | 'REFUND' | 'ADJUSTMENT' policy_id uuid, invoice_id uuid, amount numeric(15,2) NOT NULL, -- positive = inflow, negative = outflow balance_after numeric(15,2) NOT NULL, memo text, created_at timestamptz NOT NULL DEFAULT now());Carrier Remittance Reports
Section titled “Carrier Remittance Reports”At the end of each remittance cycle (typically monthly), the billing module generates a carrier remittance report:
POST /v1/remittances/generateAuthorization: Bearer <admin_token>Content-Type: application/json
{ "carrierId": "car_01J8...", "periodStart": "2025-05-01", "periodEnd": "2025-05-31"}The report lists every bound policy, endorsement premium, cancellation return, and commission deduction, with a final net amount to wire to the carrier.
Double-Entry Ledger (TigerBeetle)
Section titled “Double-Entry Ledger (TigerBeetle)”The trust_ledger SQL table shown above is the PlanetScale-side reconciliation record. The authoritative double-entry ledger is TigerBeetle, a purpose-built financial database running on Fly.io. Every premium dollar is accounted for in both systems, but TigerBeetle is the source of truth for balances.
Fiduciary Split Flow
Section titled “Fiduciary Split Flow”When a premium payment is received, calculateFiduciarySplit() breaks the gross amount into 5-6 buckets. The split is posted atomically to TigerBeetle as a set of transfers debiting MGA_FIDUCIARY and crediting the destination accounts:
Payment received ($10,000 gross) -> MGA_FIDUCIARY (debit: $10,000) -> CARRIER_PAYABLE (credit: $7,500) 85% net to carrier -> PRODUCER_PAYABLE (credit: $1,000) 10% broker commission -> MGA_REVENUE (credit: $500) 5% MGA commission -> TAX_AUTHORITY_PAYABLE (credit: $500) 5% premium tax -> (fees, stamping fees as applicable)All amounts are in integer cents — no floating-point arithmetic touches financial data.
Account Types
Section titled “Account Types”Each organization is provisioned with 9 TigerBeetle accounts:
| Code | Account Type | Purpose |
|---|---|---|
| 1 | CARRIER_PAYABLE | Net premium owed to the carrier |
| 2 | MGA_FIDUCIARY | Hub account — all premium receipts land here first |
| 3 | MGA_REVENUE | MGA commission and fee income |
| 4 | PRODUCER_PAYABLE | Commissions owed to producers |
| 5 | TAX_AUTHORITY_PAYABLE | Premium taxes owed to state authorities |
| 6 | LOSS_FUND | Captive loss fund assets |
| 7 | CLAIMS_PAID | Cumulative claim disbursements |
| 8 | RESERVES | Outstanding loss reserves |
| 9 | REINSURER_PAYABLE | Reinsurance premiums owed to reinsurers |
Transfer Codes
Section titled “Transfer Codes”| Code | Name | Description |
|---|---|---|
| 101 | PREMIUM_PAYMENT | Gross premium receipt into the fiduciary account |
| 102 | COMMISSION_SPLIT | Commission payout to producer or MGA |
| 103 | TAX_SPLIT | Premium tax allocation to tax authority |
| 104 | FEE_SPLIT | Fee allocation (stamping, policy, MGA) |
| 201 | SETTLEMENT | Claim settlement disbursement |
Claim Transactions
Section titled “Claim Transactions”recordClaimTransaction supports four transaction types:
- payment — Debits
LOSS_FUNDand creditsCLAIMS_PAIDwhen a claim payment is issued. - reserve_adjustment — Moves funds between
RESERVESandLOSS_FUNDwhen case reserves change. - recovery — Credits
LOSS_FUNDwhen a subrogation or salvage recovery is received. - reinsurance_recovery — Debits
REINSURER_PAYABLEand creditsLOSS_FUNDwhen a stop-loss or treaty recovery is received.
NSF and Return Premium Handling
Section titled “NSF and Return Premium Handling”NSF (Non-Sufficient Funds)
Section titled “NSF (Non-Sufficient Funds)”When a Stripe payment fails:
- The invoice is marked
FAILED. - A dunning sequence begins: reminder at Day 1, warning at Day 5, final notice at Day 9.
- If the invoice remains unpaid at Day 10, a cancellation for non-payment is initiated (state notice requirements apply).
- An NSF fee (configurable, typically $25–$35) is added to the outstanding balance.
# Retry a failed invoicePOST /v1/invoices/:id/retryAuthorization: Bearer <token>
# Waive the NSF feePOST /v1/invoices/:id/waive-feeAuthorization: Bearer <admin_token>Content-Type: application/json{ "reason": "First-time incident, customer has paid balance" }Return Premium
Section titled “Return Premium”Return premium arises from cancellations, endorsements that reduce coverage, and audit adjustments that result in lower payroll/revenue than estimated.
POST /v1/invoices/:id/refundAuthorization: Bearer <token>Content-Type: application/json
{ "amount": 412.50, "reason": "PRO_RATA_CANCELLATION", "refundMethod": "ORIGINAL_PAYMENT" // or "CHECK" or "CREDIT"}