Payeazie
Payment operations: async charging, recovery tools and a full audit trail
A Fastify API creates payments idempotently and hands charging to BullMQ workers. Every status change goes through one transition model with an audit record, and an internal ops dashboard lets operators retry, reconcile and refund stuck or failed payments.
- 2 background workers
- 5 payment states
- Audit row per transition
- 44 Vitest cases
§1
The problem
Charging a card inside the HTTP request makes checkout as slow as the gateway, and a timeout leaves nobody sure whether the customer paid. Payments that get stuck need a safe way for an operator to fix them, with a record of who did what.
§2
My role
Sole developer: API, workers, status model, auth (JWT, Google OAuth, password reset), the merchant UI and the internal ops dashboard.
§3
Architecture
§4
Key decisions
D/01
Idempotent payment intents
One CTE returns the existing payment for (order, idempotency key) or inserts a new one. Reusing a key with a different payload is 409; paying an order that's already processing or paid is 422.
D/02
API and workers split
The API persists the payment as pending and enqueues a charge job (5 attempts, exponential backoff from 250 ms). The gateway call happens in a worker, never in the request.
D/03
No double charges
The charge worker selects the payment with FOR UPDATE SKIP LOCKED, so two workers can never pick up the same payment.
D/04
One source of truth for status
pending → processing → succeeded / failed, succeeded → refunded, failed → pending for a retry. Finality, retryability and refundability all come from the same module.
D/05
Role-aware surfaces
Merchants see their own payments; admin and ops roles see all of them plus recovery actions, each attributed to the operator in the audit log.
§5
Challenges
- Stuck payments: a reconcile worker sweeps payments that aren't in a final state and asks the gateway what really happened. Operators can also restart or reconcile one by hand.
- Testing failure paths without a real gateway: a mock gateway and webhook simulator force success, failure or slow processing on demand.
- Hardening the API with Helmet, rate limiting and secure sessions, plus health and metrics endpoints for basic operational visibility.
§6
Stack
- Node.js
- Fastify
- BullMQ
- Redis
- PostgreSQL
- React
- TypeScript
- Vitest
Next case study
MediSync
Patient-management back end as Spring Boot microservices with gRPC and Kafka