bysquare 2.11.0 → 2.12.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
@@ -1,24 +1,20 @@
1
1
  # bysquare
2
2
 
3
- Simple JavaScript library to encode and decode "PAY by square" string.
3
+ "PAY by square" is a national standard for QR code payments that was adopted by
4
+ the Slovak Banking Association in 2013. It is incorporated into a variety of
5
+ invoices, reminders and other payment regulations.
4
6
 
5
- **What is `PAY by square`?**
7
+ ## Why
6
8
 
7
- It's a national standard for QR code payments that was adopted by the Slovak
8
- Banking Association in 2013. It is incorporated into a variety of invoices,
9
- reminders and other payment regulations.
10
-
11
- **Can I generate an image?**
12
-
13
- This library doesn't have a specific opinion and how the QR code string is
14
- transformed into images depends on how you implement it. See
15
- [examples](./docs/examples/).
9
+ It's simple, I couldn't find any implementation of "PAY by square" standard for
10
+ JavaScript, so I decided to create one and share it with the community to help
11
+ individuals and businesses to create QR codes for their invoices.
16
12
 
17
13
  ## Features
18
14
 
19
- - Encode data to qr string
20
- - Decode data to json
21
- - Detect bysquare from qr string
15
+ - TypeScript support
16
+ - Compatible with Slovak banking apps
17
+ - Runtime-independent JavaScript implementation
22
18
 
23
19
  ## Installation
24
20
 
@@ -30,103 +26,97 @@ transformed into images depends on how you implement it. See
30
26
  [mozzila-esm]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
31
27
  [mozzila-import]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
32
28
 
33
- ### npm registry
29
+ ### npm
34
30
 
35
31
  ```sh
36
- npm install bysquare
32
+ $ npm install bysquare
37
33
  ```
38
34
 
39
- ### CLI Node `v18+`
35
+ ### browser
40
36
 
41
- ```sh
42
- npm install --global bysquare
37
+ ```html
38
+ <script type="module">
39
+ import { encode, decode } from "https://esm.sh/bysquare@latest";
40
+ </script>
43
41
  ```
44
42
 
45
- ### deno
43
+ ## Usage
46
44
 
47
- Since `v1.28+` import from npm registry using `npm:` prefix.
45
+ ### Basic Usage
48
46
 
49
- ```ts
50
- import {
51
- decode,
52
- encode,
53
- } from "npm:bysquare@latest";
54
- ```
47
+ Simple helper functions to wrap encoding for most common use cases.
55
48
 
56
- ### Browser
49
+ - `simplePayment` - Encode simple payment data.
50
+ - `directDebit` - Encode direct debit data.
51
+ - `standingOrder` - Encode standing order data.
57
52
 
58
- ```html
59
- <script type="module">
60
- import { encode, decode } from "https://esm.sh/bysquare@latest";
61
- </script>
53
+ ```typescript
54
+ import { simplePayment } from "bysquare";
55
+
56
+ const qrstring = simplePayment({
57
+ amount: 100,
58
+ variableSymbol: "123456",
59
+ currencyCode: CurrencyCode.EUR,
60
+ iban: "SK9611000000002918599669",
61
+ });
62
62
  ```
63
63
 
64
- ## Usage
64
+ ### Adavanced usage
65
65
 
66
- ### Encode
66
+ For more complex data use `encode` and `decode` functions:
67
+
68
+ > [!NOTE]
69
+ > Encoded data are without diacritics
70
+ >
71
+ > The library removes all diacritics from the input data to ensure maximum
72
+ > compatibility, as not all banks support diacritics, which may lead to errors.
73
+ > If you need to retain diacritics, disable deburr option when encoding data -
74
+ > `encode(model, { deburr: false })`.
67
75
 
68
76
  ```ts
69
77
  import {
70
78
  CurrencyCode,
71
79
  DataModel,
80
+ decode,
72
81
  encode,
73
82
  PaymentOptions,
74
83
  } from "bysquare";
75
84
 
76
- // string ready to be encoded to QR
77
- const qrString = encode({
85
+ const data = {
78
86
  invoiceId: "random-id",
79
87
  payments: [
80
88
  {
81
89
  type: PaymentOptions.PaymentOrder,
82
- amount: 100.0,
83
- bankAccounts: [
84
- {
85
- iban: "SK9611000000002918599669",
86
- },
87
- ],
88
90
  currencyCode: CurrencyCode.EUR,
91
+ amount: 100.0,
89
92
  variableSymbol: "123",
93
+ paymentNote: "hello world",
94
+ bankAccounts: [{ iban: "SK9611000000002918599669" }],
95
+ // ...more fields
90
96
  },
91
97
  ],
92
- });
93
- ```
98
+ } satisfies DataModel;
94
99
 
95
- ### Decode
100
+ // Encode data to a QR string
101
+ const qrstring = encode(data);
96
102
 
97
- ```ts
98
- import { decode } from "bysquare";
99
-
100
- const model = decode(
101
- "0405QH8090IFU27IV0J6HGGLIOTIBVHNQQJQ6LAVGNBT363HR13JC6CB54HSI0KH9FCRASHNQBSKAQD2LJ4AU400UVKDNDPFRKLOBEVVVU0QJ000",
102
- );
103
-
104
- // {
105
- // invoiceId: "random-id",
106
- // payments: [
107
- // {
108
- // type: 1,
109
- // amount: 100.0,
110
- // bankAccounts: [
111
- // { iban: "SK9611000000002918599669" },
112
- // ],
113
- // currencyCode: "EUR",
114
- // variableSymbol: "123",
115
- // }
116
- // ]
117
- // }
118
- //
103
+ // Decode QR string back to the original data model
104
+ const model = decode(qrstring);
119
105
  ```
120
106
 
121
107
  ## CLI
122
108
 
109
+ ```sh
110
+ $ npm install --global bysquare
111
+ ```
112
+
123
113
  ### Encode
124
114
 
125
115
  Encode JSON or JSONL data from files and print the corresponding QR code.
126
116
 
127
117
  ```sh
128
- npx bysquare --encode file1.json file2.json...
129
- npx bysquare --encode file.jsonl
118
+ $ bysquare --encode file1.json file2.json...
119
+ $ bysquare --encode file.jsonl
130
120
  ```
131
121
 
132
122
  ### Decode
@@ -135,40 +125,14 @@ Decode the specified QR code string and print the corresponding JSON data. The
135
125
  qrstring argument should be a valid QR code string.
136
126
 
137
127
  ```sh
138
- npx bysquare --decode <qrstring>
128
+ $ bysquare --decode <qrstring>
139
129
  ```
140
130
 
141
131
  ## How it works
142
132
 
143
133
  ### Encoding sequence
144
134
 
145
- ![logic](./docs/uml/logic.svg)
146
-
147
- ## Platform support
148
-
149
- I mainly focus on LTS versions of Node.js and try to use the most idiomatic
150
- ECMAScript possible to avoid specific runtime coupling.
151
-
152
- This doesn't mean that the library won't work on older versions, but it might
153
- not be as reliable.
154
-
155
- As of `v1.28`, Deno now includes built-in support for npm modules and is ready
156
- to use without additional setup, showing its improved maturity.
157
-
158
- ### Node.js & Deno
159
-
160
- - Node.js `v18` and later.
161
- - Deno `v1.28` and later.
162
-
163
- ### Browser
164
-
165
- The latest version of Chrome, Firefox, and Safari.
166
-
167
- ## Troubleshooting & Recommendations
168
-
169
- ### Encoded data are without diacritics
170
-
171
- The library removes all diacritics from the input data to ensure maximum compatibility, as not all banks support diacritics, which may lead to errors. If you need to retain diacritics, disable deburr option when encoding data - `encode(model, { deburr: false })`.
135
+ <image src="./docs/logic.svg" alt="encode" width="500px">
172
136
 
173
137
  ## Related
174
138
 
package/dist/decode.d.ts CHANGED
@@ -1,15 +1,31 @@
1
1
  import { DataModel } from "./index.js";
2
+ export declare enum DecodeErrorMessage {
3
+ MissingIBAN = "IBAN is missing",
4
+ /**
5
+ * @description - find original LZMA error in extensions
6
+ */
7
+ LZMADecompressionFailed = "LZMA decompression failed",
8
+ /**
9
+ * @description - find found version in extensions
10
+ * @see {@link ./types#Version} for valid ranges
11
+ */
12
+ UnsupportedVersion = "Unsupported version"
13
+ }
14
+ export declare class DecodeError extends Error {
15
+ name: string;
16
+ extensions?: {
17
+ [name: string]: any;
18
+ };
19
+ constructor(message: DecodeErrorMessage, extensions?: {
20
+ [name: string]: any;
21
+ });
22
+ }
2
23
  /**
3
24
  * Generating by square Code
4
25
  *
5
26
  * @see 3.14.
6
27
  */
7
28
  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
- }
13
29
  /** @deprecated */
14
30
  export declare const parse: typeof decode;
15
31
  /**
@@ -21,7 +37,7 @@ export declare function decode(qr: string): DataModel;
21
37
  /**
22
38
  * Detect if qr string contains bysquare header.
23
39
  *
24
- * Bysquare header does not have too much information, therefore it is
25
- * not very reliable, there is room for improvement for the future.
40
+ * There is not magic header in the bysquare specification.
41
+ * Version is just 4 bites, so it is possible to have false positives.
26
42
  */
27
43
  export declare function detect(qr: string): boolean;
package/dist/decode.js CHANGED
@@ -1,6 +1,29 @@
1
1
  import { decompress } from "lzma1";
2
2
  import * as base32hex from "./base32hex.js";
3
3
  import { CurrencyCode, PaymentOptions, Version, } from "./index.js";
4
+ export var DecodeErrorMessage;
5
+ (function (DecodeErrorMessage) {
6
+ DecodeErrorMessage["MissingIBAN"] = "IBAN is missing";
7
+ /**
8
+ * @description - find original LZMA error in extensions
9
+ */
10
+ DecodeErrorMessage["LZMADecompressionFailed"] = "LZMA decompression failed";
11
+ /**
12
+ * @description - find found version in extensions
13
+ * @see {@link ./types#Version} for valid ranges
14
+ */
15
+ DecodeErrorMessage["UnsupportedVersion"] = "Unsupported version";
16
+ })(DecodeErrorMessage || (DecodeErrorMessage = {}));
17
+ export class DecodeError extends Error {
18
+ name = "DecodeError";
19
+ extensions;
20
+ constructor(message, extensions) {
21
+ super(message);
22
+ if (extensions) {
23
+ this.extensions = extensions;
24
+ }
25
+ }
26
+ }
4
27
  function cleanUndefined(obj) {
5
28
  Object.keys(obj).forEach((key) => {
6
29
  if (typeof obj[key] === "undefined") {
@@ -8,6 +31,12 @@ function cleanUndefined(obj) {
8
31
  }
9
32
  });
10
33
  }
34
+ function decodeNumber(value) {
35
+ return value?.length ? Number(value) : undefined;
36
+ }
37
+ function decodeString(value) {
38
+ return value?.length ? value : undefined;
39
+ }
11
40
  /**
12
41
  * Generating by square Code
13
42
  *
@@ -26,82 +55,62 @@ export function deserialize(qr) {
26
55
  const ammount = data.shift();
27
56
  const currency = data.shift();
28
57
  const dueDate = data.shift();
29
- const variables = data.shift();
30
- const constants = data.shift();
31
- const specifics = data.shift();
58
+ const variableSymbol = data.shift();
59
+ const constantSymbol = data.shift();
60
+ const specificSymbol = data.shift();
32
61
  const originatorRefInfo = data.shift();
33
62
  const paymentNote = data.shift();
34
63
  let payment = {
35
64
  bankAccounts: [],
36
65
  type: Number(paymentOptions),
37
66
  currencyCode: currency ?? CurrencyCode.EUR,
38
- amount: ammount?.length
39
- ? Number(ammount)
40
- : undefined,
41
- paymentDueDate: dueDate?.length
42
- ? dueDate
43
- : undefined,
44
- variableSymbol: variables?.length
45
- ? variables
46
- : undefined,
47
- constantSymbol: constants?.length
48
- ? constants
49
- : undefined,
50
- specificSymbol: specifics?.length
51
- ? specifics
52
- : undefined,
53
- originatorsReferenceInformation: originatorRefInfo?.length
54
- ? originatorRefInfo
55
- : undefined,
56
- paymentNote: paymentNote?.length
57
- ? paymentNote
58
- : undefined,
67
+ amount: Number(ammount),
68
+ paymentDueDate: dueDate || undefined,
69
+ variableSymbol: variableSymbol || undefined,
70
+ constantSymbol: constantSymbol || undefined,
71
+ specificSymbol: specificSymbol || undefined,
72
+ originatorsReferenceInformation: originatorRefInfo || undefined,
73
+ paymentNote: paymentNote || undefined,
59
74
  };
60
- const accountslen = Number(data.shift());
61
- for (let j = 0; j < accountslen; j++) {
75
+ const numberOfAccounts = Number(data.shift());
76
+ for (let j = 0; j < numberOfAccounts; j++) {
62
77
  const iban = data.shift();
63
78
  if (iban === undefined || iban.length === 0) {
64
- throw new Error("Missing IBAN");
79
+ throw new DecodeError(DecodeErrorMessage.MissingIBAN);
65
80
  }
66
81
  const bic = data.shift();
67
82
  const account = {
68
83
  iban: iban,
69
- bic: bic?.length
70
- ? bic
71
- : undefined,
84
+ bic: bic || undefined,
72
85
  };
73
86
  cleanUndefined(account);
74
87
  payment.bankAccounts.push(account);
75
88
  }
76
- data.shift(); // StandingOrderExt
77
- data.shift(); // DirectDebitExt
78
- // narrowing payment type
79
- switch (payment.type) {
80
- case PaymentOptions.PaymentOrder:
81
- break;
82
- case PaymentOptions.StandingOrder:
83
- payment = {
84
- ...payment,
85
- day: Number(data.shift()),
86
- month: Number(data.shift()),
87
- periodicity: data.shift(),
88
- lastDate: data.shift(),
89
- };
90
- break;
91
- case PaymentOptions.DirectDebit:
92
- payment = {
93
- ...payment,
94
- directDebitScheme: Number(data.shift()),
95
- directDebitType: Number(data.shift()),
96
- mandateId: data.shift(),
97
- creditorId: data.shift(),
98
- contractId: data.shift(),
99
- maxAmount: Number(data.shift()),
100
- validTillDate: data.shift(),
101
- };
102
- break;
103
- default:
104
- break;
89
+ const standingOrderExt = data.shift();
90
+ if (standingOrderExt === "1" && payment.type === PaymentOptions.StandingOrder) {
91
+ payment = {
92
+ ...payment,
93
+ day: decodeNumber(data.shift()),
94
+ month: decodeNumber(data.shift()),
95
+ periodicity: decodeString(data.shift()),
96
+ lastDate: decodeString(data.shift()),
97
+ };
98
+ }
99
+ const directDebitExt = data.shift();
100
+ if (directDebitExt === "1" && payment.type === PaymentOptions.DirectDebit) {
101
+ payment = {
102
+ ...payment,
103
+ directDebitScheme: decodeNumber(data.shift()),
104
+ directDebitType: decodeNumber(data.shift()),
105
+ variableSymbol: decodeString(data.shift()),
106
+ specificSymbol: decodeString(data.shift()),
107
+ originatorsReferenceInformation: decodeString(data.shift()),
108
+ mandateId: decodeString(data.shift()),
109
+ creditorId: decodeString(data.shift()),
110
+ contractId: decodeString(data.shift()),
111
+ maxAmount: decodeNumber(data.shift()),
112
+ validTillDate: decodeString(data.shift()),
113
+ };
105
114
  }
106
115
  cleanUndefined(payment);
107
116
  output.payments.push(payment);
@@ -112,15 +121,9 @@ export function deserialize(qr) {
112
121
  const addressLine2 = data.shift();
113
122
  if (Boolean(name) || Boolean(addressLine1) || Boolean(addressLine2)) {
114
123
  const beneficiary = {
115
- name: name?.length
116
- ? name
117
- : undefined,
118
- street: addressLine1?.length
119
- ? addressLine1
120
- : undefined,
121
- city: addressLine2?.length
122
- ? addressLine2
123
- : undefined,
124
+ name: name || undefined,
125
+ street: addressLine1 || undefined,
126
+ city: addressLine2 || undefined,
124
127
  };
125
128
  cleanUndefined(beneficiary);
126
129
  output.payments[i].beneficiary = beneficiary;
@@ -148,14 +151,6 @@ function bysquareHeaderDecoder(header) {
148
151
  reserved,
149
152
  };
150
153
  }
151
- export class DecodeError extends Error {
152
- cause;
153
- name = "DecodeError";
154
- constructor(cause, msg) {
155
- super(msg);
156
- this.cause = cause;
157
- }
158
- }
159
154
  /** @deprecated */
160
155
  export const parse = decode;
161
156
  /**
@@ -168,7 +163,9 @@ export function decode(qr) {
168
163
  const bysquareHeader = bytes.slice(0, 2);
169
164
  const decodedBysquareHeader = bysquareHeaderDecoder(bysquareHeader);
170
165
  if ((decodedBysquareHeader.version > Version["1.1.0"])) {
171
- throw new Error(`Unsupported Bysquare version '${decodedBysquareHeader.version}' in header detected. Only '0' and '1' values are supported`);
166
+ throw new DecodeError(DecodeErrorMessage.UnsupportedVersion, {
167
+ version: decodedBysquareHeader.version,
168
+ });
172
169
  }
173
170
  /**
174
171
  * The process of decompressing data requires the addition of an LZMA header
@@ -176,11 +173,13 @@ export function decode(qr) {
176
173
  * algorithm to properly interpret and extract the original uncompressed
177
174
  * data. Bysquare only store properties
178
175
  *
179
- * <----------------------- 13-bytes ----------------------->
176
+ * @see https://docs.fileformat.com/compression/lzma/
180
177
  *
181
- * +------------+----+----+----+----+--+--+--+--+--+--+--+--+
182
- * | Properties | Dictionary Size | Uncompressed Size |
183
- * +------------+----+----+----+----+--+--+--+--+--+--+--+--+
178
+ * +---------------+---------------------------+-------------------+
179
+ * | 1B | 4B | 8B |
180
+ * +---------------+---------------------------+-------------------+
181
+ * | Properties | Dictionary Size | Uncompressed Size |
182
+ * +---------------+---------------------------+-------------------+
184
183
  */
185
184
  const defaultProperties = [0x5D]; // lc=3, lp=0, pb=2
186
185
  const defaultDictionarySize = [0x00, 0x02, 0x00, 0x00]; // 2^17
@@ -201,7 +200,7 @@ export function decode(qr) {
201
200
  decompressed = decompress(body);
202
201
  }
203
202
  catch (error) {
204
- throw new DecodeError(error, "LZMA decompression failed");
203
+ throw new DecodeError(DecodeErrorMessage.LZMADecompressionFailed, { error });
205
204
  }
206
205
  if (typeof decompressed === "string") {
207
206
  return deserialize(decompressed);
@@ -214,8 +213,8 @@ export function decode(qr) {
214
213
  /**
215
214
  * Detect if qr string contains bysquare header.
216
215
  *
217
- * Bysquare header does not have too much information, therefore it is
218
- * not very reliable, there is room for improvement for the future.
216
+ * There is not magic header in the bysquare specification.
217
+ * Version is just 4 bites, so it is possible to have false positives.
219
218
  */
220
219
  export function detect(qr) {
221
220
  let decoded;
package/dist/encode.d.ts CHANGED
@@ -1,4 +1,38 @@
1
1
  import { DataModel } from "./types.js";
2
+ export declare enum EncodeErrorMessage {
3
+ /**
4
+ * @description - find invalid value in extensions
5
+ */
6
+ BySquareType = "Invalid BySquareType value in header, valid range <0,15>",
7
+ /**
8
+ * @description - find invalid value in extensions
9
+ * @see {@link ./types#Version} for valid ranges
10
+ */
11
+ Version = "Invalid Version value in header",
12
+ /**
13
+ * @description - find invalid value in extensions
14
+ */
15
+ DocumentType = "Invalid DocumentType value in header, valid range <0,15>",
16
+ /**
17
+ * @description - find invalid value in extensions
18
+ */
19
+ Reserved = "Invalid Reserved value in header, valid range <0,15>",
20
+ /**
21
+ * @description - find actual size of header in extensions
22
+ * @see MAX_COMPRESSED_SIZE
23
+ */
24
+ HeaderDataSize = "Allowed header data size exceeded"
25
+ }
26
+ export declare class EncodeError extends Error {
27
+ name: string;
28
+ extensions?: {
29
+ [name: string]: any;
30
+ };
31
+ constructor(message: EncodeErrorMessage, extensions?: {
32
+ [name: string]: any;
33
+ });
34
+ }
35
+ export declare const MAX_COMPRESSED_SIZE = 131072;
2
36
  /**
3
37
  * Returns a 2 byte buffer that represents the header of the bysquare
4
38
  * specification
@@ -40,6 +74,7 @@ export declare function addChecksum(serialized: string): Uint8Array;
40
74
  * @see Table 15.
41
75
  */
42
76
  export declare function serialize(data: DataModel): string;
77
+ export declare function removeDiacritics(model: DataModel): void;
43
78
  type Options = {
44
79
  /**
45
80
  * Many banking apps do not support diacritics, which results in errors when
package/dist/encode.js CHANGED
@@ -4,7 +4,42 @@ import { crc32 } from "./crc32.js";
4
4
  import { deburr } from "./deburr.js";
5
5
  import { PaymentOptions, Version, } from "./types.js";
6
6
  import { validateDataModel } from "./validations.js";
7
- const MAX_COMPRESSED_SIZE = 131_072; // 2^17
7
+ export var EncodeErrorMessage;
8
+ (function (EncodeErrorMessage) {
9
+ /**
10
+ * @description - find invalid value in extensions
11
+ */
12
+ EncodeErrorMessage["BySquareType"] = "Invalid BySquareType value in header, valid range <0,15>";
13
+ /**
14
+ * @description - find invalid value in extensions
15
+ * @see {@link ./types#Version} for valid ranges
16
+ */
17
+ EncodeErrorMessage["Version"] = "Invalid Version value in header";
18
+ /**
19
+ * @description - find invalid value in extensions
20
+ */
21
+ EncodeErrorMessage["DocumentType"] = "Invalid DocumentType value in header, valid range <0,15>";
22
+ /**
23
+ * @description - find invalid value in extensions
24
+ */
25
+ EncodeErrorMessage["Reserved"] = "Invalid Reserved value in header, valid range <0,15>";
26
+ /**
27
+ * @description - find actual size of header in extensions
28
+ * @see MAX_COMPRESSED_SIZE
29
+ */
30
+ EncodeErrorMessage["HeaderDataSize"] = "Allowed header data size exceeded";
31
+ })(EncodeErrorMessage || (EncodeErrorMessage = {}));
32
+ export class EncodeError extends Error {
33
+ name = "EncodeError";
34
+ extensions;
35
+ constructor(message, extensions) {
36
+ super(message);
37
+ if (extensions) {
38
+ this.extensions = extensions;
39
+ }
40
+ }
41
+ }
42
+ export const MAX_COMPRESSED_SIZE = 131_072; // 2^17
8
43
  /**
9
44
  * Returns a 2 byte buffer that represents the header of the bysquare
10
45
  * specification
@@ -27,16 +62,16 @@ header = [
27
62
  0x00, 0x00
28
63
  ]) {
29
64
  if (header[0] < 0 || header[0] > 15) {
30
- throw new Error(`Invalid BySquareType value '${header[0]}' in header, valid range <0,15>`);
65
+ throw new EncodeError(EncodeErrorMessage.BySquareType, { invalidValue: header[0] });
31
66
  }
32
67
  if (header[1] < 0 || header[1] > 15) {
33
- throw new Error(`Invalid Version value '${header[1]}' in header, valid range <0,15>`);
68
+ throw new EncodeError(EncodeErrorMessage.Version, { invalidValue: header[1] });
34
69
  }
35
70
  if (header[2] < 0 || header[2] > 15) {
36
- throw new Error(`Invalid DocumentType value '${header[2]}' in header, valid range <0,15>`);
71
+ throw new EncodeError(EncodeErrorMessage.DocumentType, { invalidValue: header[2] });
37
72
  }
38
73
  if (header[3] < 0 || header[3] > 15) {
39
- throw new Error(`Invalid Reserved value '${header[3]}' in header, valid range <0,15>`);
74
+ throw new EncodeError(EncodeErrorMessage.Reserved, { invalidValue: header[3] });
40
75
  }
41
76
  const [bySquareType, version, documentType, reserved,] = header;
42
77
  // Combine 4-nibbles to 2-bytes
@@ -52,7 +87,10 @@ header = [
52
87
  */
53
88
  export function headerDataLength(length) {
54
89
  if (length >= MAX_COMPRESSED_SIZE) {
55
- throw new Error(`Data size ${length} exceeds limit of ${MAX_COMPRESSED_SIZE} bytes`);
90
+ throw new EncodeError(EncodeErrorMessage.HeaderDataSize, {
91
+ actualSize: length,
92
+ allowedSize: MAX_COMPRESSED_SIZE,
93
+ });
56
94
  }
57
95
  const header = new ArrayBuffer(2);
58
96
  new DataView(header).setUint16(0, length, true);
@@ -131,7 +169,7 @@ export function serialize(data) {
131
169
  }
132
170
  return serialized.join("\t");
133
171
  }
134
- function removeDiacritics(model) {
172
+ export function removeDiacritics(model) {
135
173
  for (const payment of model.payments) {
136
174
  if (payment.paymentNote) {
137
175
  payment.paymentNote = deburr(payment.paymentNote);
@@ -161,16 +199,28 @@ export function encode(model, options) {
161
199
  validateDataModel(model);
162
200
  }
163
201
  const payload = serialize(model);
164
- const withChecksum = addChecksum(payload);
165
- const compressed = Uint8Array.from(compress(withChecksum));
166
- const _lzmaHeader = Uint8Array.from(compressed.subarray(0, 13));
167
- const lzmaBody = Uint8Array.from(compressed.subarray(13));
202
+ const payloadChecked = addChecksum(payload);
203
+ const payloadCompressed = Uint8Array.from(compress(payloadChecked));
204
+ /**
205
+ * The LZMA files has a 13-byte header that is followed by the LZMA
206
+ * compressed data.
207
+ *
208
+ * @see https://docs.fileformat.com/compression/lzma/
209
+ *
210
+ * +---------------+---------------------------+-------------------+
211
+ * | 1B | 4B | 8B |
212
+ * +---------------+---------------------------+-------------------+
213
+ * | Properties | Dictionary Size | Uncompressed Size |
214
+ * +---------------+---------------------------+-------------------+
215
+ */
216
+ const _lzmaHeader = Uint8Array.from(payloadCompressed.subarray(0, 13));
217
+ const lzmaBody = Uint8Array.from(payloadCompressed.subarray(13));
168
218
  const output = Uint8Array.from([
169
219
  // NOTE: Newer version 1.1.0 is not supported by all apps (e.g., TatraBanka).
170
220
  // We recommend using version "1.0.0" for better compatibility.
171
221
  // ...headerBysquare([0x00, Version["1.1.0"], 0x00, 0x00]),
172
222
  ...headerBysquare([0x00, Version["1.0.0"], 0x00, 0x00]),
173
- ...headerDataLength(withChecksum.byteLength),
223
+ ...headerDataLength(payloadChecked.byteLength),
174
224
  ...lzmaBody,
175
225
  ]);
176
226
  return base32hex.encode(output, false);
@@ -0,0 +1,16 @@
1
+ import { type BankAccount, SimplePayment, type StandingOrder } from "./types.js";
2
+ type PaymentInput = Pick<BankAccount, "iban"> & Pick<SimplePayment, "amount" | "currencyCode" | "variableSymbol">;
3
+ /**
4
+ * Vytvorí QR pre jednorázovú platbu
5
+ */
6
+ export declare function simplePayment(input: PaymentInput): string;
7
+ /**
8
+ * Vytvorí QR pre inkaso
9
+ */
10
+ export declare function directDebit(input: PaymentInput): string;
11
+ type StandingInput = PaymentInput & Pick<StandingOrder, "day" | "periodicity">;
12
+ /**
13
+ * Vytvorí QR pre trvalý príkaz
14
+ */
15
+ export declare function standingOrder(input: StandingInput): string;
16
+ export {};
@@ -0,0 +1,52 @@
1
+ import { encode } from "./encode.js";
2
+ import { CurrencyCode, PaymentOptions, } from "./types.js";
3
+ /**
4
+ * Vytvorí QR pre jednorázovú platbu
5
+ */
6
+ export function simplePayment(input) {
7
+ return encode({
8
+ payments: [
9
+ {
10
+ type: PaymentOptions.PaymentOrder,
11
+ amount: input.amount,
12
+ variableSymbol: input.variableSymbol,
13
+ currencyCode: CurrencyCode.EUR,
14
+ bankAccounts: [{ iban: input.iban }],
15
+ },
16
+ ],
17
+ });
18
+ }
19
+ /**
20
+ * Vytvorí QR pre inkaso
21
+ */
22
+ export function directDebit(input) {
23
+ return encode({
24
+ payments: [
25
+ {
26
+ type: PaymentOptions.DirectDebit,
27
+ amount: input.amount,
28
+ variableSymbol: input.variableSymbol,
29
+ currencyCode: CurrencyCode.EUR,
30
+ bankAccounts: [{ iban: input.iban }],
31
+ },
32
+ ],
33
+ });
34
+ }
35
+ /**
36
+ * Vytvorí QR pre trvalý príkaz
37
+ */
38
+ export function standingOrder(input) {
39
+ return encode({
40
+ payments: [
41
+ {
42
+ type: PaymentOptions.StandingOrder,
43
+ day: input.day,
44
+ periodicity: input.periodicity,
45
+ amount: input.amount,
46
+ variableSymbol: input.variableSymbol,
47
+ currencyCode: CurrencyCode.EUR,
48
+ bankAccounts: [{ iban: input.iban }],
49
+ },
50
+ ],
51
+ });
52
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright Filip Seman
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
1
6
  export { decode, detect, parse } from "./decode.js";
2
7
  export { encode, generate } from "./encode.js";
3
8
  export { validateDataModel, ValidationErrorMessage } from "./validations.js";
9
+ export * from "./helpers.js";
4
10
  export * from "./types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright Filip Seman
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
1
6
  export { decode, detect, parse } from "./decode.js";
2
7
  export { encode, generate } from "./encode.js";
3
8
  export { validateDataModel, ValidationErrorMessage } from "./validations.js";
9
+ export * from "./helpers.js";
4
10
  export * from "./types.js";
@@ -0,0 +1,43 @@
1
+ import { CurrencyCode, PaymentOptions, Periodicity } from "./types.js";
2
+ export declare const payloadWithPaymentOrder: {
3
+ invoiceId: string;
4
+ payments: {
5
+ type: PaymentOptions.PaymentOrder;
6
+ amount: number;
7
+ bankAccounts: {
8
+ iban: string;
9
+ }[];
10
+ currencyCode: CurrencyCode;
11
+ variableSymbol: string;
12
+ }[];
13
+ };
14
+ export declare const serializedPaymentOrder: string;
15
+ export declare const payloadWithStandingOrder: {
16
+ invoiceId: string;
17
+ payments: {
18
+ type: PaymentOptions.StandingOrder;
19
+ amount: number;
20
+ bankAccounts: {
21
+ iban: string;
22
+ }[];
23
+ periodicity: Periodicity.Monthly;
24
+ currencyCode: CurrencyCode;
25
+ variableSymbol: string;
26
+ lastDate: string;
27
+ day: number;
28
+ }[];
29
+ };
30
+ export declare const serializedStandingOrder: string;
31
+ export declare const payloadWithDirectDebit: {
32
+ invoiceId: string;
33
+ payments: {
34
+ type: PaymentOptions.DirectDebit;
35
+ amount: number;
36
+ bankAccounts: {
37
+ iban: string;
38
+ }[];
39
+ currencyCode: CurrencyCode;
40
+ variableSymbol: string;
41
+ }[];
42
+ };
43
+ export declare const serializedDirectDebit: string;
@@ -0,0 +1,123 @@
1
+ import { CurrencyCode, PaymentOptions, Periodicity, } from "./types.js";
2
+ export const payloadWithPaymentOrder = {
3
+ invoiceId: "random-id",
4
+ payments: [
5
+ {
6
+ type: PaymentOptions.PaymentOrder,
7
+ amount: 100.0,
8
+ bankAccounts: [
9
+ { iban: "SK9611000000002918599669" },
10
+ ],
11
+ currencyCode: CurrencyCode.EUR,
12
+ variableSymbol: "123",
13
+ },
14
+ ],
15
+ };
16
+ export const serializedPaymentOrder = /** dprint-ignore */ [
17
+ "random-id",
18
+ "\t", "1",
19
+ "\t", "1",
20
+ "\t", "100",
21
+ "\t", "EUR",
22
+ "\t",
23
+ "\t", "123",
24
+ "\t",
25
+ "\t",
26
+ "\t",
27
+ "\t",
28
+ "\t", "1",
29
+ "\t", "SK9611000000002918599669",
30
+ "\t",
31
+ "\t", "0",
32
+ "\t", "0",
33
+ "\t",
34
+ "\t",
35
+ "\t",
36
+ ].join("");
37
+ export const payloadWithStandingOrder = {
38
+ invoiceId: "random-id",
39
+ payments: [
40
+ {
41
+ type: PaymentOptions.StandingOrder,
42
+ amount: 100.0,
43
+ bankAccounts: [
44
+ { iban: "SK9611000000002918599669" },
45
+ ],
46
+ periodicity: Periodicity.Monthly,
47
+ currencyCode: CurrencyCode.EUR,
48
+ variableSymbol: "123",
49
+ lastDate: "20241011",
50
+ day: 1,
51
+ },
52
+ ],
53
+ };
54
+ export const serializedStandingOrder = /** dprint-ignore */ [
55
+ "random-id",
56
+ "\t", "1",
57
+ "\t", "2",
58
+ "\t", "100",
59
+ "\t", "EUR",
60
+ "\t",
61
+ "\t", "123",
62
+ "\t",
63
+ "\t",
64
+ "\t",
65
+ "\t",
66
+ "\t", "1",
67
+ "\t", "SK9611000000002918599669",
68
+ "\t",
69
+ "\t", "1",
70
+ "\t", "1",
71
+ "\t",
72
+ "\t", "m",
73
+ "\t", "20241011",
74
+ "\t", "0",
75
+ "\t",
76
+ "\t",
77
+ "\t",
78
+ ].join("");
79
+ export const payloadWithDirectDebit = {
80
+ invoiceId: "random-id",
81
+ payments: [
82
+ {
83
+ type: PaymentOptions.DirectDebit,
84
+ amount: 100.0,
85
+ bankAccounts: [
86
+ { iban: "SK9611000000002918599669" },
87
+ ],
88
+ currencyCode: CurrencyCode.EUR,
89
+ variableSymbol: "123",
90
+ },
91
+ ],
92
+ };
93
+ export const serializedDirectDebit = /** dprint-ignore */ [
94
+ "random-id",
95
+ "\t", "1",
96
+ "\t", "4",
97
+ "\t", "100",
98
+ "\t", "EUR",
99
+ "\t",
100
+ "\t", "123",
101
+ "\t",
102
+ "\t",
103
+ "\t",
104
+ "\t",
105
+ "\t", "1",
106
+ "\t", "SK9611000000002918599669",
107
+ "\t",
108
+ "\t", "0",
109
+ "\t", "1",
110
+ "\t",
111
+ "\t",
112
+ "\t", "123",
113
+ "\t",
114
+ "\t",
115
+ "\t",
116
+ "\t",
117
+ "\t",
118
+ "\t",
119
+ "\t",
120
+ "\t",
121
+ "\t",
122
+ "\t",
123
+ ].join("");
@@ -0,0 +1 @@
1
+ {"root":["../src/base32hex.ts","../src/cli.ts","../src/crc32.ts","../src/deburr.ts","../src/decode.ts","../src/encode.ts","../src/helpers.ts","../src/index.ts","../src/test_assets.ts","../src/types.ts","../src/validations.ts"],"version":"5.6.2"}
package/dist/types.d.ts CHANGED
@@ -248,7 +248,7 @@ export type StandingOrder = SimplePayment & {
248
248
  /**
249
249
  * Opakovanie (periodicita) trvalého príkazu.
250
250
  */
251
- periodicity?: Periodicity;
251
+ periodicity: Periodicity;
252
252
  /**
253
253
  * Dátum poslednej platby v trvalom príkaze.
254
254
  *
@@ -17,7 +17,7 @@ export class ValidationError extends Error {
17
17
  * @param path - navigates to the specific field in DataModel, where error occurred
18
18
  */
19
19
  constructor(message, path) {
20
- super(String(message));
20
+ super(message);
21
21
  this.path = path;
22
22
  }
23
23
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
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.11.0",
4
+ "version": "2.12.1",
5
5
  "license": "Apache-2.0",
6
6
  "funding": "https://github.com/sponsors/xseman",
7
7
  "homepage": "https://github.com/xseman/bysquare#readme",
@@ -17,31 +17,27 @@
17
17
  "url": "git+https://github.com/xseman/bysquare.git"
18
18
  },
19
19
  "scripts": {
20
- "prebuild": "tsc --build --clean",
21
20
  "build": "tsc --build",
22
21
  "fmt": "dprint fmt",
23
22
  "fmt:check": "dprint check",
24
23
  "typecheck": "tsc --noEmit",
25
- "version": "git checkout develop && npm test",
26
- "postversion": "echo 'Now run npm run build && npm publish'",
27
24
  "test": "TS_NODE_TRANSPILE_ONLY=true node --test --experimental-test-coverage --loader=ts-node/esm --no-warnings src/*.test.ts",
28
25
  "test:watch": "TS_NODE_TRANSPILE_ONLY=true node --test --watch --loader=ts-node/esm --no-warnings src/*.test.ts"
29
26
  },
30
27
  "dependencies": {
31
- "lzma1": "0.0.3",
28
+ "lzma1": "0.0.5",
32
29
  "validator": "^13.12.0"
33
30
  },
34
31
  "devDependencies": {
35
- "@types/node": "^22.5.0",
32
+ "@types/node": "^22.7.0",
36
33
  "@types/validator": "^13.12.0",
37
34
  "dprint": "~0.47.0",
38
35
  "ts-node": "~10.9.0",
39
- "typescript": "~5.5.0"
36
+ "typescript": "~5.6.0"
40
37
  },
41
38
  "type": "module",
42
39
  "bin": "./dist/cli.js",
43
40
  "types": "./dist/index.d.ts",
44
- "module": "./dist/index.js",
45
41
  "exports": {
46
42
  ".": {
47
43
  "import": "./dist/index.js"
@@ -50,9 +46,5 @@
50
46
  "files": [
51
47
  "dist",
52
48
  "!dist/*.test.*"
53
- ],
54
- "engines": {
55
- "node": ">=16",
56
- "npm": ">=7"
57
- }
49
+ ]
58
50
  }