Feature #3933
closedPayment Initiation for Zelle Payment
Description
General:
We are developing Payment Initiation feature for sending and receiving payments through a Zelle-connected bank partner behind a Payments API.
The following components are planned as part of this feature.
Payment Orchestrator
Bank Client (httpx)
Webhook Receiver (FastAPI)
Observability
> payment_id and client_ref across logs.
> Status metrics (initiated, completed, returned, retry counts, webhook latency).
Subtasks
Related issues
Updated by Yogeesh sai 3 months ago
- Estimated time set to 192:00 h
Payment Orchestrator Implementation: 2 days
Bank Client (httpx) Setup: 1 day
Webhook Receiver (FastAPI): 1.5 days
Observability (logging + metrics): 1.5 days
Integration & Testing (end-to-end flow): 2 days
Total Estimated Time:8 days (full cycle from development → testing → ready for QA).
Updated by Yogeesh sai 3 months ago
- % Done changed from 0 to 40
Customer → POS → Generates TX1234 & shows Zelle info
↘
Customer sends Zelle payment
↘
Plaid polls → Bank → New transaction (Zelle, $25.00, TX1234)
↘
Backend checks → Match found → POS: "Payment Confirmed"
Updated by Yogeesh sai 3 months ago
Client App → Payments API → Payment Orchestrator
↓
Bank Client (httpx → Bank Partner API)
↓
Bank Partner (Zelle rails)
↓
Counterparty Bank/Zelle
↓
Webhook Receiver (FastAPI)
↓
Payment Orchestrator (update status)
↓
Observability (metrics + logs)
Updated by Yogeesh sai 2 months ago
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.