Payment Transaction Simulator
card payments · AWS serverless

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.

protocol ISO 8583 · 0100/0110 runtime Node.js 22 · Lambda iac Terraform ci/cd GitHub Actions · OIDC edge CloudFront · WAFv2 deploy CodeDeploy canary

The payment path

// what happens to one authorization

iso 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 batch

A 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.

Browser Bearer JWT CloudFront + OAC private S3 · HTTPS WAFv2 managed + rate rules API Gateway stage · throttle · logs Lambda Authorizer verify JWT → allow/deny API Lambda Node.js 22 PAN masked Secrets Manager JWT secret Supabase Postgres · RLS SQS queue + DLQ Batch Worker async Lambda CloudWatch X-Ray · alarms
synchronous request asynchronous batch traces & logs

What the platform actually does

// eight decisions worth defending in a review

auth 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 shifts

The new version is exercised directly on its BETA alias — bypassing the gateway and real users — before CodeDeploy shifts a single request onto it.

01
test + scan
tests, lint, Checkov (enforcing), npm audit, terraform test
02
plan
saved plan, encrypted artifact
03
approve
manual production gate
04
apply
exactly the approved plan
05
pre-traffic
BETA alias · real test payment · no users
06
CodeDeploy canary
10% / 5 min · errors, 5XX, p95
07
smoke
live endpoint, post-deploy
on alarm breach → CodeDeploy rolls the alias back to the previous version automatically and the pipeline fails red. No human in the loop for the unhappy path.

Stack

// small surface, deep platform

frontend

React 18TypeScriptViteTailwind

backend

Lambda · Node.js 22API Gateway + JSON SchemaSQS + DLQJWT · bcryptISO 8583 codec

platform

CloudFront + OACWAFv2Secrets ManagerCodeDeployX-Ray · CloudWatch

data & delivery

Supabase · PostgresRow Level SecurityTerraformGitHub Actions · OIDC

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 users table 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.