1. Payment Orchestrator
Role: Central brain for initiating and managing payments.
Responsibilities:
Accepts a payment initiation request (e.g., {amount, recipient, client_ref}).
Generates a payment_id (UUID).
Calls Bank Client to send payment to partner bank’s API.
Tracks lifecycle states: initiated → processing → completed/returned.
Handles retries (e.g., if partner bank API is down).
Persists state to database (payments table).
2. Bank Client (httpx)
Role: Abstraction over the partner bank’s Payments API.
Responsibilities:
Handle auth (OAuth2, mTLS, API key depending on Chase/partner requirements).
Send payment initiation requests via HTTP.
Map responses to internal schema (payment_id, status, bank_ref).
Include resiliency: timeout, retries, circuit breaker (httpx supports retries).
Example:
async with httpx.AsyncClient() as client:
resp = await client.post(
bank_url + "/payments",
headers={"Authorization": f"Bearer {token}"},
json=payload
)
3. Webhook Receiver (FastAPI)
Role: Handle async notifications from bank/Zelle (e.g., payment completed, returned).
Responsibilities:
Expose /webhook/payments.
Verify auth/signature from partner bank.
Parse payload and update local DB (payment_id, status, timestamps).
Ack quickly with 200 OK.
Queue heavy processing (e.g., ERP updates) to async workers.
Example (FastAPI):
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook/payments")
async def payment_webhook(req: Request):
body = await req.json()
payment_id = body["payment_id"]
status = body["status"]
# Update DB with new status
return {"ok": True}
4. Observability
Logs:
Every log line includes payment_id and client_ref.
Example:
{"level":"info","payment_id":"1234","client_ref":"INV-1002","msg":"Payment initiated"}
Metrics (Prometheus / OpenTelemetry):
payment_status_total{status="initiated"}
payment_status_total{status="completed"}
payment_status_total{status="returned"}
payment_retry_count{payment_id="1234"}
payment_webhook_latency_seconds{}
Tracing (OpenTelemetry + Jaeger/Tempo):
Trace spans: PaymentOrchestrator → BankClient → WebhookReceiver.
Propagate payment_id + client_ref as trace attributes.
🔹 Payment Lifecycle Example
Initiation:
Client → Orchestrator → Bank Client → Bank API → initiated.
Log: {"payment_id":"123","status":"initiated"}
Webhook:
Bank → Webhook Receiver → DB update → Orchestrator marks completed.
Log: {"payment_id":"123","status":"completed"}
Metrics:
payment_status_total{status="initiated"}++
Later payment_status_total{status="completed"}++
Webhook latency recorded.
🔹 Suggested Tech Stack
FastAPI → Orchestrator + Webhook service.
httpx → Bank client.
SQLAlchemy / Postgres → Persistence for payments.
Celery / RQ → For async retries & background jobs.
Prometheus + Grafana → Metrics.
OpenTelemetry + Jaeger → Tracing.
Structured JSON logging → For correlation.