bysquare 2.0.0 → 2.0.1

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/README.md CHANGED
@@ -15,40 +15,45 @@ other payment regulations.
15
15
  This library is un-opinionated. Image generation from qr-code string depends on
16
16
  your implementation. See [examples](examples).
17
17
 
18
- ## How it works
19
-
20
- ![diagram](./doc/uml/logic.svg)
21
-
22
18
  ## Install
23
19
 
24
- GitHub
20
+ **npm registry**
25
21
 
26
22
  ```sh
27
- npm install xseman/bysquare#master
28
- npm install xseman/bysquare#develop
23
+ npm install bysquare
29
24
  ```
30
25
 
31
- npm registry
26
+ **GitHub**
32
27
 
33
28
  ```sh
34
- npm install bysquare
29
+ # same as latest released npm registry version
30
+ npm install xseman/bysquare#master
31
+
32
+ # latest unreleased changes
33
+ npm install xseman/bysquare#develop
35
34
  ```
36
35
 
37
- CLI
36
+ **CLI**
38
37
 
39
38
  ```sh
40
39
  npm install --global bysquare
41
40
  ```
42
41
 
42
+ ## How it works
43
+
44
+ ### Encoding sequence
45
+
46
+ ![logic](./docs/uml/logic.svg)
47
+
43
48
  ## API
44
49
 
45
50
  ```ts
46
- generate(model: DataModel): Promise<string>
51
+ generate(model: DataModel, options?: Options): Promise<string>
47
52
  parse(qr: string): Promise<DataModel>
48
53
  detect(qr: string): Boolean
49
54
  ```
50
55
 
51
- **generate(model: DataModel): Promise\<string>**
56
+ **generate(model: DataModel, options?: Options): Promise\<string>**
52
57
 
53
58
  ```ts
54
59
  import { generate, DataModel, parse, PaymentOptions } from "bysquare"
File without changes
package/lib/cjs/cli.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_readline_1 = require("node:readline");
10
+ const generate_js_1 = require("./generate.js");
11
+ if (process.stdin.isTTY) {
12
+ // bysquare "file"
13
+ handleInput(process.argv[2]);
14
+ }
15
+ else {
16
+ // echo "data" | bysquare
17
+ ;
18
+ (async () => {
19
+ const stdin = await handleStdin();
20
+ const qr = await fromJsonString(stdin).catch((e) => {
21
+ console.error(e);
22
+ process.exit(1);
23
+ });
24
+ console.log(qr);
25
+ process.exit(0);
26
+ })();
27
+ }
28
+ async function handleInput(input) {
29
+ if (input === undefined || input === "-h" || input === "--help") {
30
+ console.log(help());
31
+ process.exit(0);
32
+ }
33
+ if ((0, node_fs_1.existsSync)(process.argv[2])) {
34
+ const file = (0, node_fs_1.readFileSync)(process.argv[2], "utf8");
35
+ const qr = await fromJsonString(file).catch((e) => {
36
+ console.error(e);
37
+ process.exit(1);
38
+ });
39
+ console.log(qr);
40
+ }
41
+ else {
42
+ console.error(`File ${process.argv[2]} doesn't exists`);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ async function fromJsonString(stdin) {
47
+ return new Promise((resolve, reject) => {
48
+ try {
49
+ const data = JSON.parse(stdin);
50
+ const qrString = (0, generate_js_1.generate)(data);
51
+ resolve(qrString);
52
+ }
53
+ catch (e) {
54
+ reject(e);
55
+ }
56
+ });
57
+ }
58
+ async function handleStdin() {
59
+ const readline = (0, node_readline_1.createInterface)({
60
+ input: process.stdin,
61
+ output: process.stdout,
62
+ terminal: false
63
+ });
64
+ const lines = [];
65
+ return new Promise((resolve, reject) => {
66
+ readline
67
+ .on("line", (line) => {
68
+ lines.push(line);
69
+ })
70
+ .on("close", () => {
71
+ resolve(lines.join(""));
72
+ })
73
+ .on("SIGINT", /* CTRL+C */ reject);
74
+ });
75
+ }
76
+ function help() {
77
+ const exe = node_path_1.default.basename(process.argv[1]);
78
+ return [
79
+ "Simple Node.js library to generate 'PAY by square' QR string.",
80
+ "",
81
+ "Usage:",
82
+ ` ${exe} file`,
83
+ "",
84
+ "File:",
85
+ " Valid json file",
86
+ "",
87
+ "Flags:",
88
+ " -h, --help display this help and exit",
89
+ "",
90
+ "If <file> is omitted, reads from stdin.",
91
+ "",
92
+ "Examples:",
93
+ " bysquare ./example.json",
94
+ "",
95
+ " echo ",
96
+ " {",
97
+ ' "IBAN": "SK9611000000002918599669"',
98
+ ' "Amount": 100.0',
99
+ ' "CurrencyCode": "EUR"',
100
+ ' "VariableSymbol": "123"',
101
+ ' "Payments": 1',
102
+ ' "PaymentOptions": 1',
103
+ ' "BankAccounts": 1',
104
+ " }'",
105
+ " | bysquare"
106
+ ].join("\n");
107
+ }
@@ -0,0 +1,48 @@
1
+ /// <reference types="node" />
2
+ import { DataModel } from "./types.js";
3
+ /**
4
+ * Returns a 2 byte buffer that represents the header of the bysquare
5
+ * specification
6
+ *
7
+ * ```
8
+ * | Attribute | Number of bits | Possible values | Note
9
+ * --------------------------------------------------------------------------------------------
10
+ * | BySquareType | 4 | 0-15 | by square type
11
+ * | Version | 4 | 0-15 | version of the by square type
12
+ * | DocumentType | 4 | 0-15 | document type within given by square type
13
+ * | Reserved | 4 | 0-15 | bits reserved for future needs
14
+ * ```
15
+ *
16
+ * @see 3.5. by square header
17
+ */
18
+ export declare function bysquareHeader(header?: [
19
+ bySquareType: number,
20
+ version: number,
21
+ documentType: number,
22
+ reserved: number
23
+ ]): Buffer;
24
+ /**
25
+ * @see 3.10 Appending CRC32 checksum
26
+ */
27
+ export declare function checksum(intermediate: string): Buffer;
28
+ /**
29
+ * Transfer object to a tabbed string and append a CRC32 checksum
30
+ *
31
+ * @see 3.10. Appending CRC32 checksum
32
+ */
33
+ export declare function prepareCompression(model: DataModel): Buffer;
34
+ /**
35
+ * Transform data to ordered tab-separated intermediate representation ready for
36
+ * encoding
37
+ *
38
+ * @see Table 15 PAY by square sequence data model
39
+ */
40
+ export declare function toIntermediate(data: DataModel): string;
41
+ type Options = {
42
+ deburr: boolean;
43
+ };
44
+ /**
45
+ * Generate QR string ready for encoding into text QR code
46
+ */
47
+ export declare function generate(model: DataModel, options?: Options): Promise<string>;
48
+ export {};
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generate = exports.toIntermediate = exports.prepareCompression = exports.checksum = exports.bysquareHeader = void 0;
7
+ const lodash_deburr_1 = __importDefault(require("lodash.deburr"));
8
+ const lzma_native_1 = __importDefault(require("lzma-native"));
9
+ const rfc4648_1 = require("rfc4648");
10
+ const types_js_1 = require("./types.js");
11
+ // echo "Hello" | xz --format=raw --lzma1=lc=3,lp=0,pb=2,dict=32KiB --stdout | hexdump -C
12
+ /**
13
+ * Returns a 2 byte buffer that represents the header of the bysquare
14
+ * specification
15
+ *
16
+ * ```
17
+ * | Attribute | Number of bits | Possible values | Note
18
+ * --------------------------------------------------------------------------------------------
19
+ * | BySquareType | 4 | 0-15 | by square type
20
+ * | Version | 4 | 0-15 | version of the by square type
21
+ * | DocumentType | 4 | 0-15 | document type within given by square type
22
+ * | Reserved | 4 | 0-15 | bits reserved for future needs
23
+ * ```
24
+ *
25
+ * @see 3.5. by square header
26
+ */
27
+ function bysquareHeader(header = [
28
+ 0, 0,
29
+ 0, 0
30
+ ]) {
31
+ const isValid = header.every((nibble) => 0 <= nibble && nibble <= 15);
32
+ if (!isValid) {
33
+ throw new Error(`Invalid header byte value, valid range <0,15>`);
34
+ }
35
+ const [bySquareType, version, documentType, reserved] = header;
36
+ // Combine 4-nibbles to 2-bytes
37
+ const mergedNibbles = Buffer.from([
38
+ (bySquareType << 4) | (version << 0),
39
+ (documentType << 4) | (reserved << 0),
40
+ ]);
41
+ return mergedNibbles;
42
+ }
43
+ exports.bysquareHeader = bysquareHeader;
44
+ /**
45
+ * Allocates a new buffer of a 2 bytes that represents LZMA header which
46
+ * contains 16-bit unsigned integer (word, little-endian), which is the size of
47
+ * the decompressed data. Therefore the maximum size of compressed data is
48
+ * limited to 65535
49
+ *
50
+ * @see 3.11. LZMA Compression
51
+ */
52
+ function lzmaHeader(decompressedData) {
53
+ const bytesCount = decompressedData.length;
54
+ if (bytesCount >= 2 ** 16) {
55
+ throw new Error("The maximum compressed data size has been reached");
56
+ }
57
+ const dataSize = Buffer.alloc(2);
58
+ dataSize.writeInt16LE(bytesCount);
59
+ return dataSize;
60
+ }
61
+ /**
62
+ * @see 3.10 Appending CRC32 checksum
63
+ */
64
+ function checksum(intermediate) {
65
+ // @ts-ignore: Wrong return type
66
+ const data = lzma_native_1.default.crc32(intermediate);
67
+ const crc32 = Buffer.alloc(4);
68
+ crc32.writeUInt32LE(data);
69
+ return crc32;
70
+ }
71
+ exports.checksum = checksum;
72
+ /**
73
+ * Transfer object to a tabbed string and append a CRC32 checksum
74
+ *
75
+ * @see 3.10. Appending CRC32 checksum
76
+ */
77
+ function prepareCompression(model) {
78
+ const intermediate = toIntermediate(model);
79
+ return Buffer.concat([
80
+ checksum(intermediate),
81
+ Buffer.from(intermediate, "utf-8")
82
+ ]);
83
+ }
84
+ exports.prepareCompression = prepareCompression;
85
+ /**
86
+ * Transform data to ordered tab-separated intermediate representation ready for
87
+ * encoding
88
+ *
89
+ * @see Table 15 PAY by square sequence data model
90
+ */
91
+ function toIntermediate(data) {
92
+ const intermediate = new Array();
93
+ intermediate.push(data.invoiceId?.toString());
94
+ intermediate.push(data.payments.length.toString());
95
+ for (const p of data.payments) {
96
+ intermediate.push(p.type.toString());
97
+ intermediate.push(p.amount?.toString());
98
+ intermediate.push(p.currencyCode);
99
+ intermediate.push(p.paymentDueDate);
100
+ intermediate.push(p.variableSymbol);
101
+ intermediate.push(p.constantSymbol);
102
+ intermediate.push(p.specificSymbol);
103
+ intermediate.push(p.originatorRefInfo);
104
+ intermediate.push(p.paymentNote);
105
+ intermediate.push(p.bankAccounts.length.toString());
106
+ for (const ba of p.bankAccounts) {
107
+ intermediate.push(ba.iban);
108
+ intermediate.push(ba.bic);
109
+ }
110
+ if (p.type === types_js_1.PaymentOptions.StandingOrder) {
111
+ intermediate.push('1');
112
+ intermediate.push(p.day?.toString());
113
+ intermediate.push(p.month?.toString());
114
+ intermediate.push(p.periodicity);
115
+ intermediate.push(p.lastDate);
116
+ }
117
+ else {
118
+ intermediate.push('0');
119
+ }
120
+ if (p.type === types_js_1.PaymentOptions.DirectDebit) {
121
+ intermediate.push('1');
122
+ intermediate.push(p.directDebitScheme?.toString());
123
+ intermediate.push(p.directDebitType?.toString());
124
+ intermediate.push(p.variableSymbol?.toString());
125
+ intermediate.push(p.specificSymbol?.toString());
126
+ intermediate.push(p.originatorRefInfo?.toString());
127
+ intermediate.push(p.mandateId?.toString());
128
+ intermediate.push(p.creditorId?.toString());
129
+ intermediate.push(p.contractId?.toString());
130
+ intermediate.push(p.maxAmount?.toString());
131
+ intermediate.push(p.validTillDate?.toString());
132
+ }
133
+ else {
134
+ intermediate.push('0');
135
+ }
136
+ }
137
+ for (const p of data.payments) {
138
+ intermediate.push(p.beneficiary?.name);
139
+ intermediate.push(p.beneficiary?.street);
140
+ intermediate.push(p.beneficiary?.city);
141
+ }
142
+ return intermediate.join('\t');
143
+ }
144
+ exports.toIntermediate = toIntermediate;
145
+ /**
146
+ * Transfer diacritics to basic latin letters
147
+ */
148
+ function removeDiacritics(model) {
149
+ for (const payment of model.payments) {
150
+ if (payment.paymentNote) {
151
+ payment.paymentNote = (0, lodash_deburr_1.default)(payment.paymentNote);
152
+ }
153
+ if (payment.beneficiary?.name) {
154
+ payment.beneficiary.name = (0, lodash_deburr_1.default)(payment.beneficiary.name);
155
+ }
156
+ if (payment.beneficiary?.city) {
157
+ payment.beneficiary.city = (0, lodash_deburr_1.default)(payment.beneficiary.city);
158
+ }
159
+ if (payment.beneficiary?.street) {
160
+ payment.beneficiary.street = (0, lodash_deburr_1.default)(payment.beneficiary.street);
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Generate QR string ready for encoding into text QR code
166
+ */
167
+ function generate(model, options = { deburr: true }) {
168
+ if (options.deburr) {
169
+ removeDiacritics(model);
170
+ }
171
+ const data = prepareCompression(model);
172
+ const compressedData = [];
173
+ return new Promise((resolve, reject) => {
174
+ const encoder = lzma_native_1.default.createStream("rawEncoder", {
175
+ synchronous: true,
176
+ // @ts-ignore: Missing filter types
177
+ filters: [
178
+ {
179
+ // @ts-ignore: Missing filter types
180
+ id: lzma_native_1.default.FILTER_LZMA1,
181
+ lc: 3,
182
+ lp: 0,
183
+ pb: 2,
184
+ dict_size: 2 ** 17, // 128 kilobytes
185
+ },
186
+ ],
187
+ });
188
+ encoder
189
+ .on("end", () => {
190
+ const output = Buffer.concat([
191
+ bysquareHeader(),
192
+ lzmaHeader(data),
193
+ ...compressedData
194
+ ]);
195
+ resolve(rfc4648_1.base32hex.stringify(output, { pad: false }));
196
+ })
197
+ .on("data", (chunk) => {
198
+ compressedData.push(chunk);
199
+ })
200
+ .on("error", reject)
201
+ .write(data, (error) => {
202
+ error && reject(error);
203
+ encoder.end();
204
+ });
205
+ });
206
+ }
207
+ exports.generate = generate;
File without changes
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.parse = exports.detect = exports.generate = void 0;
18
+ var generate_js_1 = require("./generate.js");
19
+ Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return generate_js_1.generate; } });
20
+ var parse_js_1 = require("./parse.js");
21
+ Object.defineProperty(exports, "detect", { enumerable: true, get: function () { return parse_js_1.detect; } });
22
+ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parse_js_1.parse; } });
23
+ __exportStar(require("./types.js"), exports);
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "bysquare",
3
+ "description": "It's a national standard for payment QR codes adopted by Slovak Banking Association (SBA)",
4
+ "version": "2.0.1",
5
+ "license": "Apache-2.0",
6
+ "type": "commonjs",
7
+ "funding": "https://github.com/sponsors/xseman",
8
+ "homepage": "https://github.com/xseman/bysquare#readme",
9
+ "author": {
10
+ "name": "Filip Seman",
11
+ "email": "filip.seman@protonmail.com"
12
+ },
13
+ "scripts": {
14
+ "prebuild": "rm -rf ./lib || :",
15
+ "build": "tsc --project ./tsconfig.json && tsc --project ./tsconfig.cjs.json",
16
+ "postbuild": "bash -c 'chmod +x ./lib/{cjs,mjs}/cli.js' && cp package.json ./lib/mjs/ && jq '.type=\"commonjs\"' package.json > lib/cjs/package.json",
17
+ "test": "xv --loader=tsx ./src",
18
+ "test:watch": "find ./src/*.ts | entr xv --loader=tsx ./src",
19
+ "cli:local": "npm run build && npm link bysquare",
20
+ "version": "git flow release start v$npm_package_version",
21
+ "prepare": "",
22
+ "prepublishOnly": "npm run build",
23
+ "preversion": "git checkout develop",
24
+ "postversion": ""
25
+ },
26
+ "dependencies": {
27
+ "lodash.deburr": "~4.1.0",
28
+ "lzma-native": "7.0.1",
29
+ "rfc4648": "1.5.2"
30
+ },
31
+ "devDependencies": {
32
+ "@types/lodash.deburr": "~4.1.0",
33
+ "@types/lzma-native": "^4.0.1",
34
+ "@types/node": "~18.11.0",
35
+ "tsx": "~3.12.0",
36
+ "typescript": "~4.9.0",
37
+ "xv": "~2.1.0"
38
+ },
39
+ "bin": "lib/mjs/cli.js",
40
+ "types": "lib/mjs/index.d.ts",
41
+ "exports": {
42
+ ".": {
43
+ "require": "./lib/cjs/index.js",
44
+ "import": "./lib/mjs/index.js"
45
+ }
46
+ },
47
+ "files": [
48
+ "lib",
49
+ "!lib/*.test.*"
50
+ ],
51
+ "engines": {
52
+ "node": ">=16.0",
53
+ "npm": ">=7.0"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/xseman/bysquare.git"
58
+ },
59
+ "keywords": [
60
+ "pay by square",
61
+ "by square",
62
+ "paybysquare",
63
+ "bysquare",
64
+ "payments",
65
+ "qr-string",
66
+ "qr",
67
+ "cli"
68
+ ]
69
+ }
File without changes