Platform

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.

5 Domains
Isolated with clear boundaries
UUID PKs
All tables use UUID primary keys
Event-Driven
Internal event bus for communication

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.

Publishing a domain eventruby
# 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}"
)
Append-only streams: Events are never modified or deleted. Each transaction has its own stream. Subscribers react to events asynchronously — the fraud engine scores the transaction when 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)

  • ProcessPayment
  • CapturePayment
  • RefundPayment
  • VoidPayment
  • ReviewFraudCase

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:

Ledger entry for a $50.00 captured payment (2.9% + $0.30 fee)ruby
# 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:

Risk Score
0.0–1.0
ML probability of fraud
Decisions
pass / review / block
Based on configurable thresholds
Latency
<5ms
Feature extraction + scoring
Explainability
SHAP values
Per-feature contribution scores

Technology Stack

Backend
Ruby on Rails 8.1
API mode, modular monolith
Frontend
Next.js 16
React 19, TypeScript, Tailwind 4
Database
PostgreSQL
UUID PKs, JSONB, GIN indexes
Cache & Jobs
Redis + Sidekiq
Caching, background processing
Event Store
Rails Event Store
Append-only event streams
Auth
Devise + JWT
MFA, API keys, role-based access
Encryption
Lockbox
AES-256-GCM + blind index
Fraud ML
LightGBM + SHAP
Python sidecar, 200K training
Audit
PaperTrail
Full change history on all models