BillingOne Integration

PolicyOne publishes policy lifecycle events to BillingOne over the shared events:policy Redis Stream. BillingOne subscribes and reacts — creating billing accounts, activating payment schedules, and managing renewals without any direct API calls between the two systems.

Architecture →  |  Event Flow →


The Big Picture

Rendering diagram…

What Triggers an Event?

EventFired ByBillingOne Action
policy.boundbindQuoteToPolicy() in DB transactionCreate billing account, apply payment plan
policy.issuedissuePolicyRecord() in DB transactionActivate payment schedule, send first invoice
policy.cancelled(planned)Stop future charges, calculate refund
policy.renewed(planned)Create new billing period

Key Design Decisions

Outbox Pattern — Why?

The policy.bound event is written to policy.event_outbox inside the same PostgreSQL transaction as the policy bind. This means:

  • If the bind transaction rolls back → no event is emitted. ✅
  • If the server crashes after commit but before publishing to Redis → the OutboxPublisher picks it up on next boot. ✅
  • No dual-write problem. ✅
Rendering diagram…

No Direct API Calls

BillingOne never calls PolicyOne's API to confirm payment eligibility or fetch billing details. All data needed to create the billing account travels inside the event payload — billing.paymentPlanCode, billing.billingMethod, quote.premium.totalPremium. This keeps the two systems fully decoupled.

Agent Pre-Selects the Plan

Before bind, the agent navigates to the Payment Plan wizard step and selects:

  • A payment plan (e.g. quarterly, monthly-12, pif) from BillingOne's catalog
  • A billing method (direct_bill, agency_bill, mortgage_bill)
  • Optional discount codes

These are saved to policy.quote_billing and attached to the policy.bound event at bind time.


Component Index

ComponentLocationRole
PaymentPlanPanelapp/(shell)/submissions/new/PaymentPlanPanel.tsxAgent UI for plan selection
BillingOne clientlib/integration/billingone/client.tsHTTP client: getPaymentPlans, previewPaymentSchedule
Billing event schemalib/integration/queue/billingone/policy-bound.schema.jsonCanonical event shape
saveQuotePaymentPlanActionlib/actions/submissions.tsSaves plan to policy.quote_billing
bindQuoteToPolicy()packages/db/src/queries/policy.tsWrites policy row + outbox event atomically
OutboxPublisherapps/policyone/lib/workers/outbox-publisher.tsPolls outbox → XADD events:policy
PolicyBoundPayloadpackages/events/src/policy-events.tsTypeScript event payload type
PolicyEvent<T> envelopepackages/events/src/envelope.tsStandard wrapper around every event
STREAM_KEYS.POLICY_EVENTSpackages/queue/src/stream-keys.ts"events:policy" constant