Skip to main content
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

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.

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-schedule
Authorization: Bearer <token>
Content-Type: application/json

{
  "plan": "MONTHLY_10",
  "downPaymentDate": "2025-06-01",
  "paymentMethod": "pm_1Nk..."    // Stripe PaymentMethod ID
}

Stripe Integration

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

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 ledger

Creating a PaymentIntent

POST /v1/invoices/:id/payment-intent
Authorization: Bearer <token>

# Response:
{
  "clientSecret": "pi_3Nk_secret_...",
  "paymentIntentId": "pi_3NkX...",
  "amount": 142500,
  "currency": "usd"
}

Stripe Webhook Events Handled

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

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

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

  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.

Commission Accounting

Every policy transaction posts commission entries to the commission_ledger table.

Commission Types

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

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

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

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

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

At the end of each remittance cycle (typically monthly), the billing module generates a carrier remittance report:
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.

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

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

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

Transfer Codes

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

Claim Transactions

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.

NSF and Return Premium Handling

NSF (Non-Sufficient Funds)

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 2525–35) is added to the outstanding balance.
# 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

Return premium arises from cancellations, endorsements that reduce coverage, and audit adjustments that result in lower payroll/revenue than estimated.
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"
}
:::note Return premium in excess of 30 days after the original payment is typically issued by check per state regulations. The refundMethod field controls this — the compliance engine will override ORIGINAL_PAYMENT with CHECK if the original charge is too old to reverse. :::