Architecture
How PayForge processes payments using event sourcing, CQRS, saga orchestration, and double-entry bookkeeping. Explore the interactive diagrams below.
System Overview
PayForge is built as a modular monolith with domain-driven boundaries. Each domain (Payments, Ledger, Fraud, Merchants, Webhooks) is isolated with its own models, commands, and policies. Click on any domain below to explore its internals.
Payment State Machine
A payment moves through a deterministic state machine managed by the Saga orchestrator. Click each state to inspect its domain events and ledger entries, or press Play Flow to watch the full lifecycle animate.
Event Sourcing
Every state change is captured as an immutable event using Rails Event Store. The current state of a payment is derived by replaying its event stream. This provides a complete audit trail and enables temporal queries — you can reconstruct the exact state of any transaction at any point in time.
# When a payment is authorized, an event is published
event = Payments::Events::PaymentAuthorized.new(
data: {
transaction_id: transaction.id,
amount_cents: transaction.amount_cents,
currency: transaction.currency,
auth_code: "A12345",
occurred_at: Time.current
}
)
# Published to a per-aggregate stream
Rails.configuration.event_store.publish(
event,
stream_name: "Payment$#{transaction.id}"
)PaymentAuthorized is published.CQRS Pattern
Command Query Responsibility Segregation separates write operations (commands) from read operations (queries). Commands go through domain services that enforce business rules, while queries read from optimized projections.
Commands (Writes)
ProcessPaymentCapturePaymentRefundPaymentVoidPaymentReviewFraudCase
Queries (Reads)
- List transactions (filtered, paginated)
- Transaction detail + event stream
- Ledger entries per transaction
- Fraud statistics & analytics
- Merchant dashboard metrics
Double-Entry Ledger
Every financial movement creates balanced debit/credit entries across accounts. The ledger is append-only — corrections are made with reversing entries, never by modifying existing records. Watch how a payment flows through the accounting system:
# Total fee: $1.75 → merchant receives $48.25
DoubleEntryService.new.record_capture(transaction: transaction)
# Creates balanced entries:
# Debit merchant_receivable $50.00 (money owed to merchant)
# Credit payment_processing $50.00 (payment cleared)
# Credit merchant_revenue $48.25 (net amount after fees)
# Debit platform_fees $1.75 (PayForge's fee)
# Invariant: total debits == total credits (always)Fraud Detection Engine
Every transaction is evaluated by a LightGBM model trained on 200K+ synthetic transactions (2% fraud rate, 5-fold cross-validation, AUC 1.0). Try different scenarios below to see how the model scores transactions with SHAP explainability: