Skip to content

Devendraxp/payment-gateway

Repository files navigation

Payment Gateway Simulator

Dummy payment aggregator with three separate NestJS microservices:

Architecture

Payment Gateway Architecture

Merchant
  |
  v
payment-orchestrator :3000
  | Kafka: payment.attempt.events, payment.completed
  | RabbitMQ: verification + webhook dispatch
  v
metrics-service :3001              experiment-service :3002
  | Redis hot metrics               | assignment, outcomes, bandit state
  v                                 v
Redis + PostgreSQL + Kafka + RabbitMQ

Services

  • services/payment-orchestrator: payment API, routing, gateway simulation, circuit breaker, outbox, verifier, webhooks.
  • services/metrics-service: Kafka attempt consumer, sliding-window Redis metrics, PostgreSQL metric history.
  • services/experiment-service: experiments, deterministic assignment, bandit updates, experiment outcomes, analyzer worker.

API

# Payment Orchestrator health
GET    /ops/liveness                         service liveness
GET    /ops/readiness                        DB and Redis readiness

# Payments
POST   /payments                             create a payment
GET    /payments/{paymentId}                 get payment details
GET    /payments/{paymentId}/attempts        list gateway attempts
GET    /payments/{paymentId}/routing-decision
                                             get stored routing decision
GET    /payments/{paymentId}/status-verification
                                             get async verification status

# Gateway admin
GET    /gateways                             list gateway configs
GET    /gateways/{gatewayName}               get one gateway config
PATCH  /gateways/{gatewayName}               update gateway simulator config

# Scoring and routing debug
GET    /scoring/debug                        inspect ranked gateways for a payment context

# Circuit breaker
GET    /circuit-breaker/status               list circuit breaker states
POST   /circuit-breaker/force-open/{gatewayName}/{method}
                                             force a route open/unhealthy
POST   /circuit-breaker/force-close/{gatewayName}/{method}
                                             force a route closed/healthy

# Retry policies
GET    /retry-policy                         list retry policies
GET    /retry-policy/{merchantId}            get merchant retry policy
PUT    /retry-policy/{merchantId}            create/update merchant retry policy

# Metrics Service health
GET    :3001/ops/liveness                    service liveness
GET    :3001/ops/readiness                   DB and Redis readiness

# Metrics
GET    :3001/metrics/health                  Kafka consumer and buffer status
GET    :3001/metrics/gateways                all gateway hot metrics
GET    :3001/metrics/gateways/{gatewayName}  gateway metrics grouped by method
GET    :3001/metrics/gateways/{gatewayName}/{method}
                                             gateway-method hot metrics
GET    :3001/metrics/history/{gatewayName}   persisted metric history

# Experiment Service health
GET    :3002/ops/liveness                    service liveness
GET    :3002/ops/readiness                   DB and Redis readiness

# Experiments
POST   :3002/experiments                     create experiment
GET    :3002/experiments                     list experiments
GET    :3002/experiments/assign              assign customer to active experiment
GET    :3002/experiments/{experimentId}      get experiment
PATCH  :3002/experiments/{experimentId}      update experiment
POST   :3002/experiments/{experimentId}/pause
                                             pause experiment
POST   :3002/experiments/{experimentId}/complete
                                             complete experiment
GET    :3002/experiments/{experimentId}/results
                                             get experiment results

# Bandit
GET    :3002/bandit/state/{segment}          get Thompson Sampling state
GET    :3002/bandit/order/{segment}          sample gateway order
POST   :3002/bandit/reset/{segment}/{gatewayName}
                                             reset gateway bandit state

Start

docker compose up -d --build
docker compose exec -T payment-orchestrator node dist/database/seeds/seed.js

Run Published Images

You can also run this application, without sourcecode, all service images are uploaded on docker-hub and can run with a single docker-compose file.

docker compose -f docker-compose.dockerhub.yml up -d

Health checks:

curl http://localhost:3000/ops/readiness
curl http://localhost:3001/ops/readiness
curl http://localhost:3002/ops/readiness

Useful UIs:

  • Kafka UI: http://localhost:8080
  • RabbitMQ Management: http://localhost:15672 (guest / guest)

Payment API

Create UPI payment:

curl -X POST http://localhost:3000/payments \
  -H "Content-Type: application/json" \
  -H "idempotency-key: pay-upi-001" \
  -d '{
    "merchantId": "amazon",
    "customerId": "cust_001",
    "amountMinor": 33000,
    "paymentMethod": "UPI",
    "instrument": { "vpa": "user@paytm", "bank": "HDFC" }
  }'

Create card payment:

curl -X POST http://localhost:3000/payments \
  -H "Content-Type: application/json" \
  -H "idempotency-key: pay-card-001" \
  -d '{
    "merchantId": "swiggy",
    "customerId": "cust_002",
    "amountMinor": 150000,
    "paymentMethod": "CARD",
    "instrument": { "cardNetwork": "VISA", "bank": "HDFC", "last4": "1234" }
  }'

Payment lookups:

curl http://localhost:3000/payments/{paymentId}
curl http://localhost:3000/payments/{paymentId}/attempts
curl http://localhost:3000/payments/{paymentId}/routing-decision
curl http://localhost:3000/payments/{paymentId}/status-verification

Orchestrator Admin

Gateway routes:

curl http://localhost:3000/gateways
curl http://localhost:3000/gateways/razorpay
curl -X PATCH http://localhost:3000/gateways/razorpay \
  -H "Content-Type: application/json" \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod" \
  -d '{ "successRate": 0.10 }'

Scoring debug:

curl "http://localhost:3000/scoring/debug?method=UPI&bank=HDFC&amountMinor=33000"

Circuit breaker:

curl http://localhost:3000/circuit-breaker/status
curl -X POST http://localhost:3000/circuit-breaker/force-open/razorpay/UPI \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod"
curl -X POST http://localhost:3000/circuit-breaker/force-close/razorpay/UPI \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod"

Retry policy:

curl -H "X-Internal-Api-Key: dev-internal-key-change-in-prod" \
  http://localhost:3000/retry-policy

curl -X PUT http://localhost:3000/retry-policy/amazon \
  -H "Content-Type: application/json" \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod" \
  -d '{ "maxAttempts": 5, "latencyBudgetMs": 15000, "retryOnTimeout": false }'

Metrics API

curl http://localhost:3001/metrics/health
curl http://localhost:3001/metrics/gateways
curl http://localhost:3001/metrics/gateways/cashfree
curl http://localhost:3001/metrics/gateways/cashfree/UPI
curl http://localhost:3001/metrics/history/cashfree

Experiment API

Create an experiment:

curl -X POST http://localhost:3002/experiments \
  -H "Content-Type: application/json" \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod" \
  -d '{
    "name": "Cashfree first for UPI",
    "controlWeight": 0.5,
    "treatmentWeight": 0.5,
    "treatmentConfig": { "gatewayOrderOverride": ["cashfree","razorpay","payu","juspay","hdfc_gateway"] },
    "targetMethods": ["UPI"]
  }'

Experiment operations:

curl http://localhost:3002/experiments
curl "http://localhost:3002/experiments/assign?customerId=cust_001&method=UPI"
curl http://localhost:3002/experiments/{experimentId}
curl http://localhost:3002/experiments/{experimentId}/results
curl -X POST http://localhost:3002/experiments/{experimentId}/pause \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod"
curl -X POST http://localhost:3002/experiments/{experimentId}/complete \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod"

Bandit:

curl http://localhost:3002/bandit/state/UPI
curl "http://localhost:3002/bandit/order/UPI?gateways=cashfree,razorpay,payu,juspay"
curl -X POST http://localhost:3002/bandit/reset/UPI/cashfree \
  -H "X-Internal-Api-Key: dev-internal-key-change-in-prod"

Notes

  • POST /payments is throttled at 100 requests per minute per IP.
  • Idempotency is enforced with the idempotency-key header.
  • Circuit breaker state and hot metrics live in Redis.
  • Payment attempts and completions are published to Kafka.
  • Timeout payments are resolved through RabbitMQ verification.

About

Dummy payment aggregator

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors