upi-qr-code-generator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 techpool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # UPI QR Code Generator
2
+
3
+ Generate UPI payment URIs and QR codes with a pre-filled INR amount. The
4
+ library works in Node.js and browser applications using a bundler, and ships
5
+ with ESM, CommonJS, and TypeScript declarations.
6
+
7
+ > This package generates payment requests. It does not initiate, verify, or
8
+ > reconcile payments and never handles a payer's UPI PIN.
9
+
10
+ ## Features
11
+
12
+ - Creates standards-shaped `upi://pay` URIs
13
+ - Includes and validates a fixed payment amount
14
+ - Generates PNG data URLs or SVG strings
15
+ - Escapes payee names, notes, and references safely
16
+ - Supports merchant transaction references and merchant category codes
17
+ - Provides TypeScript types and works from JavaScript
18
+
19
+ ## Install
20
+
21
+ Once published to npm:
22
+
23
+ ```sh
24
+ npm install upi-qr-code-generator
25
+ ```
26
+
27
+ To install the current GitHub version:
28
+
29
+ ```sh
30
+ npm install github:techpool/upi-qr-code-generator
31
+ ```
32
+
33
+ Node.js 20 or newer is supported.
34
+
35
+ ## Quick start
36
+
37
+ ```js
38
+ import {
39
+ createUpiPaymentUri,
40
+ generateUpiQrDataUrl,
41
+ } from "upi-qr-code-generator";
42
+
43
+ const payment = {
44
+ payeeVpa: "merchant@bank",
45
+ payeeName: "Example Store",
46
+ amount: "149.00",
47
+ transactionRef: "ORDER-123",
48
+ transactionNote: "Order 123",
49
+ };
50
+
51
+ const uri = createUpiPaymentUri(payment);
52
+ console.log(uri);
53
+
54
+ const qrDataUrl = await generateUpiQrDataUrl(payment);
55
+
56
+ // Browser example
57
+ document.querySelector("img").src = qrDataUrl;
58
+ ```
59
+
60
+ The generated URI looks like this:
61
+
62
+ ```text
63
+ upi://pay?pa=merchant%40bank&pn=Example%20Store&am=149.00&cu=INR&tr=ORDER-123&tn=Order%20123
64
+ ```
65
+
66
+ ## Generate SVG
67
+
68
+ ```js
69
+ import { generateUpiQrSvg } from "upi-qr-code-generator";
70
+
71
+ const svg = await generateUpiQrSvg({
72
+ payeeVpa: "merchant@bank",
73
+ payeeName: "Example Store",
74
+ amount: "499.50",
75
+ transactionRef: "ORDER-456",
76
+ });
77
+ ```
78
+
79
+ In Node.js, save the returned SVG with `node:fs/promises`. A complete example
80
+ is available in [`examples/generate-svg.mjs`](examples/generate-svg.mjs).
81
+
82
+ ## CommonJS
83
+
84
+ ```js
85
+ const { generateUpiQrDataUrl } = require("upi-qr-code-generator");
86
+
87
+ const qrDataUrl = await generateUpiQrDataUrl({
88
+ payeeVpa: "merchant@bank",
89
+ payeeName: "Example Store",
90
+ amount: "99.00",
91
+ });
92
+ ```
93
+
94
+ ## API
95
+
96
+ ### `createUpiPaymentUri(payment)`
97
+
98
+ Returns the encoded `upi://pay` URI.
99
+
100
+ ### `generateUpiQrDataUrl(payment, qrOptions?)`
101
+
102
+ Returns a promise containing a PNG `data:image/png;base64,...` URL.
103
+
104
+ ### `generateUpiQrSvg(payment, qrOptions?)`
105
+
106
+ Returns a promise containing an SVG string.
107
+
108
+ ### `normalizeUpiAmount(amount)`
109
+
110
+ Validates a positive amount with at most two decimal places and returns an
111
+ exact two-decimal string. It does not silently round.
112
+
113
+ ### Payment options
114
+
115
+ | Option | Type | Required | UPI field | Description |
116
+ | --- | --- | --- | --- | --- |
117
+ | `payeeVpa` | `string` | Yes | `pa` | Payee UPI virtual payment address |
118
+ | `payeeName` | `string` | Yes | `pn` | Name shown in the payer's app |
119
+ | `amount` | `string \| number` | Yes | `am` | Positive INR amount, maximum two decimal places |
120
+ | `transactionRef` | `string` | No | `tr` | Unique merchant reference, maximum 35 characters |
121
+ | `transactionNote` | `string` | No | `tn` | Payment note |
122
+ | `merchantCode` | `string` | No | `mc` | Merchant category code supplied by an acquirer |
123
+
124
+ Use decimal strings such as `"149.00"` for money. JavaScript numbers are
125
+ accepted for convenience, but values with more than two decimal places are
126
+ rejected rather than rounded.
127
+
128
+ For dynamic merchant QR codes, supply a unique `transactionRef` for each
129
+ payment. Your acquiring bank or PSP may require additional fields or impose
130
+ stricter rules.
131
+
132
+ ### QR options
133
+
134
+ ```ts
135
+ {
136
+ errorCorrectionLevel?: "low" | "medium" | "quartile" | "high" | "L" | "M" | "Q" | "H";
137
+ width?: number;
138
+ margin?: number;
139
+ scale?: number;
140
+ color?: {
141
+ dark?: string;
142
+ light?: string;
143
+ };
144
+ }
145
+ ```
146
+
147
+ Defaults are a width of 512 pixels, margin of 2 modules, and error correction
148
+ level `M`.
149
+
150
+ ## Payment confirmation
151
+
152
+ A successful QR scan is not proof of payment. To confirm payments, use the
153
+ status, reconciliation, or webhook APIs provided by your bank, PSP, or payment
154
+ gateway. Match their confirmed transaction to your unique `transactionRef`.
155
+
156
+ Always show the expected payee and amount in your checkout UI. The payer should
157
+ verify those details in their UPI app before authorizing the transaction.
158
+
159
+ ## UPI compatibility
160
+
161
+ UPI app behavior can vary. Test production QR codes with the UPI apps your
162
+ customers use and follow the requirements supplied by your acquiring bank or
163
+ PSP. NPCI's merchant QR interoperability circular identifies `pa` and `pn` as
164
+ critical fields and requires `am` and `tr` for dynamic merchant QRs.
165
+
166
+ - [NPCI UPI overview](https://www.npci.org.in/product/upi)
167
+ - [NPCI merchant QR interoperability circular](https://www.npci.org.in/PDF/npci/upi/circular/2017/Circular18_BankCompliances_to_enbaleUPIMerchantecosystem_0.pdf)
168
+
169
+ ## Development
170
+
171
+ ```sh
172
+ npm install
173
+ npm test
174
+ npm run typecheck
175
+ npm run build
176
+ ```
177
+
178
+ Run the complete verification suite with:
179
+
180
+ ```sh
181
+ npm run check
182
+ ```
183
+
184
+ ## License
185
+
186
+ [MIT](LICENSE)
package/dist/index.cjs ADDED
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ UPI_PAYMENT_SCHEME: () => UPI_PAYMENT_SCHEME,
34
+ createUpiPaymentUri: () => createUpiPaymentUri,
35
+ generateUpiQrDataUrl: () => generateUpiQrDataUrl,
36
+ generateUpiQrSvg: () => generateUpiQrSvg,
37
+ normalizeUpiAmount: () => normalizeUpiAmount
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+ var import_qrcode = __toESM(require("qrcode"), 1);
41
+ var UPI_PAYMENT_SCHEME = "upi://pay";
42
+ var DEFAULT_QR_OPTIONS = {
43
+ errorCorrectionLevel: "M",
44
+ width: 512,
45
+ margin: 2
46
+ };
47
+ function requiredText(value, field) {
48
+ if (typeof value !== "string" || value.trim() === "") {
49
+ throw new TypeError(`${field} is required`);
50
+ }
51
+ return value.trim();
52
+ }
53
+ function optionalText(value, field, maxLength) {
54
+ if (value === void 0) {
55
+ return void 0;
56
+ }
57
+ if (typeof value !== "string" || value.trim() === "") {
58
+ throw new TypeError(`${field} must be a non-empty string when provided`);
59
+ }
60
+ const normalized = value.trim();
61
+ if (maxLength !== void 0 && normalized.length > maxLength) {
62
+ throw new RangeError(`${field} must be at most ${maxLength} characters`);
63
+ }
64
+ return normalized;
65
+ }
66
+ function normalizeUpiAmount(value) {
67
+ if (typeof value !== "string" && typeof value !== "number") {
68
+ throw new TypeError("amount must be a string or number");
69
+ }
70
+ if (typeof value === "number" && !Number.isFinite(value)) {
71
+ throw new TypeError("amount must be finite");
72
+ }
73
+ const candidate = String(value).trim();
74
+ const match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(candidate);
75
+ if (!match) {
76
+ throw new TypeError(
77
+ "amount must be a positive decimal with no more than two decimal places"
78
+ );
79
+ }
80
+ const whole = match[1];
81
+ const fraction = (match[2] ?? "").padEnd(2, "0");
82
+ if (BigInt(whole) === 0n && fraction === "00") {
83
+ throw new RangeError("amount must be greater than zero");
84
+ }
85
+ return `${whole}.${fraction}`;
86
+ }
87
+ function createUpiPaymentUri(options) {
88
+ if (options === null || typeof options !== "object") {
89
+ throw new TypeError("payment options are required");
90
+ }
91
+ const payeeVpa = requiredText(options.payeeVpa, "payeeVpa");
92
+ if (!/^[^\s@]+@[^\s@]+$/.test(payeeVpa)) {
93
+ throw new TypeError("payeeVpa must be a valid UPI virtual payment address");
94
+ }
95
+ const fields = [
96
+ ["pa", payeeVpa],
97
+ ["pn", requiredText(options.payeeName, "payeeName")],
98
+ ["am", normalizeUpiAmount(options.amount)],
99
+ ["cu", "INR"],
100
+ ["tr", optionalText(options.transactionRef, "transactionRef", 35)],
101
+ ["tn", optionalText(options.transactionNote, "transactionNote")],
102
+ ["mc", optionalText(options.merchantCode, "merchantCode")]
103
+ ];
104
+ const query = fields.filter((field) => field[1] !== void 0).map(
105
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
106
+ ).join("&");
107
+ return `${UPI_PAYMENT_SCHEME}?${query}`;
108
+ }
109
+ function generateUpiQrDataUrl(payment, qrOptions = {}) {
110
+ const options = {
111
+ ...DEFAULT_QR_OPTIONS,
112
+ ...qrOptions,
113
+ type: "image/png"
114
+ };
115
+ return import_qrcode.default.toDataURL(createUpiPaymentUri(payment), options);
116
+ }
117
+ function generateUpiQrSvg(payment, qrOptions = {}) {
118
+ const options = {
119
+ ...DEFAULT_QR_OPTIONS,
120
+ ...qrOptions,
121
+ type: "svg"
122
+ };
123
+ return import_qrcode.default.toString(createUpiPaymentUri(payment), options);
124
+ }
125
+ // Annotate the CommonJS export names for ESM import in node:
126
+ 0 && (module.exports = {
127
+ UPI_PAYMENT_SCHEME,
128
+ createUpiPaymentUri,
129
+ generateUpiQrDataUrl,
130
+ generateUpiQrSvg,
131
+ normalizeUpiAmount
132
+ });
133
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import QRCode from \"qrcode\";\nimport type {\n QRCodeErrorCorrectionLevel,\n QRCodeToDataURLOptions,\n QRCodeToStringOptions,\n} from \"qrcode\";\n\n/** Details encoded in a UPI payment QR code. */\nexport interface UpiPaymentOptions {\n /** Payee virtual payment address, for example `merchant@bank`. */\n payeeVpa: string;\n /** Name displayed by the payer's UPI app. */\n payeeName: string;\n /** Positive INR amount with no more than two decimal places. Prefer a string. */\n amount: string | number;\n /** Merchant transaction reference. Dynamic merchant QRs should include this. */\n transactionRef?: string;\n /** Note displayed with the payment. */\n transactionNote?: string;\n /** Merchant category code, when supplied by the acquiring bank or PSP. */\n merchantCode?: string;\n}\n\n/** Rendering controls shared by the PNG data URL and SVG generators. */\nexport interface UpiQrOptions {\n /** Error-correction level. Defaults to `M`. */\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n /** Width in pixels. Defaults to 512. */\n width?: number;\n /** Quiet-zone width in QR modules. Defaults to 2. */\n margin?: number;\n /** Pixels per module when width is not supplied. */\n scale?: number;\n /** Dark and light colors as hexadecimal RGBA strings. */\n color?: {\n dark?: string;\n light?: string;\n };\n}\n\nexport const UPI_PAYMENT_SCHEME = \"upi://pay\" as const;\n\nconst DEFAULT_QR_OPTIONS = {\n errorCorrectionLevel: \"M\",\n width: 512,\n margin: 2,\n} as const;\n\nfunction requiredText(value: string, field: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new TypeError(`${field} is required`);\n }\n\n return value.trim();\n}\n\nfunction optionalText(\n value: string | undefined,\n field: string,\n maxLength?: number,\n): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new TypeError(`${field} must be a non-empty string when provided`);\n }\n\n const normalized = value.trim();\n if (maxLength !== undefined && normalized.length > maxLength) {\n throw new RangeError(`${field} must be at most ${maxLength} characters`);\n }\n\n return normalized;\n}\n\n/**\n * Normalize an INR amount without silently rounding it.\n *\n * Decimal strings are recommended so callers do not introduce floating-point\n * rounding before the value reaches this function.\n */\nexport function normalizeUpiAmount(value: string | number): string {\n if (typeof value !== \"string\" && typeof value !== \"number\") {\n throw new TypeError(\"amount must be a string or number\");\n }\n\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n throw new TypeError(\"amount must be finite\");\n }\n\n const candidate = String(value).trim();\n const match = /^(0|[1-9]\\d*)(?:\\.(\\d{1,2}))?$/.exec(candidate);\n\n if (!match) {\n throw new TypeError(\n \"amount must be a positive decimal with no more than two decimal places\",\n );\n }\n\n const whole = match[1];\n const fraction = (match[2] ?? \"\").padEnd(2, \"0\");\n\n if (BigInt(whole) === 0n && fraction === \"00\") {\n throw new RangeError(\"amount must be greater than zero\");\n }\n\n return `${whole}.${fraction}`;\n}\n\n/** Create the `upi://pay` URI that is encoded into the QR code. */\nexport function createUpiPaymentUri(options: UpiPaymentOptions): string {\n if (options === null || typeof options !== \"object\") {\n throw new TypeError(\"payment options are required\");\n }\n\n const payeeVpa = requiredText(options.payeeVpa, \"payeeVpa\");\n if (!/^[^\\s@]+@[^\\s@]+$/.test(payeeVpa)) {\n throw new TypeError(\"payeeVpa must be a valid UPI virtual payment address\");\n }\n\n const fields: Array<[string, string | undefined]> = [\n [\"pa\", payeeVpa],\n [\"pn\", requiredText(options.payeeName, \"payeeName\")],\n [\"am\", normalizeUpiAmount(options.amount)],\n [\"cu\", \"INR\"],\n [\"tr\", optionalText(options.transactionRef, \"transactionRef\", 35)],\n [\"tn\", optionalText(options.transactionNote, \"transactionNote\")],\n [\"mc\", optionalText(options.merchantCode, \"merchantCode\")],\n ];\n\n const query = fields\n .filter((field): field is [string, string] => field[1] !== undefined)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,\n )\n .join(\"&\");\n\n return `${UPI_PAYMENT_SCHEME}?${query}`;\n}\n\n/** Generate a PNG QR code as a data URL. Works in Node.js and browser bundlers. */\nexport function generateUpiQrDataUrl(\n payment: UpiPaymentOptions,\n qrOptions: UpiQrOptions = {},\n): Promise<string> {\n const options: QRCodeToDataURLOptions = {\n ...DEFAULT_QR_OPTIONS,\n ...qrOptions,\n type: \"image/png\",\n };\n\n return QRCode.toDataURL(createUpiPaymentUri(payment), options);\n}\n\n/** Generate a QR code as an SVG string. */\nexport function generateUpiQrSvg(\n payment: UpiPaymentOptions,\n qrOptions: UpiQrOptions = {},\n): Promise<string> {\n const options: QRCodeToStringOptions = {\n ...DEFAULT_QR_OPTIONS,\n ...qrOptions,\n type: \"svg\",\n };\n\n return QRCode.toString(createUpiPaymentUri(payment), options);\n}\n"],"mappings":"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","names":["QRCode"]}
@@ -0,0 +1,49 @@
1
+ import { QRCodeErrorCorrectionLevel } from 'qrcode';
2
+
3
+ /** Details encoded in a UPI payment QR code. */
4
+ interface UpiPaymentOptions {
5
+ /** Payee virtual payment address, for example `merchant@bank`. */
6
+ payeeVpa: string;
7
+ /** Name displayed by the payer's UPI app. */
8
+ payeeName: string;
9
+ /** Positive INR amount with no more than two decimal places. Prefer a string. */
10
+ amount: string | number;
11
+ /** Merchant transaction reference. Dynamic merchant QRs should include this. */
12
+ transactionRef?: string;
13
+ /** Note displayed with the payment. */
14
+ transactionNote?: string;
15
+ /** Merchant category code, when supplied by the acquiring bank or PSP. */
16
+ merchantCode?: string;
17
+ }
18
+ /** Rendering controls shared by the PNG data URL and SVG generators. */
19
+ interface UpiQrOptions {
20
+ /** Error-correction level. Defaults to `M`. */
21
+ errorCorrectionLevel?: QRCodeErrorCorrectionLevel;
22
+ /** Width in pixels. Defaults to 512. */
23
+ width?: number;
24
+ /** Quiet-zone width in QR modules. Defaults to 2. */
25
+ margin?: number;
26
+ /** Pixels per module when width is not supplied. */
27
+ scale?: number;
28
+ /** Dark and light colors as hexadecimal RGBA strings. */
29
+ color?: {
30
+ dark?: string;
31
+ light?: string;
32
+ };
33
+ }
34
+ declare const UPI_PAYMENT_SCHEME: "upi://pay";
35
+ /**
36
+ * Normalize an INR amount without silently rounding it.
37
+ *
38
+ * Decimal strings are recommended so callers do not introduce floating-point
39
+ * rounding before the value reaches this function.
40
+ */
41
+ declare function normalizeUpiAmount(value: string | number): string;
42
+ /** Create the `upi://pay` URI that is encoded into the QR code. */
43
+ declare function createUpiPaymentUri(options: UpiPaymentOptions): string;
44
+ /** Generate a PNG QR code as a data URL. Works in Node.js and browser bundlers. */
45
+ declare function generateUpiQrDataUrl(payment: UpiPaymentOptions, qrOptions?: UpiQrOptions): Promise<string>;
46
+ /** Generate a QR code as an SVG string. */
47
+ declare function generateUpiQrSvg(payment: UpiPaymentOptions, qrOptions?: UpiQrOptions): Promise<string>;
48
+
49
+ export { UPI_PAYMENT_SCHEME, type UpiPaymentOptions, type UpiQrOptions, createUpiPaymentUri, generateUpiQrDataUrl, generateUpiQrSvg, normalizeUpiAmount };
@@ -0,0 +1,49 @@
1
+ import { QRCodeErrorCorrectionLevel } from 'qrcode';
2
+
3
+ /** Details encoded in a UPI payment QR code. */
4
+ interface UpiPaymentOptions {
5
+ /** Payee virtual payment address, for example `merchant@bank`. */
6
+ payeeVpa: string;
7
+ /** Name displayed by the payer's UPI app. */
8
+ payeeName: string;
9
+ /** Positive INR amount with no more than two decimal places. Prefer a string. */
10
+ amount: string | number;
11
+ /** Merchant transaction reference. Dynamic merchant QRs should include this. */
12
+ transactionRef?: string;
13
+ /** Note displayed with the payment. */
14
+ transactionNote?: string;
15
+ /** Merchant category code, when supplied by the acquiring bank or PSP. */
16
+ merchantCode?: string;
17
+ }
18
+ /** Rendering controls shared by the PNG data URL and SVG generators. */
19
+ interface UpiQrOptions {
20
+ /** Error-correction level. Defaults to `M`. */
21
+ errorCorrectionLevel?: QRCodeErrorCorrectionLevel;
22
+ /** Width in pixels. Defaults to 512. */
23
+ width?: number;
24
+ /** Quiet-zone width in QR modules. Defaults to 2. */
25
+ margin?: number;
26
+ /** Pixels per module when width is not supplied. */
27
+ scale?: number;
28
+ /** Dark and light colors as hexadecimal RGBA strings. */
29
+ color?: {
30
+ dark?: string;
31
+ light?: string;
32
+ };
33
+ }
34
+ declare const UPI_PAYMENT_SCHEME: "upi://pay";
35
+ /**
36
+ * Normalize an INR amount without silently rounding it.
37
+ *
38
+ * Decimal strings are recommended so callers do not introduce floating-point
39
+ * rounding before the value reaches this function.
40
+ */
41
+ declare function normalizeUpiAmount(value: string | number): string;
42
+ /** Create the `upi://pay` URI that is encoded into the QR code. */
43
+ declare function createUpiPaymentUri(options: UpiPaymentOptions): string;
44
+ /** Generate a PNG QR code as a data URL. Works in Node.js and browser bundlers. */
45
+ declare function generateUpiQrDataUrl(payment: UpiPaymentOptions, qrOptions?: UpiQrOptions): Promise<string>;
46
+ /** Generate a QR code as an SVG string. */
47
+ declare function generateUpiQrSvg(payment: UpiPaymentOptions, qrOptions?: UpiQrOptions): Promise<string>;
48
+
49
+ export { UPI_PAYMENT_SCHEME, type UpiPaymentOptions, type UpiQrOptions, createUpiPaymentUri, generateUpiQrDataUrl, generateUpiQrSvg, normalizeUpiAmount };
package/dist/index.js ADDED
@@ -0,0 +1,94 @@
1
+ // src/index.ts
2
+ import QRCode from "qrcode";
3
+ var UPI_PAYMENT_SCHEME = "upi://pay";
4
+ var DEFAULT_QR_OPTIONS = {
5
+ errorCorrectionLevel: "M",
6
+ width: 512,
7
+ margin: 2
8
+ };
9
+ function requiredText(value, field) {
10
+ if (typeof value !== "string" || value.trim() === "") {
11
+ throw new TypeError(`${field} is required`);
12
+ }
13
+ return value.trim();
14
+ }
15
+ function optionalText(value, field, maxLength) {
16
+ if (value === void 0) {
17
+ return void 0;
18
+ }
19
+ if (typeof value !== "string" || value.trim() === "") {
20
+ throw new TypeError(`${field} must be a non-empty string when provided`);
21
+ }
22
+ const normalized = value.trim();
23
+ if (maxLength !== void 0 && normalized.length > maxLength) {
24
+ throw new RangeError(`${field} must be at most ${maxLength} characters`);
25
+ }
26
+ return normalized;
27
+ }
28
+ function normalizeUpiAmount(value) {
29
+ if (typeof value !== "string" && typeof value !== "number") {
30
+ throw new TypeError("amount must be a string or number");
31
+ }
32
+ if (typeof value === "number" && !Number.isFinite(value)) {
33
+ throw new TypeError("amount must be finite");
34
+ }
35
+ const candidate = String(value).trim();
36
+ const match = /^(0|[1-9]\d*)(?:\.(\d{1,2}))?$/.exec(candidate);
37
+ if (!match) {
38
+ throw new TypeError(
39
+ "amount must be a positive decimal with no more than two decimal places"
40
+ );
41
+ }
42
+ const whole = match[1];
43
+ const fraction = (match[2] ?? "").padEnd(2, "0");
44
+ if (BigInt(whole) === 0n && fraction === "00") {
45
+ throw new RangeError("amount must be greater than zero");
46
+ }
47
+ return `${whole}.${fraction}`;
48
+ }
49
+ function createUpiPaymentUri(options) {
50
+ if (options === null || typeof options !== "object") {
51
+ throw new TypeError("payment options are required");
52
+ }
53
+ const payeeVpa = requiredText(options.payeeVpa, "payeeVpa");
54
+ if (!/^[^\s@]+@[^\s@]+$/.test(payeeVpa)) {
55
+ throw new TypeError("payeeVpa must be a valid UPI virtual payment address");
56
+ }
57
+ const fields = [
58
+ ["pa", payeeVpa],
59
+ ["pn", requiredText(options.payeeName, "payeeName")],
60
+ ["am", normalizeUpiAmount(options.amount)],
61
+ ["cu", "INR"],
62
+ ["tr", optionalText(options.transactionRef, "transactionRef", 35)],
63
+ ["tn", optionalText(options.transactionNote, "transactionNote")],
64
+ ["mc", optionalText(options.merchantCode, "merchantCode")]
65
+ ];
66
+ const query = fields.filter((field) => field[1] !== void 0).map(
67
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
68
+ ).join("&");
69
+ return `${UPI_PAYMENT_SCHEME}?${query}`;
70
+ }
71
+ function generateUpiQrDataUrl(payment, qrOptions = {}) {
72
+ const options = {
73
+ ...DEFAULT_QR_OPTIONS,
74
+ ...qrOptions,
75
+ type: "image/png"
76
+ };
77
+ return QRCode.toDataURL(createUpiPaymentUri(payment), options);
78
+ }
79
+ function generateUpiQrSvg(payment, qrOptions = {}) {
80
+ const options = {
81
+ ...DEFAULT_QR_OPTIONS,
82
+ ...qrOptions,
83
+ type: "svg"
84
+ };
85
+ return QRCode.toString(createUpiPaymentUri(payment), options);
86
+ }
87
+ export {
88
+ UPI_PAYMENT_SCHEME,
89
+ createUpiPaymentUri,
90
+ generateUpiQrDataUrl,
91
+ generateUpiQrSvg,
92
+ normalizeUpiAmount
93
+ };
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import QRCode from \"qrcode\";\nimport type {\n QRCodeErrorCorrectionLevel,\n QRCodeToDataURLOptions,\n QRCodeToStringOptions,\n} from \"qrcode\";\n\n/** Details encoded in a UPI payment QR code. */\nexport interface UpiPaymentOptions {\n /** Payee virtual payment address, for example `merchant@bank`. */\n payeeVpa: string;\n /** Name displayed by the payer's UPI app. */\n payeeName: string;\n /** Positive INR amount with no more than two decimal places. Prefer a string. */\n amount: string | number;\n /** Merchant transaction reference. Dynamic merchant QRs should include this. */\n transactionRef?: string;\n /** Note displayed with the payment. */\n transactionNote?: string;\n /** Merchant category code, when supplied by the acquiring bank or PSP. */\n merchantCode?: string;\n}\n\n/** Rendering controls shared by the PNG data URL and SVG generators. */\nexport interface UpiQrOptions {\n /** Error-correction level. Defaults to `M`. */\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n /** Width in pixels. Defaults to 512. */\n width?: number;\n /** Quiet-zone width in QR modules. Defaults to 2. */\n margin?: number;\n /** Pixels per module when width is not supplied. */\n scale?: number;\n /** Dark and light colors as hexadecimal RGBA strings. */\n color?: {\n dark?: string;\n light?: string;\n };\n}\n\nexport const UPI_PAYMENT_SCHEME = \"upi://pay\" as const;\n\nconst DEFAULT_QR_OPTIONS = {\n errorCorrectionLevel: \"M\",\n width: 512,\n margin: 2,\n} as const;\n\nfunction requiredText(value: string, field: string): string {\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new TypeError(`${field} is required`);\n }\n\n return value.trim();\n}\n\nfunction optionalText(\n value: string | undefined,\n field: string,\n maxLength?: number,\n): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value !== \"string\" || value.trim() === \"\") {\n throw new TypeError(`${field} must be a non-empty string when provided`);\n }\n\n const normalized = value.trim();\n if (maxLength !== undefined && normalized.length > maxLength) {\n throw new RangeError(`${field} must be at most ${maxLength} characters`);\n }\n\n return normalized;\n}\n\n/**\n * Normalize an INR amount without silently rounding it.\n *\n * Decimal strings are recommended so callers do not introduce floating-point\n * rounding before the value reaches this function.\n */\nexport function normalizeUpiAmount(value: string | number): string {\n if (typeof value !== \"string\" && typeof value !== \"number\") {\n throw new TypeError(\"amount must be a string or number\");\n }\n\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n throw new TypeError(\"amount must be finite\");\n }\n\n const candidate = String(value).trim();\n const match = /^(0|[1-9]\\d*)(?:\\.(\\d{1,2}))?$/.exec(candidate);\n\n if (!match) {\n throw new TypeError(\n \"amount must be a positive decimal with no more than two decimal places\",\n );\n }\n\n const whole = match[1];\n const fraction = (match[2] ?? \"\").padEnd(2, \"0\");\n\n if (BigInt(whole) === 0n && fraction === \"00\") {\n throw new RangeError(\"amount must be greater than zero\");\n }\n\n return `${whole}.${fraction}`;\n}\n\n/** Create the `upi://pay` URI that is encoded into the QR code. */\nexport function createUpiPaymentUri(options: UpiPaymentOptions): string {\n if (options === null || typeof options !== \"object\") {\n throw new TypeError(\"payment options are required\");\n }\n\n const payeeVpa = requiredText(options.payeeVpa, \"payeeVpa\");\n if (!/^[^\\s@]+@[^\\s@]+$/.test(payeeVpa)) {\n throw new TypeError(\"payeeVpa must be a valid UPI virtual payment address\");\n }\n\n const fields: Array<[string, string | undefined]> = [\n [\"pa\", payeeVpa],\n [\"pn\", requiredText(options.payeeName, \"payeeName\")],\n [\"am\", normalizeUpiAmount(options.amount)],\n [\"cu\", \"INR\"],\n [\"tr\", optionalText(options.transactionRef, \"transactionRef\", 35)],\n [\"tn\", optionalText(options.transactionNote, \"transactionNote\")],\n [\"mc\", optionalText(options.merchantCode, \"merchantCode\")],\n ];\n\n const query = fields\n .filter((field): field is [string, string] => field[1] !== undefined)\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,\n )\n .join(\"&\");\n\n return `${UPI_PAYMENT_SCHEME}?${query}`;\n}\n\n/** Generate a PNG QR code as a data URL. Works in Node.js and browser bundlers. */\nexport function generateUpiQrDataUrl(\n payment: UpiPaymentOptions,\n qrOptions: UpiQrOptions = {},\n): Promise<string> {\n const options: QRCodeToDataURLOptions = {\n ...DEFAULT_QR_OPTIONS,\n ...qrOptions,\n type: \"image/png\",\n };\n\n return QRCode.toDataURL(createUpiPaymentUri(payment), options);\n}\n\n/** Generate a QR code as an SVG string. */\nexport function generateUpiQrSvg(\n payment: UpiPaymentOptions,\n qrOptions: UpiQrOptions = {},\n): Promise<string> {\n const options: QRCodeToStringOptions = {\n ...DEFAULT_QR_OPTIONS,\n ...qrOptions,\n type: \"svg\",\n };\n\n return QRCode.toString(createUpiPaymentUri(payment), options);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "upi-qr-code-generator",
3
+ "version": "1.0.0",
4
+ "description": "Generate UPI payment URIs and QR codes with a pre-filled amount in Node.js and browsers.",
5
+ "keywords": [
6
+ "upi",
7
+ "qr-code",
8
+ "payments",
9
+ "india",
10
+ "javascript",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "techpool",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "sideEffects": false,
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/techpool/upi-qr-code-generator.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/techpool/upi-qr-code-generator/issues"
41
+ },
42
+ "homepage": "https://github.com/techpool/upi-qr-code-generator#readme",
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "check": "npm run typecheck && npm test && npm run build",
46
+ "prepare": "npm run build",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "typecheck": "tsc --noEmit",
50
+ "prepublishOnly": "npm run check"
51
+ },
52
+ "dependencies": {
53
+ "qrcode": "^1.5.4"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^24.0.0",
57
+ "@types/qrcode": "^1.5.6",
58
+ "tsup": "^8.5.1",
59
+ "typescript": "^5.9.3",
60
+ "vitest": "^5.0.0"
61
+ },
62
+ "overrides": {
63
+ "esbuild": "^0.28.2"
64
+ }
65
+ }