TL;DR: For India-only merchants, UPI is free, instant, and covers 95%+ of smartphone wallets. BharatQR is a superset that also accepts RuPay, Visa, and Mastercard cards. For global apps, combine with other regional payment QRs.
The landscape in one paragraph
India has three overlapping payment-QR formats, all interoperable at the scan point:
- UPI Intent — the
upi://payURI scheme. Opens any UPI app on the phone (PhonePe, Google Pay, Paytm, BHIM, etc.) - BharatQR — an EMVCo TLV-encoded QR string that includes UPI and RuPay / Visa / Mastercard rails
- UPI QR (static / dynamic) — simply a QR image encoding a UPI intent URI
Format comparison
| Mode | Example / structure | Use when |
|---|---|---|
| UPI Intent | upi://pay?pa=merchant@okhdfc&pn=Shop&am=499.00&tn=Order-1234 | Peer-to-peer and merchant pay-to-VPA flows |
| BharatQR (EMVCo) | EMVCo TLV with GUID A000000524010105 (UPI) or A000000727 (RuPay) | Cards + UPI interoperable merchant QR |
| Static UPI QR | upi://pay?pa=shop@okhdfc&pn=Shop&cu=INR | Fixed-amount or variable-amount shelf QR |
| Dynamic UPI QR | Generated per-transaction with unique transactionRef | Billing counters, ticketing, subscription |
The UPI intent URI — anatomy
A UPI intent is a URI with query parameters. The spec is short and strict:
upi://pay?pa=shop@okhdfc&pn=My%20Shop&am=499.00&cu=INR&tn=Order%201234Parameters (RFC-style, all case-sensitive):
pa— payee address (the VPA / UPI ID, required)pn— payee name (required; URL-encoded)am— amount (optional; fixed if present, user-entered if absent)cu— currency (must beINRfor domestic UPI)tn— transaction note (optional; shown in the UPI app)tr— transaction reference (required for dynamic / merchant QRs)mc— merchant code (MCC; required for merchants)
Escape your parameters
Non-ASCII characters, spaces, ampersands, and special chars must be percent-encoded. The #1 reason a QR “doesn't work” is an unescaped space in pn or tn.
BharatQR — when you need cards too
BharatQR is the common merchant QR promoted by RBI and NPCI. Unlike a pure UPI intent, it uses the EMVCo TLV format — the same format as PIX (Brazil), PromptPay (Thailand), and SGQR (Singapore). This means a single QR string works in multiple payment apps via different rails.
Structure (simplified):
00020101021126XX0016A000000524010105020212
52044900530...58025954...62...6304<crc16>Each TLV record is three bytes + payload:
- Tag (2 digits) — what kind of data this is
- Length (2 digits) — how many bytes follow
- Value (that many bytes) — the actual data
Common tags: 00=payload format, 01=point of init method, 26–51=merchant account info (nested), 52=MCC, 53=currency, 54=amount, 58=country, 59=merchant name, 60=city, 63=CRC.
The whole string is terminated with a CRC-16/CCITT-FALSE checksum. Get that checksum wrong, and every EMVCo parser will reject your QR. This is by far the most common integration bug.
MDR — what you actually pay
- UPI peer-to-merchant (P2M), debit UPI, amount ≤ ₹2,000: 0% MDR — regulated by NPCI
- UPI on RuPay credit card: ~1.1% (tiered, capped by NPCI/RBI from time to time)
- BharatQR with Visa/Mastercard credit: Normal card interchange (1.9–2.5%+ GST)
- BharatQR with RuPay debit: 0.4–0.9% typically
For a chai stall or kirana store doing ₹20 lakh/month in UPI debit transactions, you pay zero fees. That's why UPI has eaten the world.
Static vs dynamic — for merchants
Print a static QR once and stick it at your counter. Customer scans → types in the amount → pays. This is the ₹5 sticker you see on every chai stall and auto rickshaw. It works forever and costs nothing.
Generate a dynamic QR at the point of sale with amount + transaction reference. The customer scans and just confirms. Required if you want automated reconciliation and fast checkout. Most POS and billing software generate these on demand.
RBI / regulatory context
- KYC: Merchants above ₹5 lakh/year in receipts must complete UPI merchant KYC
- MDR waiver: Regulated under the Finance Act 2019 as clarified by CBDT; applies to UPI/RuPay debit for P2M
- Transaction limits: Per-transaction ₹1 lakh (retail), ₹2 lakh for high-value categories, ₹5 lakh for tax/insurance. Daily caps set by issuer bank.
- Cross-border: UPI now works in Singapore, UAE, Sri Lanka, Mauritius, and more — same QR format, different currency conversion layer
Common integration mistakes
Missing URL-encoding on merchant name
Always percent-encode `pn` and `tn`.
Hard-coded amount without `cu=INR`
Some UPI apps reject amount without explicit currency.
Wrong CRC-16 in BharatQR
Use a certified library — compute AFTER adding the length-04 CRC placeholder.
Same `tr` reused for different payments
Transaction reference must be unique per attempt; causes duplicate-detection rejections.
Using static QR for amounts > ₹10,000
RBI guidance — dynamic QRs strongly recommended above that threshold.
What QRMax provides
- Both UPI intent and BharatQR (EMVCo TLV) encoders
- Validated CRC-16/CCITT-FALSE implementation
- Static + dynamic UPI QR generation with reconciliation webhooks
- Cross-payment-format coverage — UPI, BharatQR, PIX, PromptPay, SGQR, Bitcoin BIP21, Lightning
- Tested with PhonePe, Google Pay, Paytm, BHIM, CRED, Amazon Pay
Next steps
See UPI QR type for the live generator, or read about BharatQR. For merchant-platform integration see our finance workflow.