A small backend that keeps orders, payments, and inventory consistent even when the payment provider fails, times out, or the server crashes mid-request.
Built with Node.js/Express + PostgreSQL, and a mock payment provider that can be told to fail, succeed, or hang on demand - so every failure mode described below can actually be demonstrated, not just claimed.
- Idempotency keys - duplicate requests (same
Idempotency-Keyheader) never double-charge; they replay the original result. - Circuit breaker (via
opossum) - stops hammering a payment provider that's already failing, and recovers automatically. - Row-level locking -
SELECT ... FOR UPDATEon inventory prevents two concurrent buyers from both getting the last item in stock. - Crash/timeout recovery - a background reconciliation job resolves orders left in an unknown state (payment succeeded but the app never got to record it) by asking the provider for the real status, instead of guessing.
docker compose up --buildThis starts:
postgreson5432(schema auto-applied fromapi/migrations/001_init.sql)payment-mockon4001apion3000
Place an order (note the Idempotency-Key header - generate a new UUID per
logical attempt, and reuse it if you retry):
curl -X POST http://localhost:3000/orders \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"userId": "user-1", "productId": "sku-001", "quantity": 1}'Send the exact same request again with the same Idempotency-Key -
you'll get the identical response back instead of a second charge.
Check an order's status:
curl http://localhost:3000/orders/<order-id>Force every payment call to fail, then place a few orders in a row - after
enough failures the breaker opens and subsequent calls fail instantly
without even hitting payment-mock:
curl -X POST http://localhost:4001/admin/mode -H "Content-Type: application/json" -d '{"mode": "always_fail"}'Watch the api container logs for [circuit-breaker] OPEN. Switch back
with {"mode": "normal"} and watch it move to HALF-OPEN then CLOSED.
Force slow responses (8s, longer than the API's 4s timeout) so the API can't tell if the charge went through:
curl -X POST http://localhost:4001/admin/mode -H "Content-Type: application/json" -d '{"mode": "slow"}'Place an order - you'll get a 202 with PENDING_RECONCILIATION. Within
RECONCILIATION_INTERVAL_MS (15s by default) the background job checks the
provider's real status for that idempotency key and resolves the order to
PAID or FAILED automatically. Check with GET /orders/:id before and
after to see the state flip without you doing anything.
The seeded product (sku-001) has 5 units in stock. Fire several concurrent
requests for it with different idempotency keys and different quantities
summing past 5 - only the ones that fit will succeed; the rest get a 409 Insufficient stock, never an oversold order.
This is a portfolio piece focused on the payment-consistency problem, not a
full e-commerce app - no auth, no catalog UI, no cart. A minimal Postman
collection or the curl commands above are the intended way to exercise it.
order-processing-system/
├── docker-compose.yml
├── README.md
│
├── payment-mock/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ └── index.js
│
└── api/
├── Dockerfile
├── package.json
├── .env.example
│
├── migrations/
│ └── 001_init.sql
│
├── tests/
│ ├── idempotency.test.js
│ ├── locking.test.js
│ └── circuitBreaker.test.js
│
└── src/
├── config/
│ └── env.js
│
├── db/
│ ├── pool.js
│ └── repositories/
│ ├── orderRepository.js
│ ├── idempotencyRepository.js
│ ├── inventoryRepository.js
│ └── paymentRepository.js
│
├── services/
│ ├── orderService.js
│ ├── paymentClient.js
│ └── reconciliationService.js
│
├── routes/
│ └── orderRoutes.js
│
├── middleware/
│ └── errorHandler.js
│
└── index.js