Skip to main content

Application Logic

:::info Source Sourced from services/billing-service/APPLICATION_LOGIC.md in the documentation repo. :::

1. Application Services

  • PaymentService — PaymentIntent creation, capture, refund.
  • InvoiceService — generate, finalize, render PDF, mark paid.
  • SubscriptionService — lifecycle, renewals, cancellation.
  • DunningService — failed payment retry schedule + communication.
  • TaxService — tax calculation via Stripe Tax.
  • PayoutService — compute provider earnings + trigger payouts.
  • WebhookHandler — Stripe webhook signing + dispatch.
  • ReconciliationService — daily reconciliation vs Stripe.

2. Commands

CommandTrigger
CreatePaymentIntentmarketplace.order.placed.v1
ConfirmPaymentStripe webhook
RefundPaymentAdmin or auto-refund policy
CreateInvoiceSubscription renewal, marketplace order
FinalizeInvoiceInvoice ready to charge
StartDunningPayment failed on subscription
TriggerPayoutScheduled weekly
VoidInvoiceAdmin

3. Queries

  • getSubscription(id), listSubscriptions(tenantId)
  • getInvoice(id), listInvoices(tenantId)
  • getPayment(id), getProviderEarnings(providerTenantId)
  • getPayout(id), listPayouts(providerTenantId)

4. Sagas

  • Billing step of Purchase Saga: create PaymentIntent → confirm → emit payment.succeeded.v1.
  • Subscription Renewal Saga (nightly).
  • Refund Saga (coordinated with marketplace to revoke license).
  • GDPR Erasure Saga (legal hold on billing records per tax law; anonymize user PII).

5. Policies

  • Idempotency: Idempotency-Key on all writes; Stripe API also accepts idempotency keys.
  • Webhook validation: HMAC signature + 5-min timestamp window + nonce cache.
  • Dunning: 3 reminders over 7 days → suspension → 30 days → cancel.
  • Refund window: per tenant policy (default 14 days).
  • Payout threshold: provider balance ≥ $50 (configurable); weekly Friday.

6. Use Case Flows

6.1 Order Payment

marketplace.order.placed.v1 (amount, currency, orderId)


CreatePaymentIntent(amount, currency, metadata={orderId})


Return client_secret to frontend → Stripe Elements → 3DS if required

▼ Stripe confirms → webhook /webhooks/stripe

Validate signature → identify event

├─ payment_intent.succeeded → emit billing.payment.succeeded.v1
└─ payment_intent.failed → emit billing.payment.failed.v1

6.2 Subscription Renewal

Nightly cron: SELECT subscriptions WHERE currentPeriod.end < now()
For each:
CreateInvoice(lines from subscription.itemQuantities)
FinalizeInvoice → attempts payment
On success: extend currentPeriod
On fail: StartDunning

6.3 Refund

Admin POST /payments/{id}/refund (reason, amount)


Stripe API refund with original Idempotency-Key prefix


Emit billing.payment.refunded.v1


marketplace consumes → revoke license → revoke enrollment

6.4 Dunning

Payment failed on subscription S:
Day 1: email reminder + in-app banner. Retry payment.
Day 3: stage 2. Retry.
Day 7: stage 3. Retry. Manager notified if org subscription.
Day 10: suspension_pending. Grace period.
Day 14: suspended. Tenant can no longer use paid features.
Day 44: cancel.
Payment anytime → resolve, return to active.

6.5 Payout

Weekly Friday job:
For each provider tenant:
Compute earnings (order totals × (1 - 0.15) for that period)
Subtract refunds
If balance ≥ $50:
CreatePayout(providerConnectAccountId, amount)
Emit billing.payout.initiated.v1
Reconciliation on Monday vs Stripe Connect balance.