bysquare 1.3.3 → 2.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/lib/parse.d.ts CHANGED
@@ -1,21 +1,16 @@
1
- /// <reference types="node" />
2
- import { ParsedModel } from "./index";
1
+ import { DataModel } from "./index.js";
3
2
  /**
4
3
  * @see 3.14. Generating by square Code
5
4
  */
6
- export declare function assemble(tabbed: string): ParsedModel;
5
+ export declare function buildModel(qr: string): DataModel;
7
6
  /**
8
7
  * @see 3.16. Decoding client data from QR Code 2005 symbol
9
8
  */
10
- export declare function parse(qr: string): Promise<ParsedModel>;
9
+ export declare function parse(qr: string): Promise<DataModel>;
11
10
  /**
12
- * @see 3.13. Alphanumeric conversion using Base32hex
13
- */
14
- export declare function inverseAlphanumericConversion(qr: string): Buffer;
15
- /**
16
- * Simple binary header detector
11
+ * Detect if qr string contains bysquare header.
17
12
  *
18
- * The Bysquare header is pretty useless, so the detection isn't as reliable as
19
- * I'd like
13
+ * Bysquare header does not have too much information, therefore it is
14
+ * not very reliable, there is room for improvement for the future.
20
15
  */
21
16
  export declare function detect(qr: string): boolean;
package/lib/parse.js CHANGED
@@ -1,163 +1,128 @@
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 __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;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.detect = exports.inverseAlphanumericConversion = exports.parse = exports.assemble = void 0;
27
- const lzma = __importStar(require("lzma-native"));
28
- const index_1 = require("./index");
29
- const FIELD_INVOICE = 0;
30
- const FIELD_PAYMENT_COUNT = 1;
31
- const FIELD_PAYMENT_OPTIONS = 2;
32
- const FIELD_BANK_ACCOUNT_COUNT = 11;
1
+ import lzma from "lzma-native";
2
+ import { base32hex } from "rfc4648";
3
+ import { PaymentOptions } from "./index.js";
4
+ function cleanEmptyProps(obj) {
5
+ Object.keys(obj).forEach((key) => {
6
+ if (typeof obj[key] === 'undefined') {
7
+ delete obj[key];
8
+ }
9
+ });
10
+ }
33
11
  /**
34
12
  * @see 3.14. Generating by square Code
35
13
  */
36
- function assemble(tabbed) {
37
- const fields = tabbed
14
+ export function buildModel(qr) {
15
+ const intermediate = qr
38
16
  .split("\t")
39
17
  /** The end of the qr-string might contain a NULL-terminated string */
40
18
  .map((entry) => entry.replace("\x00", ""));
41
- const invoiceId = fields[FIELD_INVOICE];
19
+ const invoiceId = intermediate.shift();
42
20
  const output = {
43
21
  invoiceId: invoiceId?.length ? invoiceId : undefined,
44
22
  payments: []
45
23
  };
46
- const paymentsCount = Number(fields[FIELD_PAYMENT_COUNT]);
47
- const paymentOptions = Number(fields[FIELD_PAYMENT_OPTIONS]);
48
- const bankAccountsCount = Number(fields[FIELD_BANK_ACCOUNT_COUNT]);
49
- for (let i = 0; i < paymentsCount; i++) {
50
- const paymentFieldCount = 8;
51
- const paymentFieldStart = 3 + (i * paymentFieldCount);
52
- const paymentFieldEnd = 11 + (i * paymentFieldCount);
53
- const payment = fields.slice(paymentFieldStart, paymentFieldEnd);
54
- const [ammount, currencyCode, paymentDueDate, variableSymbol, constantSymbol, specificSymbol, originatorsReferenceInformation, paymentNote] = payment;
55
- output.payments.push({
56
- amount: ammount.length ? Number(ammount) : undefined,
57
- currencyCode: currencyCode,
58
- paymentDueDate: paymentDueDate?.length ? paymentDueDate : undefined,
59
- variableSymbol: variableSymbol?.length ? variableSymbol : undefined,
60
- constantSymbol: constantSymbol?.length ? constantSymbol : undefined,
61
- specificSymbol: specificSymbol?.length ? specificSymbol : undefined,
62
- originatorsReferenceInformation: originatorsReferenceInformation?.length ? originatorsReferenceInformation : undefined,
63
- paymentNote: paymentNote?.length ? paymentNote : undefined,
24
+ const paymentslen = Number(intermediate.shift());
25
+ for (let i = 0; i < paymentslen; i++) {
26
+ const paymentOptions = intermediate.shift();
27
+ const ammount = intermediate.shift();
28
+ const currency = intermediate.shift();
29
+ const dueDate = intermediate.shift();
30
+ const variables = intermediate.shift();
31
+ const constants = intermediate.shift();
32
+ const specifics = intermediate.shift();
33
+ const originatorRefInfo = intermediate.shift();
34
+ const paymentNote = intermediate.shift();
35
+ let payment = {
36
+ type: Number(paymentOptions),
64
37
  bankAccounts: [],
65
- });
66
- for (let j = 0; j < Number(bankAccountsCount); j++) {
67
- const bankAccountFieldCount = 2;
68
- const bankAccountFieldStart = 12 + (j * bankAccountFieldCount);
69
- const bankAccountFieldEnd = 14 + (j * bankAccountFieldCount);
70
- const bankAccounts = fields.slice(bankAccountFieldStart, bankAccountFieldEnd);
71
- const [iban, bic] = bankAccounts;
72
- if (iban?.length === 0) {
38
+ amount: ammount?.length ? Number(ammount) : undefined,
39
+ currencyCode: currency,
40
+ paymentDueDate: dueDate?.length ? dueDate : undefined,
41
+ variableSymbol: variables?.length ? variables : undefined,
42
+ constantSymbol: constants?.length ? constants : undefined,
43
+ specificSymbol: specifics?.length ? specifics : undefined,
44
+ originatorRefInfo: originatorRefInfo?.length ? originatorRefInfo : undefined,
45
+ paymentNote: paymentNote?.length ? paymentNote : undefined,
46
+ };
47
+ const accountslen = Number(intermediate.shift());
48
+ for (let j = 0; j < accountslen; j++) {
49
+ const iban = intermediate.shift();
50
+ if (iban === undefined || iban.length === 0) {
73
51
  throw new Error("Missing IBAN");
74
52
  }
75
- output.payments[i].bankAccounts.push({
53
+ const bic = intermediate.shift();
54
+ const account = {
76
55
  iban: iban,
77
- bic: bic?.length ? bic : undefined
78
- });
56
+ bic: bic?.length ? bic : undefined,
57
+ };
58
+ cleanEmptyProps(account);
59
+ payment.bankAccounts.push(account);
79
60
  }
80
- switch (paymentOptions) {
81
- case index_1.PaymentOptions.PaymentOrder:
61
+ intermediate.shift(); // StandingOrderExt
62
+ intermediate.shift(); // DirectDebitExt
63
+ // narrowing payment type
64
+ switch (payment.type) {
65
+ case PaymentOptions.PaymentOrder:
82
66
  break;
83
- case index_1.PaymentOptions.StandingOrder:
84
- const standingOrderFieldCount = 4;
85
- const standingOrderFieldStart = 15 + (i * standingOrderFieldCount);
86
- const standingOrderFieldEnd = 20 + (i * standingOrderFieldCount);
87
- const standingOrder = fields.slice(standingOrderFieldStart, standingOrderFieldEnd);
88
- const [day, month, periodicity, lastDate] = standingOrder;
89
- output.payments[i].standingOrder = {
90
- day: day?.length ? Number(day) : undefined,
91
- month: month?.length ? Number(month) : undefined,
92
- periodicity: periodicity?.length ? periodicity : undefined,
93
- lastDate: lastDate?.length ? lastDate : undefined
67
+ case PaymentOptions.StandingOrder:
68
+ payment = {
69
+ ...payment,
70
+ day: Number(intermediate.shift()),
71
+ month: Number(intermediate.shift()),
72
+ periodicity: intermediate.shift(),
73
+ lastDate: intermediate.shift()
94
74
  };
95
75
  break;
96
- case index_1.PaymentOptions.DirectDebit:
97
- const directDebitFieldCount = 10;
98
- const directDebitFieldStart = 16 + (i * directDebitFieldCount);
99
- const directDebitFieldEnd = 27 + (i * directDebitFieldStart);
100
- const directDebit = fields.slice(directDebitFieldStart, directDebitFieldEnd);
101
- const [directDebitScheme, directDebitType, variableSymbol, specificSymbol, originatorsReferenceInformation, mandateId, creditorId, contractId, maxAmount, validTillDate,] = directDebit;
102
- output.payments[i].directDebit = {
103
- directDebitScheme: directDebitScheme.length ? Number(directDebitScheme) : undefined,
104
- directDebitType: directDebitType.length ? Number(directDebitType) : undefined,
105
- variableSymbol: variableSymbol.length ? variableSymbol : undefined,
106
- specificSymbol: specificSymbol.length ? specificSymbol : undefined,
107
- originatorsReferenceInformation: originatorsReferenceInformation.length ? originatorsReferenceInformation : undefined,
108
- mandateId: mandateId.length ? mandateId : undefined,
109
- creditorId: creditorId.length ? creditorId : undefined,
110
- contractId: contractId.length ? contractId : undefined,
111
- maxAmount: maxAmount.length ? Number(maxAmount) : undefined,
112
- validTillDate: validTillDate.length ? validTillDate : undefined
76
+ case PaymentOptions.DirectDebit:
77
+ payment = {
78
+ ...payment,
79
+ directDebitScheme: Number(intermediate.shift()),
80
+ directDebitType: Number(intermediate.shift()),
81
+ mandateId: intermediate.shift(),
82
+ creditorId: intermediate.shift(),
83
+ contractId: intermediate.shift(),
84
+ maxAmount: Number(intermediate.shift()),
85
+ validTillDate: intermediate.shift()
113
86
  };
114
- default:
115
- throw new Error("Unknown payment option");
116
- }
117
- }
118
- /** Beneficiary list bysquare v1.1 */
119
- for (let i = 0; i < paymentsCount; i++) {
120
- let beneficiary = [];
121
- const offset = (i || 1) * (2 * bankAccountsCount) - 2;
122
- switch (paymentOptions) {
123
- case index_1.PaymentOptions.PaymentOrder:
124
- beneficiary = fields.slice(16 + offset, 20 + offset);
125
87
  break;
126
- case index_1.PaymentOptions.StandingOrder:
127
- beneficiary = fields.slice(20 + offset, 24 + offset);
128
- break;
129
- case index_1.PaymentOptions.DirectDebit:
130
- beneficiary = fields.slice(25 + offset, 29 + offset);
88
+ default:
131
89
  break;
132
90
  }
133
- if (beneficiary.length === 0) {
134
- break;
135
- }
136
- const [name, addressLine1, addressLine2] = beneficiary;
137
- /**
138
- * The list of recipients is optional, if we find a missing record, the
139
- * stream ends
140
- */
141
- if (!name && !addressLine1 && !addressLine2) {
142
- break;
91
+ cleanEmptyProps(payment);
92
+ output.payments.push(payment);
93
+ }
94
+ for (let i = 0; i < paymentslen; i++) {
95
+ const name = intermediate.shift();
96
+ const addressLine1 = intermediate.shift();
97
+ const addressLine2 = intermediate.shift();
98
+ if (Boolean(name) || Boolean(addressLine1) || Boolean(addressLine2)) {
99
+ const beneficiary = {
100
+ name: name?.length ? name : undefined,
101
+ street: addressLine1?.length ? addressLine1 : undefined,
102
+ city: addressLine2?.length ? addressLine2 : undefined,
103
+ };
104
+ cleanEmptyProps(beneficiary);
105
+ output.payments[i].beneficiary = beneficiary;
143
106
  }
144
- output.payments[i].beneficiary = {
145
- name: name?.length ? name : undefined,
146
- addressLine1: addressLine1?.length ? addressLine1 : undefined,
147
- addressLine2: addressLine2?.length ? addressLine2 : undefined
148
- };
149
107
  }
150
108
  return output;
151
109
  }
152
- exports.assemble = assemble;
153
110
  /**
154
111
  * @see 3.16. Decoding client data from QR Code 2005 symbol
155
112
  */
156
- function parse(qr) {
157
- const inversed = inverseAlphanumericConversion(qr);
158
- const _headerBysquare = inversed.slice(0, 2);
159
- const _headerCompression = inversed.slice(2, 4);
160
- const compressedData = inversed.slice(4);
113
+ export function parse(qr) {
114
+ try {
115
+ var decoded = base32hex.parse(qr, {
116
+ out: Buffer,
117
+ loose: true
118
+ });
119
+ }
120
+ catch {
121
+ throw new Error("Unable to parse QR");
122
+ }
123
+ // const bysquareHeader = decoded.subarray(0, 2)
124
+ // const compressionHeader = decoded.subarray(2, 4)
125
+ const compressedData = decoded.subarray(4);
161
126
  // @ts-ignore: Missing decored types
162
127
  const decoder = lzma.createStream("rawDecoder", {
163
128
  synchronous: true,
@@ -166,57 +131,44 @@ function parse(qr) {
166
131
  });
167
132
  return new Promise((resolve, reject) => {
168
133
  decoder
169
- .on("error", reject)
170
134
  .on("data", (decompress) => {
171
- const _crc32 = decompress.slice(0, 4);
172
- const tabbed = decompress.slice(4).toString();
173
- const model = assemble(tabbed);
174
- resolve(model);
135
+ // const crc32: Buffer = decompress.subarray(0, 4)
136
+ const tabbed = decompress.subarray(4).toString();
137
+ resolve(buildModel(tabbed));
175
138
  })
139
+ .on("error", reject)
176
140
  .write(compressedData, (error) => {
177
141
  error && reject(error);
178
142
  decoder.end();
179
143
  });
180
144
  });
181
145
  }
182
- exports.parse = parse;
183
146
  /**
184
- * @see 3.13. Alphanumeric conversion using Base32hex
185
- */
186
- function inverseAlphanumericConversion(qr) {
187
- const binary = [...qr].reduce((acc, char) => {
188
- acc += index_1.SUBST.indexOf(char).toString(2).padStart(5, "0");
189
- return acc;
190
- }, "");
191
- let bytes = [];
192
- for (let nth = 0, leftCount = 0; binary.length > leftCount; nth++) {
193
- /** string representation of 8-bits */
194
- const slice = binary.slice(leftCount, (leftCount += 8));
195
- const byte = parseInt(slice, 2);
196
- bytes[nth] = byte;
197
- }
198
- return Buffer.from(bytes);
199
- }
200
- exports.inverseAlphanumericConversion = inverseAlphanumericConversion;
201
- /**
202
- * Simple binary header detector
147
+ * Detect if qr string contains bysquare header.
203
148
  *
204
- * The Bysquare header is pretty useless, so the detection isn't as reliable as
205
- * I'd like
149
+ * Bysquare header does not have too much information, therefore it is
150
+ * not very reliable, there is room for improvement for the future.
206
151
  */
207
- function detect(qr) {
208
- const inversed = inverseAlphanumericConversion(qr);
209
- if (inversed.byteLength < 2) {
152
+ export function detect(qr) {
153
+ try {
154
+ var parsed = base32hex.parse(qr, {
155
+ out: Buffer,
156
+ loose: true
157
+ });
158
+ }
159
+ catch {
160
+ throw new Error("Unable to parse QR string, invalid data");
161
+ }
162
+ if (parsed.byteLength < 2) {
210
163
  return false;
211
164
  }
212
- const headerBysquare = inversed.slice(0, 2);
165
+ const headerBysquare = parsed.subarray(0, 2);
213
166
  return [...headerBysquare.toString("hex")]
214
167
  .map((nibble) => parseInt(nibble, 16))
215
168
  .every((nibble, index) => {
216
- if (index === 1 /** version */) {
169
+ if ( /** version */index === 1) {
217
170
  return 0 >= nibble && nibble <= 1;
218
171
  }
219
172
  return 0 <= nibble && nibble <= 15;
220
173
  });
221
174
  }
222
- exports.detect = detect;