Why amounts differ by paise
Two customers buy the same ₹499 mug within a minute of each other. One is asked for ₹499.00 and the other for ₹498.99. This page is why, what it costs, and how to put it on an invoice so your books balance to the paisa.
The constraint
Section titled “The constraint”A UPI deep link lets the payer set a transaction reference (tr) and a note
(tn). Neither is ever returned to the payee. We verified that twice, from
opposite directions:
- 665 real HDFC credit SMS read off a device carried no reference and no note, in any of the three message formats HDFC has used since 2019.
- A live forwarded HDFC credit email is richer — it even adds the payer’s name — and still carries neither.
What your bank does tell you is: the amount, the payer’s UPI handle, sometimes the payer’s name, a 12-digit reference number generated by the banking system, and a timestamp.
Of those, the amount is the only field anybody controls before the customer pays. The reference number is minted by the banks at the moment of payment and nobody can know it in advance. So:
The amount is the order identity.
Everything else in this product follows from that one sentence.
Which means two orders at the same price need different amounts
Section titled “Which means two orders at the same price need different amounts”If two orders are both waiting for ₹499.00 and ₹499.00 lands, there is no honest way to say which one it was for. We do not pick the likelier one. We park it and ask you — which is correct, and also miserable if it happens often.
So when you reserve an amount for a customer, we hand out the exact list price if it is free, and otherwise the closest free amount below it.
₹499.00 ← held by someone else₹498.99 ← free. this customer gets this one.₹498.98…₹498.01 ← the floor, with the default 99-paise bandAlways down, never up
Section titled “Always down, never up”paid_paise is never more than list_paise. discount_paise is never
negative. There is no configuration that makes it work the other way, and there
never will be, for three reasons:
- A customer can only be pleased by it. Being charged one paisa more than the price you advertised is a price you did not agree to, however small. Being charged one paisa less is a rounding in your favour.
- It bounds your exposure and nothing else. The worst case is the width of your discount band — 99 paise by default — per order. There is no case where a customer is asked for more than the number on your page.
- Your invoice can show it. A discount is an ordinary line on an invoice — never a surcharge that appeared without the customer agreeing to it, which no invoice can absorb as cleanly.
What it actually costs you
Section titled “What it actually costs you”The walk starts at the list price, so the first customer at a given price almost always pays it exactly. The discount only grows as far down as the number of orders competing for that same price at that same moment.
At 100 orders an hour, roughly eight orders are open at once, so most customers never see more than a few paise off; at 1,000 an hour it is roughly 83. Those are planning numbers, not a hard ceiling — the table on Reservations has the full picture, including what it does and doesn’t account for, and what a 2% gateway would have cost on the same order.
How to invoice it
Section titled “How to invoice it”Three fields, and they are all in every API response and every webhook:
| field | meaning |
|---|---|
list_paise |
The price you charged. |
discount_paise |
What you gave up. list_paise - paid_paise. Zero most of the time. |
paid_paise |
What actually landed in your account. |
Put the discount on the invoice as a line item. Then the invoice total equals the money you received, exactly:
Blue mug 499.00Discount -0.01──────────────────────────────────────────────────Total 498.99₹498.99 is paid_paise. Your bank statement says ₹498.99. Your invoice says
₹498.99. Nothing to reconcile.
Rendering it, given the order object from GET /v1/intents/{id}:
const rupees = (paise) => (paise / 100).toFixed(2);
const lines = [ { label: order.description ?? 'Order', amount: rupees(order.list_paise) },];if (order.discount_paise > 0) { lines.push({ label: 'Discount', amount: `-${rupees(order.discount_paise)}` });}const total = rupees(order.paid_paise); // always equals what your bank receivedIf you are GST-registered: this is a discount shown on the face of the invoice at the time of supply, which is the ordinary treatment for any trade discount — it reduces the taxable value, and your accountant will recognise it as the same thing they already handle for a ₹50 festive discount. The only unusual thing about it is how small it is. We are not your tax advisers; if the amount is material to you, it is not, and if it is not material, it is still worth asking once.
A word of warning on rounding: keep the arithmetic in integer paise until the
last step. 499 * 100 is an integer; 4.99 * 100 is not reliably 499 in
floating point, and a half-paisa error is exactly the kind of thing that makes
a payment unconfirmable.
What to tell the customer
Section titled “What to tell the customer”Almost nothing. Our hosted pay page shows one line next to a discounted amount:
The small discount tells us which order you paid for.
That is enough. Nobody has ever needed more than that, and explaining the correlation problem to a buyer on a checkout is not a good use of their attention.
Three things you must not do
Section titled “Three things you must not do”Do not pick your own amount. An amount we did not reserve is an amount
nobody is holding for you. When the money lands it will not match an open
reservation and it will be parked for you to place by hand. If reserve
returns a 503 with pool_exhausted, retry it; do not improvise.
Do not round it. Not up, not down, not to the nearest rupee. ₹498.99 is the identity of that order. ₹499.00 is a different order, or nobody’s.
Do not ask the customer to type it. The one place a customer can enter an amount by hand is the copy-the-UPI-ID fallback, which exists because iPhone has no system-wide UPI chooser, and which is the single largest source of parked credits we have. See Limits.
If the discount is unacceptable to you
Section titled “If the discount is unacceptable to you”It is, for some businesses. If you process enough volume that the band gets deep, or you simply cannot vary a price at all, the answer is not a wider band — it is a merchant VPA from your own bank. A bank merchant channel returns your order reference to you directly and removes amount variation entirely. Money still never touches us. That feed is not built yet; it is the next thing on the list.