bysquare 2.0.1 → 2.2.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/lib/cjs/parse.js CHANGED
@@ -1,71 +1,106 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
4
24
  };
5
25
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.detect = exports.parse = exports.buildModel = void 0;
7
- const lzma_native_1 = __importDefault(require("lzma-native"));
26
+ exports.detect = exports.parse = exports.DecodeError = exports.deserialize = void 0;
27
+ const lzma = __importStar(require("lzma1"));
8
28
  const rfc4648_1 = require("rfc4648");
9
29
  const index_js_1 = require("./index.js");
10
30
  function cleanEmptyProps(obj) {
11
31
  Object.keys(obj).forEach((key) => {
12
- if (typeof obj[key] === 'undefined') {
32
+ if (typeof obj[key] === "undefined") {
13
33
  delete obj[key];
14
34
  }
15
35
  });
16
36
  }
17
37
  /**
18
- * @see 3.14. Generating by square Code
38
+ * Generating by square Code
39
+ *
40
+ * @see 3.14.
19
41
  */
20
- function buildModel(qr) {
21
- const intermediate = qr
22
- .split("\t")
23
- /** The end of the qr-string might contain a NULL-terminated string */
24
- .map((entry) => entry.replace("\x00", ""));
25
- const invoiceId = intermediate.shift();
42
+ function deserialize(qr) {
43
+ const serialized = qr.split("\t");
44
+ const invoiceId = serialized.shift();
26
45
  const output = {
27
46
  invoiceId: invoiceId?.length ? invoiceId : undefined,
28
47
  payments: []
29
48
  };
30
- const paymentslen = Number(intermediate.shift());
49
+ const paymentslen = Number(serialized.shift());
31
50
  for (let i = 0; i < paymentslen; i++) {
32
- const paymentOptions = intermediate.shift();
33
- const ammount = intermediate.shift();
34
- const currency = intermediate.shift();
35
- const dueDate = intermediate.shift();
36
- const variables = intermediate.shift();
37
- const constants = intermediate.shift();
38
- const specifics = intermediate.shift();
39
- const originatorRefInfo = intermediate.shift();
40
- const paymentNote = intermediate.shift();
51
+ const paymentOptions = serialized.shift();
52
+ const ammount = serialized.shift();
53
+ const currency = serialized.shift();
54
+ const dueDate = serialized.shift();
55
+ const variables = serialized.shift();
56
+ const constants = serialized.shift();
57
+ const specifics = serialized.shift();
58
+ const originatorRefInfo = serialized.shift();
59
+ const paymentNote = serialized.shift();
41
60
  let payment = {
42
- type: Number(paymentOptions),
43
61
  bankAccounts: [],
44
- amount: ammount?.length ? Number(ammount) : undefined,
62
+ type: Number(paymentOptions),
45
63
  currencyCode: currency,
46
- paymentDueDate: dueDate?.length ? dueDate : undefined,
47
- variableSymbol: variables?.length ? variables : undefined,
48
- constantSymbol: constants?.length ? constants : undefined,
49
- specificSymbol: specifics?.length ? specifics : undefined,
50
- originatorRefInfo: originatorRefInfo?.length ? originatorRefInfo : undefined,
51
- paymentNote: paymentNote?.length ? paymentNote : undefined,
64
+ amount: ammount?.length
65
+ ? Number(ammount)
66
+ : undefined,
67
+ paymentDueDate: dueDate?.length
68
+ ? dueDate
69
+ : undefined,
70
+ variableSymbol: variables?.length
71
+ ? variables
72
+ : undefined,
73
+ constantSymbol: constants?.length
74
+ ? constants
75
+ : undefined,
76
+ specificSymbol: specifics?.length
77
+ ? specifics
78
+ : undefined,
79
+ originatorRefInfo: originatorRefInfo?.length
80
+ ? originatorRefInfo
81
+ : undefined,
82
+ paymentNote: paymentNote?.length
83
+ ? paymentNote
84
+ : undefined
52
85
  };
53
- const accountslen = Number(intermediate.shift());
86
+ const accountslen = Number(serialized.shift());
54
87
  for (let j = 0; j < accountslen; j++) {
55
- const iban = intermediate.shift();
88
+ const iban = serialized.shift();
56
89
  if (iban === undefined || iban.length === 0) {
57
90
  throw new Error("Missing IBAN");
58
91
  }
59
- const bic = intermediate.shift();
92
+ const bic = serialized.shift();
60
93
  const account = {
61
94
  iban: iban,
62
- bic: bic?.length ? bic : undefined,
95
+ bic: bic?.length
96
+ ? bic
97
+ : undefined
63
98
  };
64
99
  cleanEmptyProps(account);
65
100
  payment.bankAccounts.push(account);
66
101
  }
67
- intermediate.shift(); // StandingOrderExt
68
- intermediate.shift(); // DirectDebitExt
102
+ serialized.shift(); // StandingOrderExt
103
+ serialized.shift(); // DirectDebitExt
69
104
  // narrowing payment type
70
105
  switch (payment.type) {
71
106
  case index_js_1.PaymentOptions.PaymentOrder:
@@ -73,22 +108,22 @@ function buildModel(qr) {
73
108
  case index_js_1.PaymentOptions.StandingOrder:
74
109
  payment = {
75
110
  ...payment,
76
- day: Number(intermediate.shift()),
77
- month: Number(intermediate.shift()),
78
- periodicity: intermediate.shift(),
79
- lastDate: intermediate.shift()
111
+ day: Number(serialized.shift()),
112
+ month: Number(serialized.shift()),
113
+ periodicity: serialized.shift(),
114
+ lastDate: serialized.shift()
80
115
  };
81
116
  break;
82
117
  case index_js_1.PaymentOptions.DirectDebit:
83
118
  payment = {
84
119
  ...payment,
85
- directDebitScheme: Number(intermediate.shift()),
86
- directDebitType: Number(intermediate.shift()),
87
- mandateId: intermediate.shift(),
88
- creditorId: intermediate.shift(),
89
- contractId: intermediate.shift(),
90
- maxAmount: Number(intermediate.shift()),
91
- validTillDate: intermediate.shift()
120
+ directDebitScheme: Number(serialized.shift()),
121
+ directDebitType: Number(serialized.shift()),
122
+ mandateId: serialized.shift(),
123
+ creditorId: serialized.shift(),
124
+ contractId: serialized.shift(),
125
+ maxAmount: Number(serialized.shift()),
126
+ validTillDate: serialized.shift()
92
127
  };
93
128
  break;
94
129
  default:
@@ -98,14 +133,20 @@ function buildModel(qr) {
98
133
  output.payments.push(payment);
99
134
  }
100
135
  for (let i = 0; i < paymentslen; i++) {
101
- const name = intermediate.shift();
102
- const addressLine1 = intermediate.shift();
103
- const addressLine2 = intermediate.shift();
136
+ const name = serialized.shift();
137
+ const addressLine1 = serialized.shift();
138
+ const addressLine2 = serialized.shift();
104
139
  if (Boolean(name) || Boolean(addressLine1) || Boolean(addressLine2)) {
105
140
  const beneficiary = {
106
- name: name?.length ? name : undefined,
107
- street: addressLine1?.length ? addressLine1 : undefined,
108
- city: addressLine2?.length ? addressLine2 : undefined,
141
+ name: name?.length
142
+ ? name
143
+ : undefined,
144
+ street: addressLine1?.length
145
+ ? addressLine1
146
+ : undefined,
147
+ city: addressLine2?.length
148
+ ? addressLine2
149
+ : undefined
109
150
  };
110
151
  cleanEmptyProps(beneficiary);
111
152
  output.payments[i].beneficiary = beneficiary;
@@ -113,42 +154,113 @@ function buildModel(qr) {
113
154
  }
114
155
  return output;
115
156
  }
116
- exports.buildModel = buildModel;
157
+ exports.deserialize = deserialize;
158
+ /**
159
+ * LZMA compression properties from the byte
160
+ *
161
+ * @param props 1-byte size
162
+ */
163
+ function LzmaPropertiesDecoder(props) {
164
+ const byte = props[0];
165
+ return {
166
+ lc: byte >> 5,
167
+ lp: byte >> 2 & 0b0111,
168
+ pb: byte & 0b0011
169
+ };
170
+ }
171
+ function calcLzmaDictionarySize(props) {
172
+ const dictionarySize = new ArrayBuffer(4);
173
+ new DataView(dictionarySize).setUint32(0, Math.pow(2, props.pb + props.lp));
174
+ return new Uint8Array(dictionarySize);
175
+ }
117
176
  /**
118
- * @see 3.16. Decoding client data from QR Code 2005 symbol
177
+ * The function uses bit-shifting and masking to convert the first two bytes of
178
+ * the input header array into four nibbles representing the bysquare header
179
+ * values.
180
+ *
181
+ * @param header 2-bytes sie
182
+ */
183
+ function bysquareHeaderDecoder(header) {
184
+ const bytes = (header[0] << 8) | header[1];
185
+ const bysquareType = bytes >> 12;
186
+ const version = (bytes >> 8) & 0b1111;
187
+ const documentType = (bytes >> 4) & 0b1111;
188
+ const reserved = bytes & 0b1111;
189
+ return {
190
+ bysquareType,
191
+ version,
192
+ documentType,
193
+ reserved
194
+ };
195
+ }
196
+ class DecodeError extends Error {
197
+ cause;
198
+ name = "DecodeError";
199
+ constructor(cause, msg) {
200
+ super(msg);
201
+ this.cause = cause;
202
+ }
203
+ }
204
+ exports.DecodeError = DecodeError;
205
+ /**
206
+ * Decoding client data from QR Code 2005 symbol
207
+ *
208
+ * @see 3.16.
119
209
  */
120
210
  function parse(qr) {
121
211
  try {
122
- var decoded = rfc4648_1.base32hex.parse(qr, {
123
- out: Buffer,
212
+ var bytes = rfc4648_1.base32hex.parse(qr, {
124
213
  loose: true
125
214
  });
126
215
  }
127
- catch {
128
- throw new Error("Unable to parse QR");
216
+ catch (error) {
217
+ throw new DecodeError(error, "Unable to decode QR string base32hex encoding");
129
218
  }
130
- // const bysquareHeader = decoded.subarray(0, 2)
131
- // const compressionHeader = decoded.subarray(2, 4)
132
- const compressedData = decoded.subarray(4);
133
- // @ts-ignore: Missing decored types
134
- const decoder = lzma_native_1.default.createStream("rawDecoder", {
135
- synchronous: true,
136
- // @ts-ignore: Missing filter types
137
- filters: [{ id: lzma_native_1.default.FILTER_LZMA1 }]
138
- });
139
- return new Promise((resolve, reject) => {
140
- decoder
141
- .on("data", (decompress) => {
142
- // const crc32: Buffer = decompress.subarray(0, 4)
143
- const tabbed = decompress.subarray(4).toString();
144
- resolve(buildModel(tabbed));
145
- })
146
- .on("error", reject)
147
- .write(compressedData, (error) => {
148
- error && reject(error);
149
- decoder.end();
150
- });
151
- });
219
+ const bysquareHeader = bytes.slice(0, 2);
220
+ const { version } = bysquareHeaderDecoder(bysquareHeader);
221
+ if ((version > 1 /* Version["1.1.0"] */)) {
222
+ throw new Error("Unsupported Bysquare version");
223
+ }
224
+ /**
225
+ * The process of decompressing data requires the addition of an LZMA header
226
+ * to the compressed data. This header is necessary for the decompression
227
+ * algorithm to properly interpret and extract the original uncompressed
228
+ * data. Bysquare only store properties
229
+ *
230
+ * <----------------------- 13-bytes ----------------------->
231
+ *
232
+ * +------------+----+----+----+----+--+--+--+--+--+--+--+--+
233
+ * | Properties | Dictionary Size | Uncompressed Size |
234
+ * +------------+----+----+----+----+--+--+--+--+--+--+--+--+
235
+ */
236
+ const lzmaProperties = bytes.slice(2, 3);
237
+ const decodedProps = LzmaPropertiesDecoder(lzmaProperties);
238
+ const dictSize = calcLzmaDictionarySize(decodedProps);
239
+ const header = new Uint8Array([
240
+ lzmaProperties[0],
241
+ ...dictSize,
242
+ /** Uncompressed size, this value indicates that size is unknown */
243
+ ...[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
244
+ ]);
245
+ const payload = bytes.slice(4);
246
+ const body = new Uint8Array([
247
+ ...header,
248
+ ...payload
249
+ ]);
250
+ try {
251
+ var decompressed = new Uint8Array(lzma.decompress(body));
252
+ }
253
+ catch (error) {
254
+ throw new DecodeError(error, "LZMA decompression failed");
255
+ }
256
+ const dataLength = bytes.slice(3, 4);
257
+ if (dataLength[0] !== decompressed.length) {
258
+ throw new Error("The length of the data after decompression is not as expected.");
259
+ }
260
+ const _checksum = decompressed.slice(0, 4);
261
+ const decompressedBody = decompressed.slice(4);
262
+ const decoded = new TextDecoder("utf-8").decode(decompressedBody);
263
+ return deserialize(decoded);
152
264
  }
153
265
  exports.parse = parse;
154
266
  /**
@@ -160,24 +272,24 @@ exports.parse = parse;
160
272
  function detect(qr) {
161
273
  try {
162
274
  var parsed = rfc4648_1.base32hex.parse(qr, {
163
- out: Buffer,
164
275
  loose: true
165
276
  });
166
277
  }
167
278
  catch {
168
- throw new Error("Unable to parse QR string, invalid data");
279
+ throw new Error("Invalid data, Unable to decode base32hex QR string");
169
280
  }
170
281
  if (parsed.byteLength < 2) {
171
282
  return false;
172
283
  }
173
- const headerBysquare = parsed.subarray(0, 2);
174
- return [...headerBysquare.toString("hex")]
175
- .map((nibble) => parseInt(nibble, 16))
284
+ const bysquareHeader = parsed.subarray(0, 2);
285
+ const { bysquareType, version, documentType, reserved } = bysquareHeaderDecoder(bysquareHeader);
286
+ const isValid = [bysquareType, version, documentType, reserved]
176
287
  .every((nibble, index) => {
177
- if ( /** version */index === 1) {
178
- return 0 >= nibble && nibble <= 1;
288
+ if (index === 1) {
289
+ return nibble <= 1 /* Version["1.1.0"] */;
179
290
  }
180
- return 0 <= nibble && nibble <= 15;
291
+ return 0x00 <= nibble && nibble <= 0x0F;
181
292
  });
293
+ return isValid;
182
294
  }
183
295
  exports.detect = detect;
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Mapping semantic version to encoded version number, header 4-bits
3
+ *
4
+ * It's a bit silly to limit the version number to 4-bit, if they keep
5
+ * increasing the version number, the latest possible mapped value is 16
6
+ */
7
+ export declare const enum Version {
8
+ /**
9
+ * 2013-02-22
10
+ * Created this document from original by square specifications
11
+ */
12
+ "1.0.0" = 0,
13
+ /**
14
+ * 2015-06-24
15
+ * Added fields for beneficiary name and address
16
+ */
17
+ "1.1.0" = 1
18
+ }
1
19
  /**
2
20
  * Selection of one or more months on which payment occurs. This is enabled
3
21
  * only if periodicity is set to one of the following value: “Weekly,
package/lib/mjs/cli.js CHANGED
@@ -3,52 +3,34 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { createInterface } from "node:readline";
5
5
  import { generate } from "./generate.js";
6
- if (process.stdin.isTTY) {
7
- // bysquare "file"
6
+ if /** bysquare "file" */ (process.stdin.isTTY) {
8
7
  handleInput(process.argv[2]);
9
- }
8
+ } /** bysquare <<< "data" */
10
9
  else {
11
- // echo "data" | bysquare
12
10
  ;
13
11
  (async () => {
14
12
  const stdin = await handleStdin();
15
- const qr = await fromJsonString(stdin).catch((e) => {
16
- console.error(e);
17
- process.exit(1);
18
- });
19
- console.log(qr);
13
+ console.log(fromJsonString(stdin));
20
14
  process.exit(0);
21
15
  })();
22
16
  }
23
- async function handleInput(input) {
17
+ function handleInput(input) {
24
18
  if (input === undefined || input === "-h" || input === "--help") {
25
19
  console.log(help());
26
20
  process.exit(0);
27
21
  }
28
22
  if (existsSync(process.argv[2])) {
29
23
  const file = readFileSync(process.argv[2], "utf8");
30
- const qr = await fromJsonString(file).catch((e) => {
31
- console.error(e);
32
- process.exit(1);
33
- });
34
- console.log(qr);
24
+ console.log(fromJsonString(file));
35
25
  }
36
26
  else {
37
27
  console.error(`File ${process.argv[2]} doesn't exists`);
38
28
  process.exit(1);
39
29
  }
40
30
  }
41
- async function fromJsonString(stdin) {
42
- return new Promise((resolve, reject) => {
43
- try {
44
- const data = JSON.parse(stdin);
45
- const qrString = generate(data);
46
- resolve(qrString);
47
- }
48
- catch (e) {
49
- reject(e);
50
- }
51
- });
31
+ function fromJsonString(stdin) {
32
+ const data = JSON.parse(stdin);
33
+ return generate(data);
52
34
  }
53
35
  async function handleStdin() {
54
36
  const readline = createInterface({
@@ -85,18 +67,17 @@ function help() {
85
67
  "If <file> is omitted, reads from stdin.",
86
68
  "",
87
69
  "Examples:",
88
- " bysquare ./example.json",
89
- "",
90
- " echo ",
91
- " {",
92
- ' "IBAN": "SK9611000000002918599669"',
93
- ' "Amount": 100.0',
94
- ' "CurrencyCode": "EUR"',
95
- ' "VariableSymbol": "123"',
96
- ' "Payments": 1',
97
- ' "PaymentOptions": 1',
98
- ' "BankAccounts": 1',
99
- " }'",
100
- " | bysquare"
70
+ " bysquare <<< \"{",
71
+ " \"invoiceId\": \"random-id\",",
72
+ " \"payments\": [",
73
+ " {",
74
+ " \"type\": 1,",
75
+ " \"amount\": 100.0,",
76
+ " \"bankAccounts\": [{ \"iban\": \"SK9611000000002918599669\" }],",
77
+ " \"currencyCode\": \"EUR\",",
78
+ " \"variableSymbol\": \"123\"",
79
+ " }",
80
+ " ]",
81
+ " }\""
101
82
  ].join("\n");
102
83
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { DataModel } from "./types.js";
3
2
  /**
4
3
  * Returns a 2 byte buffer that represents the header of the bysquare
@@ -13,36 +12,45 @@ 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
- ]): Buffer;
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 prepareCompression(model: DataModel): Buffer;
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 toIntermediate(data: DataModel): string;
42
+ export declare function serialize(data: DataModel): string;
41
43
  type Options = {
44
+ /**
45
+ * Many banking apps do not support diacritics, which results in errors when
46
+ * serializing data from QR codes.
47
+ *
48
+ * @default true
49
+ */
42
50
  deburr: boolean;
43
51
  };
44
52
  /**
45
53
  * Generate QR string ready for encoding into text QR code
46
54
  */
47
- export declare function generate(model: DataModel, options?: Options): Promise<string>;
55
+ export declare function generate(model: DataModel, options?: Options): string;
48
56
  export {};