RizTech Academy logo
RizTech Academy
PaymentsLesson 1 of 425 min

How an online payment actually works

Before writing any code, it is worth knowing what actually happens when somebody taps "Pay ₹230" — because almost every mistake in payment code comes from a wrong mental model of that.

Four parties, not two

Party Who they are
Customer has a card, a bank account or a UPI app
Issuing bank the customer's bank. Decides whether to approve
Acquiring bank the shop's bank. Receives the money
Gateway the software between you and all of that

You talk to the gateway. The gateway talks to the rest. Razorpay, Stripe, PayU, Cashfree and the others are all in that middle box, and the shape of the integration is the same whichever you pick — which is why this module builds against a seam rather than an SDK.

The money does not move when the customer taps

This is the single most important thing, and the thing most tutorials skip.

Authorisation is the issuing bank saying "yes, this card is good for ₹230, and I am holding it". No money has moved. It is a promise with a time limit.

Capture is the shop saying "take it". Now the money moves — and, in India, lands in the shop's account a day or two later after the gateway settles.

Many gateways auto-capture for a normal online order, which is why it looks like one step. It is not one step, and the distinction becomes real the moment you sell something that might be out of stock when you go to pack it.

The third state matters too: void is cancelling an authorisation before capture, which costs nothing. A refund is moving captured money back, which costs a fee and takes days. If you can void instead of refunding, void.

The flow, honestly

1  customer chooses "pay online" at checkout
2  your API creates the order            PENDING_PAYMENT, stock held
3  your API asks the gateway to open a payment for ₹230
4  gateway returns a reference           pay_9f3a…
5  browser hands the customer to the gateway's own page or widget
6  customer authenticates                UPI PIN, OTP, bank page
7  gateway settles with the banks
8a gateway POSTs a webhook to your API   ← this is the truth
8b gateway redirects the browser back    ← this is only navigation
9  your API marks the order PLACED

Steps 8a and 8b are the whole lesson of this module. They are different events, they arrive in either order, and only one of them can be believed.

Why the redirect cannot be trusted

The gateway sends the customer back to a URL like /checkout/success?order=KS-20260927-7F3K2A. Treating that arrival as proof of payment is one line of code and it works every time you test it.

It is also a URL, and anybody can visit a URL. A customer who notices that pattern can type it with a different order number and the shop ships groceries it was never paid for.

Set aside malice, though, because the honest failures are more common:

  • The customer closes the tab on the bank's page after paying.
  • Their train goes into a tunnel between the bank and your site.
  • The UPI app takes over, the payment succeeds, and the browser never comes back.
  • The browser comes back before the gateway has finished settling, so at the moment of the redirect the payment genuinely is not complete yet.

In every one of those the money moved and the browser never told you. A system that learns about payments from the browser loses orders it was paid for, which is worse than the fraud case because nobody notices.

The webhook is a server-to-server POST from the gateway's machines. It is retried if you are down. It is signed. It is the truth.

Never let the browser say what to charge

const providerRef = this.gateway.createIntent(order.orderNumber, order.totalPaise);

order.totalPaise, read from the database inside the request. Not a number from the request body.

This is the same discipline as module 12's checkout, and it is worth stating as a rule: the client says what it wants; the server says what it costs. A client that can send an amount can send 1.

The amount that goes back to the browser in the intent is for display only. What the gateway will collect is what your server told it.

The three sentences of PCI compliance

Card data is regulated. The rules are long; the part that matters to an application developer is short:

Never let card numbers touch your servers. Use the gateway's own hosted page or their iframe widget. The customer types their card into the gateway's origin, not yours.

Do that and your compliance burden collapses to a short self-assessment questionnaire. Build your own card form and you are in scope for an audit that costs more than the shop makes in a year.

It is also why this course's PayForm is a stand-in with two buttons rather than a card form. There is no exercise where you collect a card number, because there is no situation in which you should.

Test mode is a real mode

Every gateway gives you two sets of keys. Test keys hit a sandbox: published card numbers that always succeed, always fail, or always require OTP. No money moves.

Two things to know about it:

Keys are environment configuration, never committed. A live key in a git history is a live key, and rotating it means a conversation with the provider.

Test mode does not exercise everything. Settlement timing, real bank declines, and the customer who pays twice by accident are all things you first meet in production. Plan for them in code rather than expecting the sandbox to show them to you.

What "idempotent" means here, and why it is not optional

A gateway will send the same webhook more than once. This is documented behaviour, not a fault: if your endpoint is slow, times out, or returns a 500, they retry — because the alternative is losing payment notifications.

So the handler must be safe to run twice. Not "unlikely to be run twice" — safe. If handling a duplicate would place the order a second time, or refund twice, or send two texts, that will happen, and it will happen on the busiest day.

The next lesson builds that in, and it comes down to one check: has this payment already left the PENDING state?

Money is still an integer

Decision 0001, restated because this is the module where it would hurt most. Every amount in this course is an integer count of paise. Gateways work the same way — Razorpay and Stripe both take amounts in the smallest currency unit — which is a strong hint that it is the right representation.

285.15 * 100 is 285.14999999999998 in JavaScript. Round, never truncate, and keep floats out of the path entirely.

Check your work

The four parties: customer, issuing bank, acquiring bank and gateway. You talk only to the gateway.

Authorisation versus capture: authorisation is a hold with a time limit; capture moves the money. Voiding an authorisation is free, refunding a capture is not.

Why the redirect cannot be trusted: it is a URL anybody can visit, and it is missed entirely when a tab closes, a connection drops or a UPI app takes over.

Why the webhook can be: it is a signed server-to-server POST, retried if you are down.

Why the amount comes from the database: a client that can send an amount can send 1.

The whole of PCI for an application developer: never let card numbers reach your servers; use the gateway's hosted page or iframe.

Why webhooks must be idempotent: gateways retry by design, so duplicates are certain rather than unlikely.

Why amounts are integer paise: floats lose money, and the gateways agree.

Practice

  1. Read the webhook documentation of one Indian gateway — Razorpay, Cashfree or PayU. Find how they sign, which header carries it, and what they say about retries.
  2. Find, in that documentation, the sentence telling you not to rely on the redirect. Note how easy it was to miss.
  3. Write down what should happen if a customer pays and closes the tab before the redirect. Then check your answer against the flow above.
  4. Work out the fee on a ₹230 order at 2% plus GST. Decide whether the shop should offer online payment at all on small orders.
  5. List every place in this application that would have to change to swap the gateway. If it is more than one file, the seam is wrong.
  6. In node, evaluate 285.15 * 100 and then Math.round(285.15 * 100).
  7. Explain to somebody else why "the payment page said success" is not proof of payment. If they push back, you have not got the model yet.
  8. Find your gateway's test card numbers. Note which ones fail, and why having a guaranteed failure is useful.
  9. Sketch what happens if the webhook arrives before the browser redirect. Confirm your design does not depend on the order.
  10. Decide where the shop's gateway secret will live in production, and who can read it.

Next: the code — putting a gateway behind a seam.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship