Find the Best Cosmetic Hospitals

Explore trusted cosmetic hospitals and make a confident choice for your transformation.

“Invest in yourself — your confidence is always worth it.”

Explore Cosmetic Hospitals

Start your journey today — compare options in one place.

How to Validate UPI & Paytm Payment QR Codes Programmatically (Step-by-Step Guide

Great question. You can validate “payment QR codes” at two levels:

  1. Decode & structural checks (what you can do locally in code)
  2. Live verification (needs a PSP/gateway API; optional but best-practice)

Below is a practical, copy-pasteable approach you can drop into your stack (PHP/Laravel, plus quick Node/Python snippets).


1) What kinds of payment QR payloads you’ll see

  • UPI intent URI (most common; including Paytm VPAs)
    Example:
    upi://pay?pa=merchant@paytm&pn=Acme+Store&am=123.45&cu=INR&tn=Order+123
  • EMVCo / BharatQR TLV string (starts with digits)
    Example:
    000201010211...5303561540...6304ABCD (ends with CRC “63 04 XXXX”)
  • Wallet/app-specific deep links
    • Paytm: upi://pay?... or paytmmp://... or short URL like https://paytm.me/...
      (Most Paytm merchant QRs are actually UPI with a Paytm VPA such as ...@paytm.)

2) Flow to implement

A. Decode the QR image → get the raw text

  • PHP: khanamiryan/qrcode-detector-decoder (ZXing port)
  • Node: @zxing/library, jsqr
  • Python: pyzbar or opencv + zbarlight

B. Decide the type & validate structurally

  • If it starts with upi://pay → parse as URI, validate required params.
  • If it looks like TLV (digits, starts with “00 02 01 02 11 …” etc.) → parse TLV and verify CRC16-CCITT in tag 63.
  • If it’s app link (paytmmp:// or https://paytm.me/...) → allowlist the scheme/domain and (optionally) resolve server-side to ensure it’s a valid, reachable link owned by Paytm.

C. (Optional but recommended) Live verification

  • You cannot guarantee a VPA exists or is active from the QR alone.
  • Use a PSP/gateway that exposes VPA verification (aka “UPI PSP lookup”) APIs:
    • Razorpay, Cashfree, Paytm for Business, PhonePe, Juspay, etc.
    • Typically you send pa=merchant@bank to an endpoint → they reply if it’s valid and sometimes return the account holder name.

3) Validation rules (UPI intent)

For upi://pay links:

  • Scheme must be upi, host must be pay
  • Required:
    • pa (VPA): ^[a-zA-Z0-9.\-_]{2,}@[a-zA-Z0-9.\-_]{2,}$
    • cu (currency): usually INR
  • Common/optional: pn (payee name), am (amount), tn (note), tr (txn ref), tid, mc (merchant code), orgid, sign (signed intents)
  • If you require fixed amount QRs, ensure am is present and numeric (^\d+(\.\d{1,2})?$).
  • Reject unknown params if you want a strict policy, or allow a whitelist (pa,pn,am,tn,tr,tid,mc,cu,orgid,sign,mode).

4) Validation rules (EMVCo/BharatQR TLV)

  • TLV format: <TAG><LEN><VALUE> repeated, all numeric.
  • CRC is tag 63 with length 04. Compute CRC16-CCITT (0x1021, init 0xFFFF) over the bytes up to but not including the CRC value, then compare with the provided 4-hex checksum.
  • Presence of specific tags depends on the profile (UPI merchant, etc.). For basic acceptance, verifying a correct TLV structure + CRC already weeds out malformed codes.

5) PHP (Laravel-friendly) samples

A) Decode the QR

composer require khanamiryan/qrcode-detector-decoder
Code language: JavaScript (javascript)
use Zxing\QrReader;

function decodeQrImage(string $path): ?string {
    $qrcode = new QrReader($path); // supports JPG/PNG, etc.
    $text = $qrcode->text();
    return $text !== '' ? $text : null;
}
Code language: PHP (php)

B) Validate UPI intent URI

function isValidUpiUri(string $s): bool {
    // quick precheck
    if (!str_starts_with($s, 'upi://pay')) return false;

    $parts = parse_url($s);
    if (($parts['scheme'] ?? '') !== 'upi') return false;
    if (($parts['host'] ?? '') !== 'pay') return false;

    parse_str($parts['query'] ?? '', $q);

    // Required fields
    if (empty($q['pa']) || empty($q['cu'])) return false;

    // VPA pattern (basic)
    if (!preg_match('/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+$/', $q['pa'])) return false;

    // Currency
    if (strtoupper($q['cu']) !== 'INR') return false;

    // Amount (if present)
    if (isset($q['am']) && !preg_match('/^\d+(\.\d{1,2})?$/', $q['am'])) return false;

    // Optional: enforce allowlist of params
    $allowed = ['pa','pn','am','tn','tr','tid','mc','cu','orgid','sign','mode'];
    foreach (array_keys($q) as $k) {
        if (!in_array($k, $allowed, true)) return false; // tighten as you prefer
    }

    return true;
}
Code language: PHP (php)

C) Detect & validate EMVCo/BharatQR TLV (CRC check)

function looksLikeTlv(string $s): bool {
    return ctype_digit($s) && strlen($s) >= 8; // rough heuristic
}

function parseTlv(string $s): array {
    $i = 0; $fields = [];
    while ($i + 4 <= strlen($s)) {
        $tag = substr($s, $i, 2); $i += 2;
        $len = intval(substr($s, $i, 2)); $i += 2;
        $val = substr($s, $i, $len); $i += $len;
        $fields[$tag] = $val;
        if ($i > strlen($s)) break;
    }
    return $fields;
}

// CRC16-CCITT (0x1021), init 0xFFFF, no xorout
function crc16_ccitt(string $data): string {
    $crc = 0xFFFF;
    $bytes = unpack('C*', $data);
    foreach ($bytes as $b) {
        $crc ^= ($b << 8);
        for ($i=0; $i<8; $i++) {
            $crc = ($crc & 0x8000) ? (($crc << 1) ^ 0x1021) : ($crc << 1);
            $crc &= 0xFFFF;
        }
    }
    return strtoupper(str_pad(dechex($crc), 4, '0', STR_PAD_LEFT));
}

function isValidEmvcoWithCrc(string $s): bool {
    if (!looksLikeTlv($s)) return false;
    // Find CRC tag "63"
    $pos = strpos($s, '63');
    if ($pos === false) return false;

    // The CRC field is "63 04 XXXX"
    $crcFieldStart = $pos;
    $dataForCrc = substr($s, 0, $crcFieldStart + 4); // include "63" + "04", exclude the 4 hex CRC value
    $providedCrc = substr($s, $crcFieldStart + 4, 4);
    // Compute CRC over the raw bytes, which here are ASCII digits; this simplified check is
    // acceptable for gating malformed QRs. For production, convert TLV to actual bytes.
    $calc = crc16_ccitt($dataForCrc);
    return strtoupper($providedCrc) === $calc;
}
Code language: PHP (php)

D) Putting it together

function validatePaymentQr(string $imagePath): array {
    $raw = decodeQrImage($imagePath);
    if ($raw === null) {
        return ['ok' => false, 'type' => null, 'reason' => 'QR not readable'];
    }

    // UPI intent?
    if (isValidUpiUri($raw)) {
        return ['ok' => true, 'type' => 'upi-intent', 'payload' => $raw];
    }

    // EMVCo TLV with valid CRC?
    if (looksLikeTlv($raw) && isValidEmvcoWithCrc($raw)) {
        return ['ok' => true, 'type' => 'emvco-tlv', 'payload' => $raw];
    }

    // Paytm deep links (allowlist)
    if (str_starts_with($raw, 'paytmmp://') || str_starts_with($raw, 'https://paytm.me/')) {
        return ['ok' => true, 'type' => 'paytm-link', 'payload' => $raw];
    }

    return ['ok' => false, 'type' => null, 'reason' => 'Unknown or malformed payment QR'];
}
Code language: PHP (php)

6) Node & Python quickies

Node (decode + basic UPI check)

npm i @zxing/library
Code language: CSS (css)
const { BrowserQRCodeReader } = require('@zxing/library');

function isValidUpiUri(s) {
  try {
    const u = new URL(s);
    if (u.protocol !== 'upi:' || u.hostname !== 'pay') return false;
    const pa = u.searchParams.get('pa');
    const cu = u.searchParams.get('cu');
    if (!pa || !/^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+$/.test(pa)) return false;
    if ((cu || '').toUpperCase() !== 'INR') return false;
    const am = u.searchParams.get('am');
    if (am && !/^\d+(\.\d{1,2})?$/.test(am)) return false;
    return true;
  } catch { return false; }
}
Code language: JavaScript (javascript)

Python (pyzbar)

pip install pyzbar pillow
from pyzbar.pyzbar import decode
from PIL import Image
from urllib.parse import urlparse, parse_qs

def is_valid_upi_uri(s: str) -> bool:
    try:
        u = urlparse(s)
        if u.scheme != 'upi' or u.netloc != 'pay': return False
        q = {k:v[0] for k,v in parse_qs(u.query).items()}
        pa = q.get('pa'); cu = (q.get('cu') or '').upper()
        if not pa or not cu: return False
        import re
        if not re.match(r'^[A-Za-z0-9._-]+@[A-Za-z0-9._-]+$', pa): return False
        if cu != 'INR': return False
        am = q.get('am')
        if am and not re.match(r'^\d+(\.\d{1,2})?$', am): return False
        return True
    except:
        return False

def decode_qr(path: str) -> str | None:
    d = decode(Image.open(path))
    return d[0].data.decode() if d else None
Code language: JavaScript (javascript)

7) Security & product tips

  • Don’t auto-trust a decoded string just because it parses. Enforce strict allowlists (schemes, hosts, param names).
  • Sanitize & log the original payload for audit/debug.
  • Optionally: if you only accept merchant (fixed-amount) QRs, reject those missing am.
  • Live verification: integrate a PSP’s VPA validation API to confirm the VPA exists and is active before saving the QR to your database.
  • Rate-limit and virus-scan uploads; store images in a safe bucket; validate mime & dimensions.
  • UX: after upload, show the decoded details (VPA, name, amount) back to the user for confirmation.

If you want something that is widely used in production and proven to work across banks, UPI apps, and wallets like Paytm, then the industry practice is:

✅ Most Widely Accepted Implementation

  • UPI intent URI validation + (optionally) EMVCo TLV validation + VPA verification via PSP APIs.
  • Almost all real-world QR codes in India today (Paytm, PhonePe, GPay, BHIM, BharatPe, banks) follow the NPCI UPI QR Standard which is based on EMVCo QR but typically presented as a upi://pay?... intent.
  • So, in practice:
    1. Decode the QR → you’ll almost always get upi://pay?...
    2. Check required fields (pa, cu) + format (VPA regex, currency = INR)
    3. (Optional but best) Call a PSP/aggregator API to confirm the VPA is valid

🚀 Why this is the standard

  • Banks & UPI apps interoperate because NPCI mandated UPI intent URIs and EMVCo QR as the backbone.
  • Paytm merchant QRs are just UPI QR with VPAs ending in @paytm.
  • PhonePe, GPay, Amazon Pay, BHIM → all issue UPI QR codes in the same format.
  • Even BharatQR (card+UPI) is EMVCo TLV under the hood, but most merchant-facing QRs still decode to a UPI intent.

🔑 So in your system

  • Implement UPI intent URI parsing & validation (like the PHP/Python snippets I showed).
  • Treat Paytm links as a special-case allowlist (if you want to support their paytmmp:// or https://paytm.me/...).
  • If you want bulletproof validation (to avoid fake VPAs), plug into a PSP’s VPA verification API (Razorpay, Paytm for Business, Cashfree, PhonePe, Juspay all provide it).

👉 In short:
Decode QR → Validate UPI intent URI → (optional) Live VPA verification API.
That’s the widely accepted, real-world working approach used by payment apps and fintech companies.


Find Trusted Cardiac Hospitals

Compare heart hospitals by city and services — all in one place.

Explore Hospitals
I'm Rajesh Kumar, a DevOps, SRE, DevSecOps, Cloud, and Platform Engineering expert passionate about sharing practical knowledge, real-world experiences, and industry best practices. I have worked at Cotocus and regularly write about technology, travel, investing, health, product reviews, and digital marketing through my various platforms. I publish technical articles at DevOps School, travel stories at Holiday Landmark, stock market insights at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow, and SEO and digital marketing strategies at Wizbrand.

Related Posts

Team Foundation Server 2015 Installation Guide (SQL Express + Update 3)

Install Team Foundation Server 2015 using the original Microsoft download paths, with clear guidance for online, offline, and ISO-based setup. This revised guide also includes an installation workflow, command example, and practical pitfalls to avoid.

Read More

How to Access a Running Docker Container Shell: docker exec vs docker attach

Need a shell inside a running container that is already in detached mode? This updated guide shows the correct workflow, compares docker exec and docker attach, and explains when SSH does and does not make sense.

Read More

Host Your Own NuGet Feed: Local Source vs NuGet.Server vs NuGetGallery

Learn how to host a private NuGet feed using three practical approaches: a local folder source, NuGet.Server, and NuGetGallery. This revised guide includes setup steps, command examples, and a workflow diagram to help you choose the right model.

Read More

Publish NuGet Packages to NuGet.org: CLI Steps, Workflow, and CI Example

Learn how to publish a NuGet package to NuGet.org using either nuget.exe or the dotnet CLI. This revised guide includes a workflow diagram, command comparison, and a CI-friendly example.

Read More

How to Create NuGet Packages with nuget.exe (Assembly, .nuspec, and .csproj Methods)

Learn three practical ways to build NuGet packages using nuget.exe: from an existing assembly, from a convention-based folder layout, or directly from a Visual Studio project. This guide includes commands, folder conventions, token behavior, and common pitfalls.

Read More

Virtualization in DevOps: Using Vagrant, Docker, and Cloud Platforms Together

This guide explains how VM-based workflows, containers, and cloud platforms complement each other in DevOps. It includes a practical workflow, trade-off table, and command examples for day-to-day use.

Read More
Subscribe
Notify of
guest
1 Comment
Newest
Oldest Most Voted
Skylar Bennett
Skylar Bennett
10 months ago

This guide is extremely useful — by explaining both local (decode + structural) checks and optional live‑verification via PSP/gateway APIs, it shows a practical and secure way to validate UPI/Paytm QR codes in real apps. I really like how the article gives copy‑paste ready code snippets (PHP, Node, Python etc.), making even complex validation accessible to developers. Emphasising live verification — beyond just local parsing — helps avoid fraud or faulty QR‑codes being used, which is especially important for businesses & e‑commerce sites. Overall, this post is a must‑read for anyone planning to integrate QR‑based UPI payments in their system — it blends theory and real‑world code for reliable, secure payment flows.

1
0
Would love your thoughts, please comment.x
()
x