Three problems, closely

Correct by
default.

Three recurring problems across five companies, and how each one got solved structurally — not by asking people to be careful.

Fig 01 · settlement window

Eight hours, then forty-five minutes.

Same settlement run, before and after re-architecting the engine for parallel processing at Verifone. The bar is to scale.

elapsed processing time
BEFORE 8h 00m AFTER 45m ↓ 90% reduction

The settlement engine ran as a single sequential pass over every batch. Re-architecting it for parallel processing — partitioning batches and running them concurrently with isolated failure domains — took the run from an 8-hour SLA that ate the overnight window to 45 minutes with headroom to spare.

Fig 02 · the file that got rejected

One duplicate, whole batch held.

The settlement engine's output to the acquirer is a strict 80-byte fixed-record file. One duplicate transaction in that file used to mean the acquirer rejected it outright — holding funds for every merchant in the batch, not just the duplicate one. The uniqueness key is configurable per settlement service, so a service that needs a different combination of fields to define "duplicate" can set that up independently. Toggle the fix on and off.

settlement → acquirer file pipeline
TXN TRANSACTION STREAM DUP CHECK KEY: CONFIG PER SERVICE BEFORE RECORD WRITE in-memory → self-cleaning file store (no check here) RECORD WRITER 80-BYTE FIXED FORMAT FILE ACQUIRER FILE ● clean ACQUIRER ACCEPTED · on time

With the check active, only unique transactions reach the writer — the file stays clean and the acquirer accepts it on time.

This runs underneath multi-million-transaction settlement services. Since it was introduced, the acquirer file has had zero rejections.

Fig 03 · the recurring problem

New customer — config change, not a project.

Four companies, one obsession: take bespoke engineering out of onboarding. On the left, integration as a project. On the right, as a socket. Tap a company.

bespoke integration → plugin socketTSYS
BEFORE · per-customer engineering AFTER · one interface

TSYS — configurable card-application forms let banks self-serve their own onboarding.

Fig 04 · read/write cache, 15-min TTL

Fast, and never stale.

A request goes Account Service → Processor Gateway, which asks an account-specific singleton actor whether this account was refreshed in the last 15 minutes. Fresh, and it's served straight from Elasticsearch. Stale, and the Gateway calls the processor APIs (Fiserv/Stifel Optis over REST, TSYS TS1/TS2 over SOAP), publishes the refresh event onto Pulsar, and re-indexes Elasticsearch. When an update comes through, the data and the singleton's refresh clock update together — no window where they can disagree. If the processor call errors, whatever's currently in Elasticsearch is served anyway — flagged stale, with a last-updated timestamp for the caller. Pick a scenario.

account service → gateway → singleton actor → cache

▷ Fresh — account was refreshed within the last 15 minutes; served straight from Elasticsearch.

Fig 05 · once each, not twice

A different singleton, a different job.

Not to be confused with the file-level check in Fig 02 or the account-refresh actor in Fig 04 — this is a third use of the same pattern. When the Processor Gateway fetches transactions from the processor APIs (Optis, TS1/TS2), a duplicate fetch can happen two ways: the same user triggers the fetch more than once, or Directory publishes the same account event onto Pulsar twice. Either way, without a check, the same transaction gets processed twice. A cluster singleton actor sits in front of publish/store via the Gateway — so it only ever happens once. Built at SpendLabs, carried forward into Fiserv. Toggle the fix.

processor fetch → dedup → publish + store
PGW PROCESSOR GATEWAY ⚠ duplicate call (e.g. same user, twice) OPTIS · TS1 · TS2 REST · SOAP TRANSACTION FETCH CLUSTER SINGLETON DEDUP · VIA PGW ONE POD PROCESSES EACH TXN NO SINGLETON (bypassed) PULSAR ● published once ELASTICSEARCH ● stored once

With the singleton active, exactly one pod processes each transaction — Pulsar and Elasticsearch each see it exactly once.

Fig 06 · quality as a default

Testcontainers over unit tests.

Across 30+ services at Verifone, the testing strategy shifted from unit-test-heavy coverage to container-backed integration tests. A container-backed test spins up a real dependency — a real Postgres, a real broker — instead of mocking it, so a broken contract between two services fails in CI, not in production.

built-in quality
BEFORE · unit-heavy

Mocked dependencies pass in isolation. Integration breaks surface after merge — sometimes after deploy.

AFTER · container-backed

Real dependencies via Testcontainers. A broken contract fails the build, not the customer.