Events
:::info Source
Sourced from services/billing-service/EVENT_SCHEMAS.md in the documentation repo.
:::
1. Envelope
Standard. retentionClass = regulated (7-year cold archive for tax compliance).
2. Events Published
2.1 billing.subscription.created.v1
interface SubscriptionCreatedV1 { subscriptionId: SubscriptionId; tenantId: TenantId; planId: string; state: string; trialEnd?: ISODate; currentPeriod: { start: ISODate; end: ISODate }; itemQuantities: Record<string, number>; }
2.2 billing.subscription.changed.v1
interface SubscriptionChangedV1 { subscriptionId: SubscriptionId; tenantId: TenantId; previousState: string; newState: string; changedAt: ISODate; reason?: string; }
2.3 billing.subscription.canceled.v1
interface SubscriptionCanceledV1 { subscriptionId: SubscriptionId; tenantId: TenantId; canceledAt: ISODate; effectiveAt: ISODate; reason?: string; }
2.4 billing.invoice.created.v1, .finalized.v1, .paid.v1, .voided.v1
interface InvoicePaidV1 { invoiceId: InvoiceId; tenantId: TenantId; customerId: string; total: Money; paidAt: ISODate; paymentId: PaymentId; }
2.5 billing.payment.succeeded.v1 (integration event)
interface PaymentSucceededV1 {
paymentId: PaymentId;
orderId?: OrderId;
invoiceId?: InvoiceId;
tenantId: TenantId;
amount: Money;
currency: ISO4217;
processor: string;
processorRef: string;
succeededAt: ISODate;
}
2.6 billing.payment.failed.v1
interface PaymentFailedV1 { paymentId: PaymentId; orderId?: OrderId; tenantId: TenantId; amount: Money; reason: string; processorCode?: string; failedAt: ISODate; }
2.7 billing.payment.refunded.v1
interface PaymentRefundedV1 { paymentId: PaymentId; refundId: string; tenantId: TenantId; amount: Money; reason: string; refundedAt: ISODate; }
2.8 billing.dunning.*
interface DunningStartedV1 { processId: ULID; subscriptionId: SubscriptionId; tenantId: TenantId; startedAt: ISODate; }
interface DunningStageAdvancedV1 { processId: ULID; stage: string; advancedAt: ISODate; }
interface DunningResolvedV1 { processId: ULID; resolution: string; resolvedAt: ISODate; }
2.9 billing.payout.initiated.v1 / .completed.v1 / .failed.v1
interface PayoutCompletedV1 { payoutId: ULID; providerTenantId: TenantId; amount: Money; processorRef: string; completedAt: ISODate; }
2.10 billing.tax.calculated.v1
For audit / analytics, tax computation at order time.
3. EP-6 — Marketplace checkout (Ghasi-EdTech implementation, 2026-04)
The following types are emitted on billing.orders flows (outbox, buyer tenantId context). Payload shapes match @ghasi/event-envelope envelopes in code (services/billing-service). Amounts are integer micro-units; currency ISO 4217.
| Event type (wire) | When |
|---|---|
marketplace.order.placed.v1 | Order created (pending_payment). |
billing.payment.succeeded.v1 | Sandbox pay succeeds (paid). |
billing.payment.refunded.v1 | Refund completes after enrollment revoke handoff. |
marketplace.order.refunded.v1 | Same refund saga (buyer-visible order outcome). |
marketplace.coupon.redeemed.v1 | Coupon applied on order create + redemption row written. |
billing.payout.scheduled.v1 | Reserved — emit when payout scheduler exists (US-32). |
billing.payout.paid.v1 | Reserved — emit when payout completion exists (US-32). |
3.1 marketplace.order.placed.v1 (billing outbox)
interface MarketplaceOrderPlacedV1Payload {
orderId: string;
listingId: string;
courseId: string;
buyerTenantId: string;
providerTenantId: string;
totalAmountMicros: number;
currency: string;
}
3.2 billing.payment.succeeded.v1 (checkout)
interface BillingPaymentSucceededV1Payload {
orderId: string;
paymentRef: string;
amountMicros: number;
currency: string;
}
3.3 billing.payment.refunded.v1
interface BillingPaymentRefundedV1Payload {
orderId: string;
amountMicros: number;
currency: string;
}
3.4 marketplace.order.refunded.v1
interface MarketplaceOrderRefundedV1Payload {
orderId: string;
listingId: string;
providerTenantId: string;
}
3.5 marketplace.coupon.redeemed.v1
interface MarketplaceCouponRedeemedV1Payload {
orderId: string;
couponId: string;
discountMicros: number;
}
Note: Canonical subscription/invoice events in §2 remain the long-term billing model; §3 documents the EP-6 marketplace order path coexisting with that roadmap.
4. Events Consumed
marketplace.order.placed.v1→ create PaymentIntent (future PSP; today order is created in billing directly).tenant.org.provisioned.v1→ create customer + trial subscription (if plan includes trial).gdpr.subject_request.received.v1→ anonymize PII in customer records (legal hold on billing retained).
5. Versioning
- v1 schemas additive.
- Money field always in micro-units + ISO4217 (never change unit).
- Processor-specific metadata in separate
processorMetadataJSONB field.
6. Idempotency
- Consumer inbox dedups.
- Stripe webhooks:
event.idis PK in inbox. - Saga steps idempotent on order-id.
7. Outbox / Inbox
Standard; regulated class → 7-year cold archive.
8. Partition Key
tenantIdfor subscription/invoice events.orderIdfor payment events (groups with marketplace saga).
9. DLQ
billing.dlq. Poison messages alerted; manual reconciliation tool for stuck payments.