bysquare 2.1.0 → 2.3.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/README.md CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  ![version][version] ![build][build]
4
4
 
5
- Simple `Node.js` library to generate "PAY by square" `QR` string.
5
+ Simple JavaScript library to generate and parse "PAY by square" string.
6
6
 
7
7
  **What is `PAY by square`?**
8
8
 
9
- It's a national standard for payment QR codes adopted by Slovak Banking
10
- Association in 2013. It is part of a large number of invoices, reminders and
11
- other payment regulations.
9
+ It's a national standard for QR code payments that was adopted by the Slovak
10
+ Banking Association in 2013. It is incorporated into a variety of invoices,
11
+ reminders and other payment regulations.
12
12
 
13
13
  **Can I generate an image?**
14
14
 
15
- This library is un-opinionated. Image generation from qr-code string depends on
16
- your implementation. See [examples](examples).
15
+ This library doesn't have a specific opinion and how the QR code string is
16
+ transformed into images depends on how you implement it. See
17
+ [examples](examples).
17
18
 
18
19
  ## Install
19
20
 
@@ -31,6 +32,9 @@ npm install xseman/bysquare#master
31
32
 
32
33
  # latest unreleased changes
33
34
  npm install xseman/bysquare#develop
35
+
36
+ # specific tag version, e.g. v2.1.0
37
+ npm install xseman/bysquare#v2.1.0
34
38
  ```
35
39
 
36
40
  **CLI**
@@ -39,6 +43,12 @@ npm install xseman/bysquare#develop
39
43
  npm install --global bysquare
40
44
  ```
41
45
 
46
+ **Deno** `v1.28+`, just import `npm:bysquare` `v2.1.0+`
47
+
48
+ ```
49
+ import { generate, parse } from "npm:bysquare@2.1.0"
50
+ ```
51
+
42
52
  ## How it works
43
53
 
44
54
  ### Encoding sequence
@@ -53,49 +63,55 @@ parse(qr: string): DataModel
53
63
  detect(qr: string): Boolean
54
64
  ```
55
65
 
56
- **generate(model: DataModel, options?: Options): string**
66
+ ## Usage
67
+
68
+ Generate
57
69
 
58
70
  ```ts
59
- import { generate, DataModel, parse, PaymentOptions } from "bysquare"
71
+ import { DataModel, generate, PaymentOptions } from "bysquare"
60
72
 
61
- const model {
73
+ // long string ready to be encoded to QR
74
+ const qrString = generate({
62
75
  invoiceId: "random-id",
63
76
  payments: [
64
77
  {
65
78
  type: PaymentOptions.PaymentOrder,
66
79
  amount: 100.0,
67
80
  bankAccounts: [
68
- { iban: "SK9611000000002918599669" },
81
+ { iban: "SK9611000000002918599669" }
69
82
  ],
70
83
  currencyCode: "EUR",
71
- variableSymbol: "123",
84
+ variableSymbol: "123"
72
85
  }
73
86
  ]
74
- } satisfies DataModel
75
-
76
- const qr = generate(model)
87
+ })
77
88
  ```
78
89
 
79
- **parse(qr: string): DataModel**
90
+ Parse
80
91
 
81
92
  ```ts
82
93
  import { parse } from "bysquare"
83
94
 
84
- const qr = "0004A00090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000"
85
- const model = parse(qr)
95
+ const model =
96
+ parse("0405QH8090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000")
97
+
98
+ // {
99
+ // invoiceId: "random-id",
100
+ // payments: [
101
+ // {
102
+ // type: 1,
103
+ // amount: 100.0,
104
+ // bankAccounts: [
105
+ // { iban: "SK9611000000002918599669" },
106
+ // ],
107
+ // currencyCode: "EUR",
108
+ // variableSymbol: "123",
109
+ // }
110
+ // ]
111
+ // }
112
+ //
86
113
  ```
87
114
 
88
- **detect(qr: string): Boolean**
89
-
90
- ```ts
91
- import { detect } from "bysquare"
92
-
93
- const qr = "0004A00090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000"
94
- const isBysquare = detect(qr)
95
- ```
96
-
97
- ## CLI
98
-
99
115
  You can use json file with valid model to generate qr-string.
100
116
 
101
117
  ```sh
@@ -114,7 +130,7 @@ You can use json file with valid model to generate qr-string.
114
130
  # }
115
131
 
116
132
  $ npx bysquare ./example.json
117
- $ 0004A00090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000
133
+ $ 0405QH8090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000
118
134
  ```
119
135
 
120
136
  You can also use stdin.
@@ -133,7 +149,7 @@ $ npx bysquare <<< '{
133
149
  ]
134
150
  }'
135
151
 
136
- $ 0004A00090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000
152
+ $ 0405QH8090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000
137
153
  ```
138
154
 
139
155
  ## Related
package/lib/cjs/cli.js CHANGED
@@ -8,12 +8,10 @@ const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_readline_1 = require("node:readline");
10
10
  const generate_js_1 = require("./generate.js");
11
- if (process.stdin.isTTY) {
12
- // bysquare "file"
11
+ if /** bysquare "file" */ (process.stdin.isTTY) {
13
12
  handleInput(process.argv[2]);
14
- }
13
+ } /** bysquare <<< "data" */
15
14
  else {
16
- // echo "data" | bysquare
17
15
  ;
18
16
  (async () => {
19
17
  const stdin = await handleStdin();
@@ -85,6 +83,6 @@ function help() {
85
83
  " \"variableSymbol\": \"123\"",
86
84
  " }",
87
85
  " ]",
88
- " }\"",
86
+ " }\""
89
87
  ].join("\n");
90
88
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { DataModel } from "./types.js";
3
2
  /**
4
3
  * Returns a 2 byte buffer that represents the header of the bysquare
@@ -13,31 +12,34 @@ import { DataModel } from "./types.js";
13
12
  * | Reserved | 4 | 0-15 | bits reserved for future needs
14
13
  * ```
15
14
  *
16
- * @see 3.5. by square header
15
+ * @see 3.5.
17
16
  */
18
- export declare function bysquareHeader(header?: [
17
+ export declare function headerBysquare(
18
+ /** dprint-ignore */
19
+ header?: [
19
20
  bySquareType: number,
20
21
  version: number,
21
22
  documentType: number,
22
23
  reserved: number
23
24
  ]): Uint8Array;
24
25
  /**
25
- * @see 3.10 Appending CRC32 checksum
26
+ * Creates a one-byte array that represents the length of compressed data in
27
+ * combination with CRC32 in bytes.
26
28
  */
27
- export declare function checksum(intermediate: string): Buffer;
29
+ export declare function headerDataLength(length: number): Uint8Array;
28
30
  /**
29
31
  * Transfer object to a tabbed string and append a CRC32 checksum
30
32
  *
31
- * @see 3.10. Appending CRC32 checksum
33
+ * @see 3.10.
32
34
  */
33
- export declare function addChecksum(model: DataModel): Uint8Array;
35
+ export declare function addChecksum(serialized: string): Uint8Array;
34
36
  /**
35
37
  * Transform data to ordered tab-separated intermediate representation ready for
36
38
  * encoding
37
39
  *
38
- * @see Table 15 PAY by square sequence data model
40
+ * @see Table 15.
39
41
  */
40
- export declare function deserialize(data: DataModel): string;
42
+ export declare function serialize(data: DataModel): string;
41
43
  type Options = {
42
44
  /**
43
45
  * Many banking apps do not support diacritics, which results in errors when
@@ -3,13 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.generate = exports.deserialize = exports.addChecksum = exports.checksum = exports.bysquareHeader = void 0;
6
+ exports.generate = exports.serialize = exports.addChecksum = exports.headerDataLength = exports.headerBysquare = void 0;
7
7
  const crc_32_1 = __importDefault(require("crc-32"));
8
8
  const lodash_deburr_1 = __importDefault(require("lodash.deburr"));
9
+ const lzma1_1 = require("lzma1");
9
10
  const rfc4648_1 = require("rfc4648");
10
11
  const types_js_1 = require("./types.js");
11
- // @ts-ignore: missing types
12
- const lzma_1 = __importDefault(require("lzma"));
13
12
  /**
14
13
  * Returns a 2 byte buffer that represents the header of the bysquare
15
14
  * specification
@@ -23,63 +22,52 @@ const lzma_1 = __importDefault(require("lzma"));
23
22
  * | Reserved | 4 | 0-15 | bits reserved for future needs
24
23
  * ```
25
24
  *
26
- * @see 3.5. by square header
25
+ * @see 3.5.
27
26
  */
28
- function bysquareHeader(header = [
29
- 0, 0,
30
- 0, 0
27
+ function headerBysquare(
28
+ /** dprint-ignore */
29
+ header = [
30
+ 0x00, 0x00,
31
+ 0x00, 0x00
31
32
  ]) {
32
33
  const isValid = header.every((nibble) => 0 <= nibble && nibble <= 15);
33
34
  if (!isValid) {
34
- throw new Error(`Invalid header byte value, valid range <0,15>`);
35
+ throw new Error("Invalid header byte value, valid range <0,15>");
35
36
  }
36
37
  const [bySquareType, version, documentType, reserved] = header;
37
38
  // Combine 4-nibbles to 2-bytes
38
39
  const mergedNibbles = Uint8Array.from([
39
40
  (bySquareType << 4) | (version << 0),
40
- (documentType << 4) | (reserved << 0),
41
+ (documentType << 4) | (reserved << 0)
41
42
  ]);
42
43
  return mergedNibbles;
43
44
  }
44
- exports.bysquareHeader = bysquareHeader;
45
+ exports.headerBysquare = headerBysquare;
45
46
  /**
46
- * Allocates a new buffer of a 2 bytes that represents LZMA header which
47
- * contains 16-bit unsigned integer (word, little-endian), which is the size of
48
- * the decompressed data. Therefore the maximum size of compressed data is
49
- * limited to 65535
50
- *
51
- * @see 3.11. LZMA Compression
47
+ * Creates a one-byte array that represents the length of compressed data in
48
+ * combination with CRC32 in bytes.
52
49
  */
53
- function datasizeHeader(data) {
54
- if (data.byteLength >= 2 ** 16) {
50
+ function headerDataLength(length) {
51
+ if (length >= 131072 /** 2^17 */) {
55
52
  throw new Error("The maximum compressed data size has been reached");
56
53
  }
57
- const header = new Uint8Array(2);
58
- header.set(Uint16Array.from([data.byteLength]));
59
- return header;
60
- }
61
- /**
62
- * @see 3.10 Appending CRC32 checksum
63
- */
64
- function checksum(intermediate) {
65
- const data = crc_32_1.default.str(intermediate);
66
- const checksum = Buffer.alloc(4);
67
- checksum.writeUInt32LE(data);
68
- return checksum;
54
+ const header = new ArrayBuffer(2);
55
+ new DataView(header).setUint16(0, length);
56
+ return new Uint8Array(header);
69
57
  }
70
- exports.checksum = checksum;
58
+ exports.headerDataLength = headerDataLength;
71
59
  /**
72
60
  * Transfer object to a tabbed string and append a CRC32 checksum
73
61
  *
74
- * @see 3.10. Appending CRC32 checksum
62
+ * @see 3.10.
75
63
  */
76
- function addChecksum(model) {
77
- const intermediate = deserialize(model);
78
- const checksum = Uint32Array.from([crc_32_1.default.str(intermediate)]);
79
- const byearray = [...intermediate].map(char => char.charCodeAt(0));
64
+ function addChecksum(serialized) {
65
+ const checksum = new ArrayBuffer(4);
66
+ new DataView(checksum).setUint32(0, crc_32_1.default.str(serialized), true);
67
+ const byteArray = new TextEncoder().encode(serialized);
80
68
  return Uint8Array.from([
81
- ...new Uint8Array(checksum.buffer),
82
- ...Uint8Array.from(byearray)
69
+ ...new Uint8Array(checksum),
70
+ ...Uint8Array.from(byteArray)
83
71
  ]);
84
72
  }
85
73
  exports.addChecksum = addChecksum;
@@ -87,62 +75,62 @@ exports.addChecksum = addChecksum;
87
75
  * Transform data to ordered tab-separated intermediate representation ready for
88
76
  * encoding
89
77
  *
90
- * @see Table 15 PAY by square sequence data model
78
+ * @see Table 15.
91
79
  */
92
- function deserialize(data) {
93
- const intermediate = new Array();
94
- intermediate.push(data.invoiceId?.toString());
95
- intermediate.push(data.payments.length.toString());
80
+ function serialize(data) {
81
+ const serialized = new Array();
82
+ serialized.push(data.invoiceId?.toString());
83
+ serialized.push(data.payments.length.toString());
96
84
  for (const p of data.payments) {
97
- intermediate.push(p.type.toString());
98
- intermediate.push(p.amount?.toString());
99
- intermediate.push(p.currencyCode);
100
- intermediate.push(p.paymentDueDate);
101
- intermediate.push(p.variableSymbol);
102
- intermediate.push(p.constantSymbol);
103
- intermediate.push(p.specificSymbol);
104
- intermediate.push(p.originatorRefInfo);
105
- intermediate.push(p.paymentNote);
106
- intermediate.push(p.bankAccounts.length.toString());
85
+ serialized.push(p.type.toString());
86
+ serialized.push(p.amount?.toString());
87
+ serialized.push(p.currencyCode);
88
+ serialized.push(p.paymentDueDate);
89
+ serialized.push(p.variableSymbol);
90
+ serialized.push(p.constantSymbol);
91
+ serialized.push(p.specificSymbol);
92
+ serialized.push(p.originatorRefInfo);
93
+ serialized.push(p.paymentNote);
94
+ serialized.push(p.bankAccounts.length.toString());
107
95
  for (const ba of p.bankAccounts) {
108
- intermediate.push(ba.iban);
109
- intermediate.push(ba.bic);
96
+ serialized.push(ba.iban);
97
+ serialized.push(ba.bic);
110
98
  }
111
99
  if (p.type === types_js_1.PaymentOptions.StandingOrder) {
112
- intermediate.push('1');
113
- intermediate.push(p.day?.toString());
114
- intermediate.push(p.month?.toString());
115
- intermediate.push(p.periodicity);
116
- intermediate.push(p.lastDate);
100
+ serialized.push("1");
101
+ serialized.push(p.day?.toString());
102
+ serialized.push(p.month?.toString());
103
+ serialized.push(p.periodicity);
104
+ serialized.push(p.lastDate);
117
105
  }
118
106
  else {
119
- intermediate.push('0');
107
+ serialized.push("0");
120
108
  }
121
109
  if (p.type === types_js_1.PaymentOptions.DirectDebit) {
122
- intermediate.push('1');
123
- intermediate.push(p.directDebitScheme?.toString());
124
- intermediate.push(p.directDebitType?.toString());
125
- intermediate.push(p.variableSymbol?.toString());
126
- intermediate.push(p.specificSymbol?.toString());
127
- intermediate.push(p.originatorRefInfo?.toString());
128
- intermediate.push(p.mandateId?.toString());
129
- intermediate.push(p.creditorId?.toString());
130
- intermediate.push(p.contractId?.toString());
131
- intermediate.push(p.maxAmount?.toString());
132
- intermediate.push(p.validTillDate?.toString());
110
+ serialized.push("1");
111
+ serialized.push(p.directDebitScheme?.toString());
112
+ serialized.push(p.directDebitType?.toString());
113
+ serialized.push(p.variableSymbol?.toString());
114
+ serialized.push(p.specificSymbol?.toString());
115
+ serialized.push(p.originatorRefInfo?.toString());
116
+ serialized.push(p.mandateId?.toString());
117
+ serialized.push(p.creditorId?.toString());
118
+ serialized.push(p.contractId?.toString());
119
+ serialized.push(p.maxAmount?.toString());
120
+ serialized.push(p.validTillDate?.toString());
133
121
  }
134
122
  else {
135
- intermediate.push('0');
123
+ serialized.push("0");
136
124
  }
137
125
  }
138
126
  for (const p of data.payments) {
139
- intermediate.push(p.beneficiary?.name);
140
- intermediate.push(p.beneficiary?.street);
141
- intermediate.push(p.beneficiary?.city);
127
+ serialized.push(p.beneficiary?.name);
128
+ serialized.push(p.beneficiary?.street);
129
+ serialized.push(p.beneficiary?.city);
142
130
  }
143
- return intermediate.join('\t');
131
+ return serialized.join("\t");
144
132
  }
145
- exports.deserialize = deserialize;
133
+ exports.serialize = serialize;
146
134
  function removeDiacritics(model) {
147
135
  for (const payment of model.payments) {
148
136
  if (payment.paymentNote) {
@@ -166,17 +154,19 @@ function generate(model, options = { deburr: true }) {
166
154
  if (options.deburr) {
167
155
  removeDiacritics(model);
168
156
  }
169
- const payload = addChecksum(model);
170
- const compressed = Uint8Array.from(lzma_1.default.compress(payload));
171
- /**
172
- * @see https://docs.fileformat.com/compression/lzma/#lzma-header
173
- */
174
- const _header = Uint8Array.from(compressed.subarray(0, 13));
175
- const data = Uint8Array.from(compressed.subarray(13));
157
+ const payload = serialize(model);
158
+ const withChecksum = addChecksum(payload);
159
+ const compressed = Uint8Array.from((0, lzma1_1.compress)(withChecksum));
160
+ const _lzmaHeader = Uint8Array.from(compressed.subarray(0, 13));
161
+ const lzmaBody = Uint8Array.from(compressed.subarray(13));
176
162
  const output = Uint8Array.from([
177
- ...bysquareHeader(),
178
- ...datasizeHeader(payload),
179
- ...data
163
+ // FIXME:
164
+ // for now other implementation of bysquare doesn't recognize header if
165
+ // version is specified like TatraBanka
166
+ // ...headerBysquare([0x00, Version["1.1.0"], 0x00, 0x00]),
167
+ ...headerBysquare([0x00, 0x00, 0x00, 0x00]),
168
+ ...headerDataLength(withChecksum.byteLength),
169
+ ...lzmaBody
180
170
  ]);
181
171
  return rfc4648_1.base32hex.stringify(output, {
182
172
  pad: false
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "bysquare",
3
3
  "description": "It's a national standard for payment QR codes adopted by Slovak Banking Association (SBA)",
4
- "version": "2.1.0",
4
+ "version": "2.3.0",
5
5
  "license": "Apache-2.0",
6
- "type": "commonjs",
7
6
  "funding": "https://github.com/sponsors/xseman",
8
7
  "homepage": "https://github.com/xseman/bysquare#readme",
9
- "author": {
10
- "name": "Filip Seman",
11
- "email": "filip.seman@protonmail.com"
8
+ "author": "Filip Seman <filip.seman@pm.me>",
9
+ "keywords": [
10
+ "pay by square",
11
+ "by square",
12
+ "paybysquare",
13
+ "bysquare",
14
+ "payments",
15
+ "qr-string",
16
+ "generate",
17
+ "parse"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/xseman/bysquare.git"
12
22
  },
13
23
  "scripts": {
14
24
  "prebuild": "rm -rf ./lib || :",
@@ -27,17 +37,18 @@
27
37
  "dependencies": {
28
38
  "crc-32": "~1.2.0",
29
39
  "lodash.deburr": "~4.1.0",
30
- "lzma": "~2.3.0",
40
+ "lzma1": "~0.0.1",
31
41
  "rfc4648": "~1.5.0"
32
42
  },
33
43
  "devDependencies": {
34
44
  "@types/lodash.deburr": "~4.1.0",
35
- "@types/lzma-native": "^4.0.1",
36
- "@types/node": "~18.11.0",
45
+ "@types/node": ">=16.14",
46
+ "dprint": "~0.35.0",
37
47
  "tsx": "~3.12.0",
38
- "typescript": "~4.9.0",
48
+ "typescript": "~5.0.0",
39
49
  "xv": "~2.1.0"
40
50
  },
51
+ "type": "commonjs",
41
52
  "bin": "lib/mjs/cli.js",
42
53
  "types": "lib/mjs/index.d.ts",
43
54
  "exports": {
@@ -51,21 +62,7 @@
51
62
  "!lib/*.test.*"
52
63
  ],
53
64
  "engines": {
54
- "node": ">=16.0",
65
+ "node": ">=16.14",
55
66
  "npm": ">=7.0"
56
- },
57
- "repository": {
58
- "type": "git",
59
- "url": "git+https://github.com/xseman/bysquare.git"
60
- },
61
- "keywords": [
62
- "pay by square",
63
- "by square",
64
- "paybysquare",
65
- "bysquare",
66
- "payments",
67
- "qr-string",
68
- "qr",
69
- "cli"
70
- ]
67
+ }
71
68
  }
@@ -1,10 +1,19 @@
1
1
  import { DataModel } from "./index.js";
2
2
  /**
3
- * @see 3.14. Generating by square Code
3
+ * Generating by square Code
4
+ *
5
+ * @see 3.14.
4
6
  */
5
- export declare function serialize(qr: string): DataModel;
7
+ export declare function deserialize(qr: string): DataModel;
8
+ export declare class DecodeError extends Error {
9
+ cause: Error;
10
+ name: string;
11
+ constructor(cause: Error, msg?: string);
12
+ }
6
13
  /**
7
- * @see 3.16. Decoding client data from QR Code 2005 symbol
14
+ * Decoding client data from QR Code 2005 symbol
15
+ *
16
+ * @see 3.16.
8
17
  */
9
18
  export declare function parse(qr: string): DataModel;
10
19
  /**