Skip to content

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.

When a policy is bound, the billing module creates an installment schedule based on the selected plan:

PlanCodeDown PaymentRemaining PaymentsService Fee
Paid in FullPIF100%NoneNone
2-PayTWO_PAY50%1 × 50% at 6 monthsNone
QuarterlyQUARTERLY25%3 × 25% quarterly$5/installment
Monthly (10-pay)MONTHLY_1020% (2 months)8 × 10% monthly$8/installment
Monthly (12-pay)MONTHLY_12~8.33%11 × 8.33% monthly$8/installment
Premium FinancePREMIUM_FINANCEDown per PFAPer PFA schedulePer 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.

The schedule is automatically created when POST /v1/submissions/:id/bind succeeds. You can also create or modify it:

Terminal window
POST /v1/policies/:id/billing-schedule
Authorization: Bearer <token>
Content-Type: application/json
{
"plan": "MONTHLY_10",
"downPaymentDate": "2025-06-01",
"paymentMethod": "pm_1Nk..." // Stripe PaymentMethod ID
}

OpenInsure uses Stripe PaymentIntents for all premium collection. The integration is in packages/billing/src/stripe/.

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 ledger
Terminal window
POST /v1/invoices/:id/payment-intent
Authorization: Bearer <token>
# Response:
{
"clientSecret": "pi_3Nk_secret_...",
"paymentIntentId": "pi_3NkX...",
"amount": 142500,
"currency": "usd"
}
EventAction
payment_intent.succeededMark invoice paid, post commission, send receipt
payment_intent.payment_failedMark invoice failed, trigger dunning
charge.dispute.createdFlag policy for review, notify UW
charge.refundedPost return premium to ledger
setup_intent.succeededStore verified payment method for recurring billing

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
);

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.

  1. Producer selects “Premium Finance” as the installment plan.
  2. The portal generates a PFA quote via the configured PFC integration (e.g., AFCO, First Insurance Funding).
  3. Insured signs the PFA electronically.
  4. PFC wires the net premium to the MGA trust account.
  5. Billing module records PIF payment against the policy invoice.
  6. If the insured defaults, the PFC sends a PREMIUM_FINANCE_CANCELLATION notice — the system triggers a 10-day notice of cancellation workflow.

Every policy transaction posts commission entries to the commission_ledger table.

TypeDescriptionTiming
PRODUCER_COMMISSIONPercentage of gross premium to the producerEarned pro-rata with premium
MGA_OVERRIDEMGA’s retained spread above producer commissionEarned pro-rata
CARRIER_NETNet premium remitted to carrierEarned pro-rata
CONTINGENTProfit-sharing based on loss ratioCalculated at year-end
MGAFEEFlat policy fee retained by MGA100% earned at inception

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.

Terminal window
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
}

MGAs are fiduciaries for premium they collect on behalf of carriers. OpenInsure maintains a shadow trust ledger that reconciles against the actual bank account.

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()
);

At the end of each remittance cycle (typically monthly), the billing module generates a carrier remittance report:

Terminal window
POST /v1/remittances/generate
Authorization: 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.

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.

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.

Each organization is provisioned with 9 TigerBeetle accounts:

CodeAccount TypePurpose
1CARRIER_PAYABLENet premium owed to the carrier
2MGA_FIDUCIARYHub account — all premium receipts land here first
3MGA_REVENUEMGA commission and fee income
4PRODUCER_PAYABLECommissions owed to producers
5TAX_AUTHORITY_PAYABLEPremium taxes owed to state authorities
6LOSS_FUNDCaptive loss fund assets
7CLAIMS_PAIDCumulative claim disbursements
8RESERVESOutstanding loss reserves
9REINSURER_PAYABLEReinsurance premiums owed to reinsurers
CodeNameDescription
101PREMIUM_PAYMENTGross premium receipt into the fiduciary account
102COMMISSION_SPLITCommission payout to producer or MGA
103TAX_SPLITPremium tax allocation to tax authority
104FEE_SPLITFee allocation (stamping, policy, MGA)
201SETTLEMENTClaim settlement disbursement

recordClaimTransaction supports four transaction types:

  • payment — Debits LOSS_FUND and credits CLAIMS_PAID when a claim payment is issued.
  • reserve_adjustment — Moves funds between RESERVES and LOSS_FUND when case reserves change.
  • recovery — Credits LOSS_FUND when a subrogation or salvage recovery is received.
  • reinsurance_recovery — Debits REINSURER_PAYABLE and credits LOSS_FUND when a stop-loss or treaty recovery is received.

When a Stripe payment fails:

  1. The invoice is marked FAILED.
  2. A dunning sequence begins: reminder at Day 1, warning at Day 5, final notice at Day 9.
  3. If the invoice remains unpaid at Day 10, a cancellation for non-payment is initiated (state notice requirements apply).
  4. An NSF fee (configurable, typically $25–$35) is added to the outstanding balance.
Terminal window
# Retry a failed invoice
POST /v1/invoices/:id/retry
Authorization: Bearer <token>
# Waive the NSF fee
POST /v1/invoices/:id/waive-fee
Authorization: Bearer <admin_token>
Content-Type: application/json
{ "reason": "First-time incident, customer has paid balance" }

Return premium arises from cancellations, endorsements that reduce coverage, and audit adjustments that result in lower payroll/revenue than estimated.

Terminal window
POST /v1/invoices/:id/refund
Authorization: Bearer <token>
Content-Type: application/json
{
"amount": 412.50,
"reason": "PRO_RATA_CANCELLATION",
"refundMethod": "ORIGINAL_PAYMENT" // or "CHECK" or "CREDIT"
}