Architecture for a multi-bank payment aggregator
A blueprint the client's team can build from, covering everything from the domain model to the failover runbook.
- Domain
- Fintech, payments
- Our role
- Solution architecture, domain modeling, security and infrastructure design
- Year
- 2026
We designed the solution architecture for a payment aggregator that connects merchants to several acquiring banks through a single API and a hosted checkout, for a client launching a payment platform in a regional market. The deliverable is a blueprint for the client's own engineering team, not a system we operate. It covers the domain model, a nominal account with an append-only ledger, payment and refund flows on Temporal, smart routing between banks, IAM with Keycloak, network topology, high availability with a failover runbook, CI/CD and reference code for the core patterns.
Challenge
- Model a multi-level money chain (acquiring bank, aggregator, merchant, shop) where merchant funds sit on a nominal account and never mix with the aggregator's own money.
- Route payments between banks per merchant and per payment method, and switch banks quickly when one fails or rejects a merchant's traffic.
- Run long, failure-prone flows (bank calls, webhooks, timeouts) without losing or double-counting money.
- Meet fiscal receipt rules for payments, cancellations and partial refunds, including multi-vendor marketplace baskets.
- Keep the operator back office off the public internet, and plan for growth from about 100 RPS to 1,000+ RPS at peak with a clear failover story.
Solution
We delivered architecture documents, sequence and component diagrams, and small reference implementations, organized by concern. Business domain: a shared vocabulary, a domain model and flows for deposit, merchant and shop onboarding, cancel and marketplace partial refund. Backend: a layered FastAPI service (routes → services → data access), the data model and Temporal workflow patterns for payment and refund sagas. Frontend: patterns for the merchant portal and back office in React and TypeScript with OIDC and PKCE. IAM: Keycloak realm and role design, with identity configuration applied as versioned migrations in code. Infrastructure: a VPC with public and private subnets, corporate VPN, database HA options, an active-passive failover runbook and a GitLab CI/CD layout with separated responsibilities.
Architecture
What the system does
- Hosted checkout and API
- Merchants create a payment over a REST API and redirect the payer to a hosted page that offers card, QR bank transfer and wallet payments.
- Smart routing
- Bank priorities per merchant and per payment method, a manual force-switch, and automatic bank disablement when errors pass a threshold.
- Split settlement on a nominal account
- Each payment carries a split configuration, so the bank separates the merchant's share from the aggregator's commission when it settles to the nominal account.
- Full payment lifecycle
- Bills with a TTL and statuses, several payment attempts per bill, full and partial refunds, pre-settlement void and chargebacks. Marketplace refunds return only the affected vendor's share, and receipt data travels with every payment and reversal.
- Merchant and shop onboarding
- Identity first, then business verification (KYB) through a third-party provider; the company record is created only after a positive result. Shops prove domain ownership, pass a compliance review and then get production API keys.
- Merchant portal and back office
- Merchant teams get 40+ fine-grained permissions across statistics, balances, payments, payouts, shops, webhooks and users. Operators get routing rules, bank health monitoring, individual commissions, access blocking and audit logs.
Engineering decisions
- Payment and refund sagas are Temporal workflows. Bank webhooks become workflow signals, and a payment not confirmed within its TTL expires cleanly. The cost is one more stateful component; in return the team gets retries, timeouts and a visible state per payment instead of hand-written state machines and cron jobs.
- The ledger is separate from payment state. A bill is the state of a payment session; a transaction is the immutable fact of a money movement, with a per-merchant sequence number and the balance before and after. Balances derive from the ledger, so they can be audited and reconstructed at any time.
- Triple-match validation for reversals: the refund amount, the sum of vendor shares and the sum of receipt line items must match before any bank call. Inconsistencies are caught on the platform side, not at the bank or in the receipt.
- Banks plug in through adapters, and routing lives in data: banks, terminals and merchant-to-bank priorities are database records, with terminal credentials encrypted at the column level. Adding a bank means an adapter and configuration, not a schema change.
- For high availability we compared two options with their costs: a managed PostgreSQL cluster with synchronous replication across zones (RPO = 0, automatic failover, higher cost) and an active-passive pair with asynchronous replication. For the active-passive pair we wrote a step-by-step runbook: detection, promotion, traffic switch and rebuilding the old primary as a replica.
Diagrams
Stack
- Python
- FastAPI
- SQLAlchemy
- Temporal
- PostgreSQL
- Keycloak
- OAuth 2.0 / OIDC
- React
- TypeScript
- GitLab CI
- OpenTofu
- Helm
- Docker
- Nginx
What this shows
- A business brief turned into a buildable architecture for a regulated payments product before any production code was written.
- Money flows modeled through the domain: aggregator hierarchy, nominal accounts, splits, ledger, refunds and chargebacks.
- Reliable distributed workflows around unreliable bank APIs and webhooks, with routing and failover between providers.
- Security and infrastructure for fintech: centralized IAM, fine-grained RBAC, a private back office, encrypted secrets, signed webhooks, IP allow-listing and an HA plan with explicit trade-offs.
Questions about this case
Did you build and run this platform?
No. We delivered the architecture: documents, diagrams and reference code for the core patterns. The client's own team builds and operates the platform from that blueprint.
How does the design prevent lost or double-counted money?
Each payment runs as a durable Temporal workflow. A bank webhook is accepted only after its signature is verified and is passed to the workflow as a signal, and a payment not confirmed within its TTL expires. Money movements go to an append-only ledger with a sequence number and balance before and after, and partial refunds pass a triple-match check first.
What happens when a bank or the database fails?
A bank whose errors pass a threshold is disabled automatically, and operators can force-switch traffic by hand. For the database we compared a managed cluster with synchronous replication against an active-passive pair, and wrote a step-by-step failover and recovery runbook for the latter.