UPI Payments & Reconciliation
Telling a failed attempt apart from money actually received.
- Role
- Lead developer
- Status
- Production
- Stack
- Django · React · Bank UPI API · Webhooks
How it fits together
01
Context
Invoices are paid by UPI through a bank’s API. On the customer’s screen, a payment can look failed while the money has in fact arrived, and asking that customer to pay again risks collecting twice.
I led the integration across the Django backend and the React billing interface.
02
Constraints
- Payment notifications arrive from outside the system, so each one has to be authenticated before it is trusted.
- The same event can be delivered more than once and must not change billing state twice.
- An uncertain outcome is not a failure, and the customer should not be pushed to retry until it is resolved.
03
What I built
- A React billing interface on top of Django REST APIs.
- Payment requests created from invoices, with payment status driven by bank webhooks.
- Signature verification on incoming webhooks before any state changes.
- Idempotent event handling, so a repeated notification cannot be applied twice.
- A reconciliation step for uncertain outcomes that runs before a new payment is requested.
04
Tradeoff: Wait briefly instead of retrying immediately
Retrying straight away feels faster to the customer but risks a second collection. Reconciling uncertain outcomes first adds a short wait in rare cases and keeps billing state honest.
05
Outcome
The workflow tells a failed payment attempt apart from money actually received, which lowers the risk of collecting twice or asking for unnecessary retries.