All work

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

A simplified illustration. The bank, its endpoints and all transaction data are left out.
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.