npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hussainpithawala/emv-merchant-qr

v1.0.0

Published

EMV® QR Code encoder/decoder for Merchant-Presented Mode v1.0 — Bharat QR / NPCI compatible. Zero dependencies.

Readme

@hussainpithawala/emv-merchant-qr

npm version CI License: MIT Zero Dependencies

TypeScript encoder/decoder for EMV® QR Code payloads — Merchant-Presented Mode v1.0.
Fully compatible with Bharat QR / NPCI payment infrastructure (UPI, RuPay).

This is the TypeScript port of emv-merchant-qr-lib (Go).

Features

  • 🔍 Decode any EMV/Bharat QR string into a typed Payload object
  • ✍️ Encode a structured payload back to a valid QR Code string
  • 🔐 CRC16-CCITT validation on decode; automatic computation on encode
  • 💳 Multi-network — primitive (IDs 0225) and template (IDs 2651) MAIs
  • 🇮🇳 Bharat QR / NPCI — UPI VPA + RuPay credentials in a single QR
  • 💰 Tip & convenience fees — fixed, percentage, and consumer-prompted tip
  • 🗂️ Additional Data Fields — bill number, loyalty, store label, and more
  • 🌐 Alternate language template for Hindi and other regional display names
  • 🔧 Unreserved Templates (IDs 8099) for NPCI proprietary data
  • Zero runtime dependencies — pure TypeScript, ships as CJS + ESM

Installation

npm install @hussainpithawala/emv-merchant-qr
# or
yarn add @hussainpithawala/emv-merchant-qr

Requires Node.js 18+.

Quick Start

import { newPayload, encode, decode, PROMPT_VALUE } from '@hussainpithawala/emv-merchant-qr';

// Build a Bharat QR payload for a Mumbai kirana store
const p = newPayload();
p.addTemplateMerchantAccount('26', 'A000000524', { id: '01', value: 'sharma.kirana@icici' });
p.merchantCategoryCode = '5411';  // Grocery Stores
p.transactionCurrency  = '356';   // INR
p.countryCode          = 'IN';
p.merchantName         = 'Sharma Kirana Store';
p.merchantCity         = 'Mumbai';

const raw = encode(p);         // → "0002010226..."
const decoded = decode(raw);   // throws EMVQRCRCMismatchError if corrupted

Usage Examples

Static Bharat QR (printed sticker)

No amount embedded — the consumer enters it in their UPI app.

const p = newPayload();
p.addTemplateMerchantAccount('26', 'A000000524', { id: '01', value: 'merchant@upi' });
p.merchantCategoryCode = '5411';
p.transactionCurrency  = '356';
p.countryCode          = 'IN';
p.merchantName         = 'My Store';
p.merchantCity         = 'Mumbai';

const qr = encode(p); // print or display this string as a QR code

Dynamic QR (per-transaction, amount locked)

p.transactionAmount = '450.00'; // consumer cannot alter this
const qr = encode(p);

Full Bharat QR — UPI + RuPay

p.addTemplateMerchantAccount('26', 'A000000524', { id: '01', value: 'merchant@icici' }); // UPI
p.addTemplateMerchantAccount('27', 'A000000524', { id: '01', value: '4403847000746908' }); // RuPay

const d = decode(encode(p));
console.log(d.hasMultipleNetworks()); // true — consumer app offers network choice

Fixed Convenience Fee (restaurant packaging)

p.transactionAmount = '800';
p.setFixedConvenienceFee('25');  // ₹25 packaging fee

const d = decode(encode(p));
console.log(d.totalAmount()); // 825

Percentage Convenience Fee (electricity board)

p.transactionAmount = '5000';
p.setPercentageConvenienceFee('1.50'); // 1.50% surcharge

const d = decode(encode(p));
console.log(d.totalAmount()); // 5075

Consumer-Prompted Tip

p.transactionAmount = '1200';
p.setPromptForTip(); // consumer app shows "Add a tip?" — must allow 0 tip

const d = decode(encode(p));
console.log(d.tipOrConvenienceIndicator); // "01"

Additional Data Fields (loyalty, bill number, etc.)

p.setAdditionalData((adf) => {
  adf.billNumber    = 'INV2024-001';     // GST invoice number
  adf.loyaltyNumber = PROMPT_VALUE;      // "***" → consumer app prompts for loyalty card
  adf.storeLabel    = 'Connaught Place'; // branch label
});

const d = decode(encode(p));
console.log(d.loyaltyNumberRequired()); // true

Hindi Alternate Language Name

p.merchantName = 'Sharma Kirana Store'; // English (required)
p.setLanguageTemplate('hi', 'Sharma Kiraane', ''); // Hindi name

const d = decode(encode(p));
console.log(d.preferredMerchantName('hi')); // "Sharma Kiraane"
console.log(d.preferredMerchantName('en')); // "Sharma Kirana Store"

Error Handling

import {
  decode,
  EMVQRCRCMismatchError,
  EMVQRInvalidTLVError,
  EMVQRMissingRequiredError,
} from '@hussainpithawala/emv-merchant-qr';

try {
  const p = decode(scannedString);
} catch (err) {
  if (err instanceof EMVQRCRCMismatchError) {
    // QR code is corrupted or tampered
  } else if (err instanceof EMVQRInvalidTLVError) {
    // Malformed TLV structure
  } else if (err instanceof EMVQRMissingRequiredError) {
    // Required field missing (encode-time)
  }
}

API Reference

Top-level functions

| Function | Description | |---|---| | decode(raw) | Parse a QR string; validates CRC. Throws on error. | | decodeWithOptions(raw, opts) | Parse with { skipCRCValidation: true } option. | | encode(payload) | Serialise and append computed CRC. Throws on error. | | encodeWithOptions(payload, opts) | Serialise with override options. | | newPayload() | Create a Payload with payloadFormatIndicator = "01". |

Payload class

Data properties

| Property | Type | Notes | |---|---|---| | payloadFormatIndicator | string | Always "01" | | merchantAccountInfos | MerchantAccountInfo[] | Primitive and template MAIs | | merchantCategoryCode | string | ISO 18245 | | transactionCurrency | string | ISO 4217 numeric ("356" = INR) | | transactionAmount | string | Empty = consumer enters amount | | tipOrConvenienceIndicator | string | "" / "01" / "02" / "03" | | valueConvenienceFeeFixed | string | Set when indicator = "02" | | valueConvenienceFeePercent | string | Set when indicator = "03" | | countryCode | string | ISO 3166-1 alpha-2 | | merchantName | string | Max 25 chars | | merchantCity | string | Max 15 chars | | postalCode | string | Optional | | additionalData | AdditionalDataField \| null | | | languageTemplate | LanguageTemplate \| null | | | unreservedTemplates | UnreservedTemplate[] | IDs "80""99" |

Methods

| Method | Description | |---|---| | addPrimitiveMerchantAccount(id, value) | Add primitive MAI (IDs "02""25"). Pass "" for auto-ID. | | addTemplateMerchantAccount(id, guid, ...extra) | Add template MAI (IDs "26""51"). Pass "" for auto-ID. | | setFixedConvenienceFee(amount) | Fixed fee added automatically by consumer app. | | setPercentageConvenienceFee(percent) | Percentage fee, e.g. "1.50" for 1.50%. | | setPromptForTip() | Consumer app prompts for tip (must allow 0). | | setAdditionalData(fn) | Set ADF sub-fields via callback. | | setLanguageTemplate(lang, name, city) | Set alternate language display name. | | totalAmount() | Base + fee. Throws if no transactionAmount. | | loyaltyNumberRequired() | true when loyaltyNumber === "***". | | mobileNumberRequired() | true when mobileNumber === "***". | | preferredMerchantName(lang) | Language-aware name with English fallback. | | preferredMerchantCity(lang) | Language-aware city with English fallback. | | hasMultipleNetworks() | true when > 1 MAI entry. |

MerchantAccountInfo class

| Method | Description | |---|---| | globallyUniqueID() | Returns sub-field "00" value (template MAIs). | | subField(id) | Returns the value of sub-field id, or "". | | isTemplate() | true for IDs "26""51". |

Error classes

| Class | When thrown | |---|---| | EMVQRError | Base class for all EMV QR errors | | EMVQRInvalidLengthError | Payload string too short | | EMVQRInvalidTLVError | Malformed TLV structure | | EMVQRCRCMismatchError | CRC does not match | | EMVQRMissingRequiredError | Required field absent (encode) | | EMVQRParseError | Field-specific parse failure (has .fieldId) |

Constants

// Tip indicators
TIP_INDICATOR_PROMPT_CONSUMER       // "01"
TIP_INDICATOR_FIXED_CONVENIENCE_FEE // "02"
TIP_INDICATOR_PERCENTAGE_FEE        // "03"

// Sentinel — triggers consumer app prompt for input
PROMPT_VALUE // "***"

// Indian context
// Currency: "356" (INR)   Country: "IN"   NPCI GUID: "A000000524"

Bharat QR Reference

NPCI mandated (Sept 2017) that all Bharat QR codes must include at least:

| MAI Slot | Network | Notes | |---|---|---| | Primitive 0208 | Visa, MC, Amex, etc. | Acquirer-assigned 16-char merchant IDs | | Template 26 | UPI | GUID = A000000524, sub-field 01 = VPA | | Template 27 | RuPay | GUID = A000000524, sub-field 01 = acquirer cred |

Currency : 356 (INR)
Country  : IN

Contributing

See CONTRIBUTING.md.

License

MIT — see LICENSE.

Disclaimer

EMV® is a registered trademark of EMVCo, LLC. This library is an independent open-source implementation and is not affiliated with or endorsed by EMVCo or NPCI.