Guide contents

Lesson 8 of 53

Payment Gateway and Ledger: Framing, Scale Math, and Architecture

This lesson opens Chapter 5, Mission-Critical Payment Gateway & Double-Entry Ledger, Production System Blueprint 2: Financial Integrity, Idempotent Processing, and Distributed Ledger Design. It covers sections 1 to 3 and continues in the ledger and deep dives lesson.

At a glance

Item Value
Peak transaction volume 50,000 transactions per second (TPS)
Average daily transactions 100,000,000 transactions per day
Currencies USD, EUR, GBP, JPY, and CAD
Latency budget API gateway p99 < 100ms; ledger write commit p99 < 25ms
Durability 99.999999999% (11 9s)
Retention 7 years of immutable financial audit history
Reconciliation 15-minute sliding match windows
Ledger PostgreSQL double-entry, append-only, synchronous replication across 3 Availability Zones

1. The Scenario & Problem Framing

PROMPT:

"Design a mission-critical payment gateway and double-entry accounting ledger handling 50,000 peak transactions per second (TPS) across multi-currency, multi-tenant marketplaces. Guarantee zero double-charging, strict ACID compliance, complete mathematical auditability, sub-100ms authorization latency, and continuous real-time reconciliation against external bank settlement feeds."

In standard social media or e-commerce systems, an occasional lost like, dropped notification, or transient feed refresh glitch is completely acceptable.

In financial infrastructure, there is zero room for error:

  1. If you accidentally double-charge a customer, you violate consumer financial protection laws, trigger bank chargeback fees, and destroy brand trust.
  2. If your database drops a transaction during an ungraceful failover, you lose real company revenue.
  3. If an internal rounding bug drops a single cent across 100,000,000 daily transactions, you bleed $1,000,000 per month of untracked leakage and face severe regulatory sanctions during annual financial audits.

Senior engineers treat payment processing as a naive HTTP wrapper: receive a credit card payload, execute POST https://api.stripe.com/v1/charges, and write a record to a MySQL table with status = 'SUCCESS'.

To a Staff evaluator, this naive implementation fails immediately:

  • The Single-Column Balance Trap: Mutating a user's balance column directly (UPDATE accounts SET balance = balance - 100) destroys historical auditability. Money cannot simply appear or disappear; it must move between balance accounts.
  • The Two-Generals Problem: When an outbound HTTP call to a payment processor times out after 10 seconds, you do not know whether the bank processed the charge or dropped the packet. Treating a timeout as a failure causes immediate double-charges when the user retries.
  • The Phantom Settle: A database primary node crashes mid-transaction. Because replication was asynchronous, the secondary node is promoted with a 2-second data lag. The customer's credit card was charged at the bank, but your internal database has zero record that the transaction ever occurred.

Staff engineers treat payment systems as distributed, append-only cryptographic ledgers governed by Venetian double-entry bookkeeping, strict idempotent state machines, and continuous 15-minute reconciliation engines.

THE CORE PARADOX

A payment gateway must communicate with external, unpredictable third-party banking networks over fallible internet connections, yet internally guarantee deterministic, mathematically balanced, and legally auditable financial invariants on every single cent.

Staff Goal: Decouple volatile external payment rails from the immutable internal ledger, enforcing that Total Debits strictly equal Total Credits at the database engine level before any transaction commits to disk.

The L5 vs L6 Contrast — What the Evaluator Actually Hears

The Senior Candidate (L5): Begins by drawing a single payments table with columns: id, user_id, amount, and status. They propose calling Stripe's API with an exponential backoff retry loop. When asked, "What happens if the call to Stripe succeeds, but your database crashes before saving the record?", the candidate proposes catching the exception and running another retry. They fail to recognize that retrying will charge the customer a second time.

The Staff Candidate (L6): Reframes the problem into three decoupled failure domains within the first 60 seconds:

"Before touching third-party payment rails, we must separate authorization from ledger accounting. Every balance change must be an immutable append-only journal entry enforced by a database trigger where Sum(Debits) - Sum(Credits) = 0. All outbound provider calls must carry a deterministic, client-generated idempotency key. Any network timeout trips the transaction into a PENDING_RESOLUTION state rather than failing or retrying blindly. Finally, an independent reconciliation engine runs every 15 minutes to match bank settlement feeds against our internal ledger."

2. Step 1: Commit to Intent & Scale Math

Before choosing database engines or message brokers, an L6 Staff engineer establishes the core operational intent. The phrase "payment system" hides three conflicting operational models:

# INTENT PRIMARY SLO STORAGE MODEL CONSISTENCY BAR RETRY PHILOSOPHY
1 Card-Not-Present Latency (<100ms) ACID DB + Redis Linearizable Idempotent Safe
2 High-Volume Escrow Auditability Append-Only Ledger Strict Serial Two-Phase Outbox
3 Bank Clearing Batch Throughput (TPS) Partitioned Parquet Eventual Batch Idempotent Refund

The Three Incompatible Intents

  1. Card-Not-Present (CNP) E-Commerce Checkout:
    • The Goal: Authorize a customer's credit card or digital wallet (Apple Pay, Google Pay) in sub-100ms while preventing double-charges.
    • The Architectural Commit: Client-side tokenization (PCI-DSS SAQ A-EP), distributed idempotency locks in Redis, non-blocking provider routing, and explicit timeout resolution states.
  2. High-Volume Marketplace Escrow (Uber, Airbnb, Amazon):
    • The Goal: Hold buyer funds in escrow, deduct multi-tier platform commission fees, and disburse payouts to millions of independent sellers.
    • The Architectural Commit: Relational double-entry bookkeeping, immutable journal entries, deferred trigger constraints, and transactional outbox queues.
  3. Bank Clearing & Treasury Settlement:
    • The Goal: Ingest massive end-of-day bank clearing files (NACHA, ISO 20022, SWIFT, SEPA) and match them against internal ledger entries.
    • The Architectural Commit: Batched streaming diff pipelines, automated discrepancy categorization, and automated refund APIs.

STAFF MOVE

Senior: "We will use Stripe's API and save the transaction in MySQL with a status column."

Staff: "Before drawing any database tables, I want to clarify our accounting and audit requirements. A status column on a single payments row is an operational disaster—it cannot represent multi-party split payouts, commission deductions, or chargeback reversals without mutating historical records. I will design an immutable double-entry ledger where every balance change is an atomic append-only journal entry. We will enforce that Sum(Debits) - Sum(Credits) = 0 at the database engine level, and isolate external provider interactions behind an asynchronous state machine with a 15-minute reconciliation watchdog."

Why: Mutating financial records makes forensic auditing impossible and guarantees silent financial leakage.

Capacity Planning & Hardware Math

In an L6 loop, you must derive your storage tiers, network throughput, and database IOPS from first principles.

SCALE REQUIREMENTS

  • Peak Transaction Volume: 50,000 transactions per second (TPS).
  • Average Daily Transactions: 100,000,000 transactions per day.
  • Global Footprint: Multi-currency support across USD, EUR, GBP, JPY, and CAD.
  • Latency Budget: API gateway p99 < 100ms; ledger write commit p99 < 25ms.
  • Data Durability: 99.999999999% (11 9s) with zero unrecoverable financial records.
  • Retention: 7 years of immutable financial audit history (mandated by SEC, IRS, and SOX regulations).

SIZING STORAGE: THE DOUBLE-ENTRY APPEND-ONLY LEDGER

Each transaction in a double-entry ledger generates at least one journal entry and three ledger entries (Buyer Debit, Merchant Escrow Credit, Platform Commission Credit):

Text
Daily Records = 100,000,000 transactions * 4 rows = 400,000,000 rows/day
Annual Records = 400,000,000 rows * 365 days = 146,000,000,000 rows/year (146 Billion rows!)

Each ledger entry row requires approximately 200 bytes (UUID, transaction_id, account_id, amount_in_cents, direction, currency, created_at, signature):

Text
Daily Raw Data = 400,000,000 rows * 200 bytes = 80 GB/day
Annual Raw Data = 80 GB * 365 days ~ 29.2 Terabytes/year
With B-Tree Indexes & Write-Ahead Logs (WAL) ~ 70 Terabytes/year

The Storage Strategy: A single monolithic database will collapse under 146 Billion rows. We structure storage into three lifecycle tiers:

  1. Hot Tier (Last 30 Days): PostgreSQL cluster partitioned by month (ledger_entries_2026_09). Hosted on NVMe SSDs with synchronous replication across 3 Availability Zones (AZs).
  2. Warm Tier (30 Days to 1 Year): Read-only PostgreSQL read replicas and columnar analytics tables (ClickHouse / Snowflake) for financial reporting.
  3. Cold Tier (1 Year to 7 Years): Read-only Parquet files exported to Amazon S3 Glacier with WORM (Write Once, Read Many) Object Lock enabled to guarantee legal compliance against tampering.

3. End-to-End System Architecture

To process 50,000 TPS with zero double-charging, the architecture must separate the fast external checkout path from the strictly consistent accounting engine:

Figure 5.1: Mission-Critical Payment Processing & Ledger Architecture

The Architecture Components in Depth

flowchart TD
    Client["Client Browser / Mobile SDK"]
    C1["COMPONENT 1: Client Checkout & Secure PCI Tokenization Layer (SAQ-A Zero-Scope)<br/>Hosted Field iFrames Directly to PSP Vault (Raw PAN / CVV Never Touches Origin)<br/>Client-Side UUIDv4 Idempotency Key Anchor (idemp_usr_... in Local Storage)"]
    C2["COMPONENT 2: API Gateway & Distributed Idempotency Engine (Envoy + Redis Cluster)<br/>Phase 1: Completed Key Short-Circuit Lookup (X-Cache: HIT with Cached JSON)<br/>Phase 2: In-Flight Concurrency Mutex Lock (SET idemp:key PROCESSING NX EX 120)<br/>Phase 3: Transaction Finalization Commit (24h TTL Payload Caching)"]
    C3["COMPONENT 3: Payment Orchestrator & SAGA State Machine Engine<br/>Transactional Outbox Pattern (PostgreSQL State Transitions + Kafka Events)<br/>State Transitions: CREATED -#gt; IN_FLIGHT -#gt; AUTHORIZED -#gt; CAPTURED -#gt; SETTLED<br/>3.0s PSP Timeout Tripwire -#gt; Transitions to PENDING_RESOLUTION (No Blind Retries)"]
    C4["COMPONENT 4: Multi-PSP Adapter Gateway & Dynamic Provider Routing Matrix<br/>Unified Provider Interface: authorize(), capture(), refund(), queryStatus()<br/>Real-Time BIN & Issuer Authorization Rate Routing (Stripe / Adyen / Chase)<br/>Dynamic Circuit Breakers (Trips on #gt;3% Errors, Sub-250ms Failover)"]
    C5["COMPONENT 5: Immutable PostgreSQL Double-Entry Ledger (System of Record)<br/>Invariant: Sum(Debits) - Sum(Credits) = 0.00 (Balanced Journal Entries)<br/>Append-Only: Zero UPDATEs or DELETEs (Revoked by DB Triggers)<br/>Synchronous Replication Across 3 Availability Zones (synchronous_commit = on)"]
    C6["COMPONENT 6: Continuous 3-Way Reconciliation<br/>15-Minute Sliding Match Window Pipeline<br/>Triangulates: Ledger + PSP Logs + Bank SFTP<br/>Automated Drift Balancing & Aging Alarms"]
    C7["COMPONENT 7: Observability & Anomaly<br/>Continuous Trial Balance Monitor<br/>P0 Alarm on Delta != 0.00<br/>Recon SLI: 99.999% Match Target"]
    Client -->|"(1) Checkout Tokenized Ingress (X-Signature + Idempotency-Key)"| C1
    C1 -->|"(2) API Ingress (#lt;2ms Hot Path)"| C2
    C2 -->|"(3) Mutex Acquired: Process Payment"| C3
    C3 -->|"(4) Dispatch Acquiring Request"| C4
    C4 -->|"(5) Auth Succeeded: Atomic Settlement"| C5
    C5 -->|"Asynchronous Settlement Audit"| C6
    C5 -->|"Streaming Financial SLIs"| C7

Component 1: Client Checkout & Secure PCI Tokenization Layer (SAQ-A Zero-Scope)

Zero-Scope PCI DSS Compliance: The customer checkout frontend (web browser or native iOS/Android SDK) renders payment input fields via hosted iframes or sandboxed secure mobile elements. Raw Primary Account Numbers (PAN), CVVs, and expiration dates travel directly from the client's device to the Payment Service Provider's (PSP) PCI-compliant vault over TLS 1.3. The provider returns an opaque, single-use payment method token (tok_live_891b...). Application servers never touch, process, or store raw cardholder data, maintaining PCI-DSS SAQ-A compliance scope.

Client Idempotency Anchoring: When the checkout modal opens, the client generates a cryptographically random UUIDv4 Idempotency-Key and anchors it in persistent client-side storage (idemp_usr_...). Every subsequent button tap, network reconnection, or automatic retry triggered by the client transmits this exact idempotency key.

Tamper-Proof Payload Signatures: The client signs critical order attributes (amount, currency, merchant account, items hash) using an ephemeral HMAC-SHA256 signature header (X-Signature), preventing client-side price tampering (e.g., modifying an authorization amount from $100.00 to $1.00 in browser dev tools).

Component 2: API Gateway & Distributed Idempotency Engine (Hot Path)

Sub-2ms Hot-Path Interception: An Envoy/Kong reverse proxy fleet intercepts all incoming requests to /api/v1/payments/* and inspects the mandatory Idempotency-Key header before any downstream orchestration service is invoked.

Distributed Mutex & Response Cache (Redis Cluster): Executes a three-phase state machine:

  1. Lookup Phase: Queries the distributed Redis cluster for key idemp:{key}. If a completed transaction record exists, the gateway immediately returns the cached HTTP response payload with header X-Cache: HIT, executing zero downstream RPCs.

  2. Locking Phase: If the key does not exist, the gateway executes:

    Text
    SET idemp:{key} "PROCESSING" NX EX 120

    If lock acquisition fails (indicating a concurrent duplicate request from a double-click), the gateway returns HTTP 409 Conflict (or suspends the connection on a Redis Pub/Sub channel awaiting completion of the active thread).

  3. Finalization Phase: Once the orchestrator commits, it replaces the PROCESSING flag with the finalized HTTP status code and response payload with a 24-hour TTL.

Component 3: Payment Orchestrator & SAGA State Machine Engine

Transactional State Machine: The central conductor of the financial workflow. Manages the lifecycle of every transaction across strictly defined states: CREATED -> IN_FLIGHT -> AUTHORIZED -> CAPTURED -> SETTLED -> PENDING_RESOLUTION.

Transactional Outbox Pattern: To eliminate dual-write hazards between relational database commits and Kafka event emissions, the orchestrator writes the updated transaction state and an outbound integration event into PostgreSQL within the same ACID transaction:

SQL
INSERT INTO payment_outbox (event_id, aggregate_id, event_type, payload)
VALUES (:evt_id, :pay_id, 'PAYMENT_AUTHORIZED', :payload);

A Change Data Capture (CDC) engine (Debezium) tails the PostgreSQL write-ahead log (WAL) and streams events to Kafka with zero dual-write latency and zero data loss.

The 3.0-Second Timeout Tripwire: External payment provider calls enforce a strict 3.0-second network timeout. If a PSP hangs, the orchestrator refuses to guess the outcome or retry blindly; it transitions the transaction into PENDING_RESOLUTION and dispatches asynchronous status polling workers.

Component 4: Multi-PSP Adapter Gateway & Dynamic Provider Routing Matrix

Unified Provider Interface: Abstracts proprietary bank protocols (Stripe, Adyen, Chase Paymentech, BACS, SEPA, PIX) behind a unified polymorphic adapter interface (authorize(), capture(), refund(), queryStatus()).

Real-Time BIN & Authorization Optimization: Evaluates card Bank Identification Numbers (BINs), transaction amounts, and regional acquiring rules in real time. If Chase Paymentech experiences elevated decline rates in the European Union, the routing engine dynamically shifts Visa/Mastercard traffic to Adyen within 250 milliseconds.

Resilience & Circuit Breaking: Maintains per-provider circuit breakers. If a PSP's HTTP 5xx error rate exceeds 3.0% over a 60-second rolling window, the circuit breaker trips, instantly re-routing subsequent transaction authorizations to secondary acquiring rails without customer intervention.

Component 5: Immutable PostgreSQL Double-Entry Ledger (System of Record)

Fundamental Accounting Invariant: Money cannot appear or disappear; it can only move between accounts. Every financial operation is recorded as an atomic, multi-row journal entry where:

$$\sum \text{Debits} - \sum \text{Credits} = 0.00$$

Append-Only Immutability: The ledger_entries table is strictly append-only. Mutation operations (UPDATE, DELETE) are revoked via PostgreSQL permissions and verified by database triggers. Corrections are executed by appending reversing journal entries, preserving an immutable audit trail.

Synchronous ACID Persistence: Configured with synchronous_commit = on across three Availability Zones. Critical account state mutations execute under SERIALIZABLE or READ COMMITTED isolation with transactional row locks (SELECT ... FOR UPDATE or pg_advisory_xact_lock(account_id)), completely preventing negative account balances and race conditions.

Component 6: Continuous 3-Way Reconciliation Engine & Settlement Matcher

Asynchronous Match Pipeline: A dedicated background worker fleet running continuous 15-minute sliding match windows.

Triangulation Methodology: Reconciles three independent financial sources:

  1. Internal Double-Entry Ledger: Application records of authorized and captured funds.
  2. Payment Service Provider (PSP) Webhooks & Settlement Logs: Provider confirmation of successful charges and refunds.
  3. Bank Settlement Statements: Authoritative bank clearing statements (CAMT.053, BAI2, MT940 files) downloaded via SFTP.

Automated Drift Classification: Automatically matches transactions via unique clearing_reference_ids. Classifies variances into Timing Differences (clearing transit lag), Fee Discrepancies (interchange fee changes), and Hard Mismatches (orphaned charges). Automatically generates balancing journal entries for pre-approved fee structures and pages finance engineering if an un-reconciled item exceeds 24 hours.

Component 7: Financial Telemetry, Trial Balance & Anomaly Detection Pipeline

The Continuous Trial Balance Invariant Monitor: An independent telemetry daemon continuously evaluates the global ledger health invariant:

$$\Delta_{\text{trial_balance}} = \sum \text{All Ledger Debits} - \sum \text{All Ledger Credits}$$

If (\Delta_{\text{trial_balance}} \ne 0.00) for even a single microsecond, the system triggers an immediate P0 PagerDuty emergency incident, alerting on-call Staff engineers to investigate accounting integrity.

Core Financial SLIs:

  • payment.auth_success_rate: Percentage of attempted bank authorizations approved.
  • recon.match_rate: 99.999% target. Percentage of internal ledger transactions matched to external bank settlements within 24 hours.
  • pending_resolution_age_p99: Age of transactions in ambiguous states; must remain strictly under 15 minutes.

Step-by-Step Payment Orchestration & Double-Entry Ledger Flow

The following sequence details the complete lifecycle of a payment transaction from client checkout initiation to distributed idempotency locking, PSP acquiring authorization, double-entry ledger commit, and bank settlement reconciliation:

sequenceDiagram
    participant Client as Client SDK
    participant GW as API Gateway
    participant Orch as Payment Orch
    participant PSP as Stripe PSP
    participant DB as PostgreSQL DB
    participant K as Kafka Outbox
    participant Recon as Recon Engine
    Client->>GW: (1) POST /charge (Idemp-Key)
    GW->>Orch: (2) Lock, Redis SETNX
    Orch->>DB: (3) Write State: PENDING
    Orch->>PSP: (4) Auth, POST /v1/charges
    PSP-->>Orch: 200 OK, Auth Code
    Orch->>DB: (5) Atomic Balanced Journal<br/>DEBIT InFlight_Cash $100.00<br/>CREDIT Merchant_Escrow $100.00<br/>INSERT INTO outbox_events
    DB->>K: (6) Stream, CDC via WAL
    GW-->>Client: (7) 200 Success, Charge Confirmed
    Recon->>K: (8) Match, Bank CAMT.053

STEP 1: CLIENT CHECKOUT INITIATION & IDEMPOTENCY KEY GENERATION

The customer navigates to checkout. The client SDK generates a unique UUIDv4 idempotency key (idemp_9841b_c019). Card details are entered into the hosted iframe; Stripe returns an opaque payment method token (pm_card_visa_891). The client dispatches POST /api/v1/payments/charges carrying payload: amount: 10000 ($100.00 USD), payment_method: "pm_card_visa_891", and header Idempotency-Key: idemp_9841b_c019.

STEP 2: API GATEWAY IDEMPOTENCY LOCK ACQUISITION

The API Gateway intercepts the request. It queries the distributed Redis cluster for idemp:idemp_9841b_c019. The key does not exist. The gateway executes an atomic lock reservation:

Text
SET idemp:idemp_9841b_c019 "PROCESSING" NX EX 120

The lock succeeds. If the user rapidly double-clicks the purchase button, the second HTTP request fails the NX check and is rejected with HTTP 409 Conflict, preventing duplicate authorization calls.

STEP 3: TRANSACTIONAL OUTBOX STATE INITIALIZATION

The Payment Orchestrator receives the request. It begins a local PostgreSQL transaction:

SQL
INSERT INTO payments (payment_id, idempotency_key, amount_cents, currency, status)
VALUES ('pay_4901', 'idemp_9841b_c019', 10000, 'USD', 'IN_FLIGHT');

The transaction is committed to disk, creating a durable recovery checkpoint before any external network calls are executed.

STEP 4: MULTI-PSP ROUTING & CARD NETWORK AUTHORIZATION

The Provider Router selects the optimal acquiring partner (Stripe US). It formats an authorization request containing amount = 10000, currency = "usd", and passes the original idempotency_key. The call is bound by a strict 3.0-second HTTP timeout. The card issuing bank authorizes the transaction, placing a $100.00 hold on the customer's credit line. Stripe returns 200 OK with auth_code = "AUTH_891023" in 780 milliseconds.

STEP 5: ATOMIC DOUBLE-ENTRY LEDGER COMMITMENT

The orchestrator records the financial movement in PostgreSQL within an ACID transaction. Rather than modifying a single balance column, it appends a balanced double-entry journal entry:

SQL
BEGIN TRANSACTION;
-- 1. Insert Transaction Header
INSERT INTO journal_entries (entry_id, payment_id, reference_type, description)
VALUES ('jrn_8819', 'pay_4901', 'CUSTOMER_PURCHASE', 'Order #9841');
-- 2. Debit Asset Account (In-Flight Cash Receivable from Stripe)
INSERT INTO ledger_entries (entry_id, account_id, entry_type, amount_cents)
VALUES ('jrn_8819', 'acc_stripe_receivable', 'DEBIT', 10000);
-- 3. Credit Liability Account (Escrow Owed to Merchant)
INSERT INTO ledger_entries (entry_id, account_id, entry_type, amount_cents)
VALUES ('jrn_8819', 'acc_merchant_escrow', 'CREDIT', 10000);
-- 4. Update Payment State
UPDATE payments SET status = 'AUTHORIZED', auth_code = 'AUTH_891023' WHERE payment_id = 'pay_4901';
-- 5. Write to Outbox
INSERT INTO payment_outbox (event_id, event_type, payload)
VALUES ('evt_771', 'PAYMENT_AUTHORIZED', '{"payment_id": "pay_4901", "amount": 10000}');
COMMIT;

Database triggers verify that (\sum \text{Debits} - \sum \text{Credits} = 100.00 - 100.00 = 0.00). The transaction commits cleanly.

STEP 6: OUTBOX CDC EVENT STREAMING

Debezium tails the PostgreSQL Write-Ahead Log (WAL) and reads the committed record from payment_outbox. It publishes the PAYMENT_AUTHORIZED event into the Kafka topic payment_payments.events. Downstream fulfillment, email confirmation, and inventory microservices consume the event asynchronously.

STEP 7: GATEWAY RESPONSE CACHING & CLIENT CONFIRMATION

The Payment Orchestrator returns the completed payment JSON to the API Gateway. The gateway updates the Redis idempotency key:

Text
SET idemp:idemp_9841b_c019 '{"status":"AUTHORIZED","payment_id":"pay_4901"}' EX 86400

The client browser receives 200 OK and renders: "Payment Approved! Order confirmed." Any future request with idemp_9841b_c019 received in the next 24 hours will be served directly from Redis memory in under 2ms without touching payment rails.

STEP 8: CONTINUOUS 3-WAY BANK SETTLEMENT RECONCILIATION

At 03:00 UTC, the acquiring bank delivers the daily settlement clearing file (CAMT.053 XML) via SFTP. The Continuous Reconciliation Engine parses the file:

  1. It matches auth_code = "AUTH_891023" against the internal ledger.

  2. It confirms $100.00 was deposited into the company's operating account, minus a $2.90 interchange processing fee.

  3. The engine posts a secondary balanced journal entry:

    Text
    Debit: Cash_Operating_Account ($97.10)
    Debit: Processing_Fee_Expense ($2.90)
    Credit: InFlight_Cash_Receivable ($100.00)

    Net Balance: $100.00 Debits = $100.00 Credits ((\Delta = 0.00)).

The transaction is marked SETTLED. The 3-way reconciliation match rate SLI confirms 100% precision.