Card authorization, done properly. On a platform built to be trusted.
An authorization simulator that speaks real ISO 8583 — bitmaps, MTIs, response codes — with idempotent retries and exact money handling, running on production-grade AWS infrastructure.
No real money moves here; every card is a test card. What's real is the engineering: a wire-format ISO 8583 codec, deterministic issuer decisions, database-enforced idempotency, JWT auth at the edge, and canary deploys that roll back on their own.
The payment path
// what happens to one authorizationiso Real ISO 8583 on the wire
Each payment becomes a packed 0100 request and 0110 response: MTI, 64-bit primary bitmap, fixed and LLVAR fields, STAN, RRN, auth code (field 38) and response code (field 39). Stored masked, round-trip tested.
issuer Deterministic decisions
Ordered issuer rules: Luhn check (14), amount limit (61) and test cards for 05, 51, 54, 59, 96. Same input, same answer — so every outcome is testable.
idem Idempotency in the database
A UNIQUE (merchant_id, order_id) index turns a client retry into a replay of the original result; reusing an order id with different details is a 409. Concurrent duplicates are resolved by the database, not by luck.
batch Safe to redeliver
SQS is at-least-once, so the load-test worker uses deterministic order ids, resumes a half-finished batch without duplicates, splits amounts to the exact cent, and marks a batch failed before it reaches the DLQ.
How a request moves through the platform
// two paths: synchronous request, asynchronous batchA protected call is authorised at the API Gateway edge before the main function ever runs. A load-test batch is accepted instantly and handed to a background worker over a queue, so no request is ever blocked waiting on bulk work.
What the platform actually does
// eight decisions worth defending in a reviewauth JWT at the edge
A dedicated Lambda authorizer verifies HS256 tokens before the main function runs. Login is timing-safe — a wrong username costs the same as a wrong password, so response timing can't enumerate users.
async Batch off the request path
POST /api/process-batch enqueues to SQS and returns 202 immediately; a worker Lambda drains the queue and the client polls GET /api/batches/{id} for progress.
deploy Canary with auto-rollback
AWS CodeDeploy shifts the Lambda alias 10% for five minutes, then 100% — and rolls back automatically if the Lambda-error, API-5XX or p95-latency alarms breach during the bake. Alarms use 60-second periods so they can fire inside that window.
waf Login-scoped rate limiting
WAFv2 managed rules plus a general per-IP limit, and a stricter one scoped to /api/login to blunt credential stuffing against the single admin account.
edge Fully private origin
S3 is entirely private; the frontend is reachable only through CloudFront over HTTPS via Origin Access Control. HTTP redirects to HTTPS; deep links resolve for the SPA.
data Masking + RLS lockdown
Card numbers are masked before any write or log — and a database CHECK constraint rejects an unmasked PAN even if the code regressed. RLS denies the anon key entirely; the service_role key lives in Secrets Manager, never in a Lambda environment.
obs Monitoring as code
X-Ray tracing, structured logs with PAN redaction, approval/decline business metrics from log filters, WAF logs with the Authorization header redacted, alarms on the worker and its DLQ, and a Budget guardrail — all in Terraform.
ci/cd Keyless, gated pipeline
OIDC instead of static keys, actions pinned to commit SHAs, and a saved plan that is encrypted, approved by a human, and applied exactly — never re-planned behind the reviewer's back.
From push to production
// validate in isolation before real traffic ever shiftsThe new version is exercised directly on its BETA alias — bypassing the gateway and real users — before CodeDeploy shifts a single request onto it.
Stack
// small surface, deep platformfrontend
backend
platform
data & delivery
Known trade-offs — stated on purpose
- Rollback alarms are threshold-based; on a very low-traffic deploy they may never see enough requests to fire.
- One admin user, from environment variables. A DB-backed
userstable and a Cognito migration path are designed but not yet wired. - The issuer is simulated by deterministic rules and test cards — there is no network connection, clearing or settlement.
- Encryption uses AWS-managed keys; customer-managed KMS keys (and with them encrypted SNS alarms) are the next step.