@amboras-dev/money 0.0.0-stage → 0.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/README.md +401 -2
- package/dist/ai.cjs +185 -0
- package/dist/ai.d.cts +97 -0
- package/dist/ai.d.ts +97 -0
- package/dist/ai.js +175 -0
- package/dist/allocate-CZRg4TOP.cjs +222 -0
- package/dist/allocate-X7_jAtOj.js +199 -0
- package/dist/amount-BV0O-aN8.cjs +241 -0
- package/dist/amount-BoYBMgK9.js +200 -0
- package/dist/arithmetic-BhNwisjb.js +132 -0
- package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
- package/dist/billing.cjs +184 -0
- package/dist/billing.d.cts +81 -0
- package/dist/billing.d.ts +81 -0
- package/dist/billing.js +175 -0
- package/dist/commerce.cjs +536 -0
- package/dist/commerce.d.cts +293 -0
- package/dist/commerce.d.ts +293 -0
- package/dist/commerce.js +516 -0
- package/dist/format-BdPnijvn.js +99 -0
- package/dist/format-DLhno7eX.cjs +128 -0
- package/dist/fx.cjs +407 -0
- package/dist/fx.d.cts +157 -0
- package/dist/fx.d.ts +157 -0
- package/dist/fx.js +374 -0
- package/dist/index.cjs +352 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +276 -0
- package/dist/input.cjs +163 -0
- package/dist/input.d.cts +203 -0
- package/dist/input.d.ts +203 -0
- package/dist/input.js +153 -0
- package/dist/iso-minor-CqkWDxHS.js +48 -0
- package/dist/iso-minor-wSwJN57T.cjs +65 -0
- package/dist/medusa-4K8N1pUc.d.cts +87 -0
- package/dist/medusa-DBXJXXvF.d.ts +87 -0
- package/dist/medusa-DqJY1qpj.js +116 -0
- package/dist/medusa-lRY9xlsf.cjs +175 -0
- package/dist/money-B-3aFjNJ.d.cts +248 -0
- package/dist/money-B-3aFjNJ.d.ts +248 -0
- package/dist/money-B3wH_Z4S.cjs +3990 -0
- package/dist/money-WhnGiI00.js +3583 -0
- package/dist/number-text-BWu67jUq.cjs +145 -0
- package/dist/number-text-DE-q4jO5.js +134 -0
- package/dist/own-D7544D3k.js +4 -0
- package/dist/own-oP9DXFWv.cjs +9 -0
- package/dist/percent-C0InC2t2.cjs +43 -0
- package/dist/percent-D1VB9xXG.js +20 -0
- package/dist/rate-CVeMii4a.cjs +217 -0
- package/dist/rate-DBPiYgT1.d.ts +63 -0
- package/dist/rate-DicStBue.js +152 -0
- package/dist/rate-tqK8ijG0.d.cts +63 -0
- package/dist/reporting-CG-ai2sM.d.cts +31 -0
- package/dist/reporting-tpGJbqYp.d.ts +31 -0
- package/dist/retail-1mu888J1.js +69 -0
- package/dist/retail-C_BPLvMj.d.cts +31 -0
- package/dist/retail-Cml8YSKg.cjs +86 -0
- package/dist/retail-DRUTTQoJ.d.ts +31 -0
- package/dist/rounding-DHBtNCHG.cjs +79 -0
- package/dist/rounding-g9rvye0k.js +50 -0
- package/dist/sql.cjs +290 -0
- package/dist/sql.d.cts +147 -0
- package/dist/sql.d.ts +147 -0
- package/dist/sql.js +271 -0
- package/dist/testing.cjs +61995 -0
- package/dist/testing.d.cts +125 -0
- package/dist/testing.d.ts +125 -0
- package/dist/testing.js +61986 -0
- package/dist/wire-Bhuut8cW.cjs +2097 -0
- package/dist/wire-CaUN9Vf_.d.ts +309 -0
- package/dist/wire-sW0yzqth.js +1888 -0
- package/dist/wire-xjYXv4go.d.cts +309 -0
- package/dist/wire.cjs +37 -0
- package/dist/wire.d.cts +2 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +2 -0
- package/package.json +158 -3
|
@@ -0,0 +1,2097 @@
|
|
|
1
|
+
const require_money = require("./money-B3wH_Z4S.cjs");
|
|
2
|
+
const require_allocate = require("./allocate-CZRg4TOP.cjs");
|
|
3
|
+
const require_own = require("./own-oP9DXFWv.cjs");
|
|
4
|
+
const require_iso_minor = require("./iso-minor-wSwJN57T.cjs");
|
|
5
|
+
const require_number_text = require("./number-text-BWu67jUq.cjs");
|
|
6
|
+
//#region src/wire/anet.ts
|
|
7
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
/**
|
|
9
|
+
* The first `"<field>": <decimal>` (quoted or not) in a raw JSON body, verbatim: `JSON.parse`
|
|
10
|
+
* would turn the gateway's `43.00` into `43`. The value is the whole JSON token, held to
|
|
11
|
+
* `parseDecimal`'s `'plain'` grammar (`1e3` or `"43.10abc"` is not a decimal literal).
|
|
12
|
+
* Undefined when absent; there is no lossy fallback.
|
|
13
|
+
*/
|
|
14
|
+
function jsonDecimalLiteral(rawBody, field) {
|
|
15
|
+
if (typeof rawBody !== "string" || typeof field !== "string") return void 0;
|
|
16
|
+
const tokens = rawBody.matchAll(new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*("?)([^"\\s,}\\]]+)\\1`, "g"));
|
|
17
|
+
for (const [, , token] of tokens) if (require_money.parseDecimal(token, "plain")) return token;
|
|
18
|
+
}
|
|
19
|
+
const isPositiveLiteral = (text) => {
|
|
20
|
+
const value = require_money.parseDecimal(text.trim());
|
|
21
|
+
return value !== null && value.units > 0n;
|
|
22
|
+
};
|
|
23
|
+
function authorizeNetReversibleAmount(details) {
|
|
24
|
+
const settle = details.settleAmount ?? void 0;
|
|
25
|
+
if (settle !== void 0 && isPositiveLiteral(settle)) return settle;
|
|
26
|
+
return details.authAmount ?? settle;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/data/psp.ts
|
|
30
|
+
/** developer.paypal.com/reference/currency-codes ("Last updated: August 17, 2026"). */
|
|
31
|
+
const PAYPAL_CURRENCY_DIGITS = {
|
|
32
|
+
0: [
|
|
33
|
+
"HUF",
|
|
34
|
+
"JPY",
|
|
35
|
+
"TWD"
|
|
36
|
+
],
|
|
37
|
+
2: [
|
|
38
|
+
"AUD",
|
|
39
|
+
"BRL",
|
|
40
|
+
"CAD",
|
|
41
|
+
"CHF",
|
|
42
|
+
"CNY",
|
|
43
|
+
"CZK",
|
|
44
|
+
"DKK",
|
|
45
|
+
"EUR",
|
|
46
|
+
"GBP",
|
|
47
|
+
"HKD",
|
|
48
|
+
"ILS",
|
|
49
|
+
"MXN",
|
|
50
|
+
"MYR",
|
|
51
|
+
"NOK",
|
|
52
|
+
"NZD",
|
|
53
|
+
"PHP",
|
|
54
|
+
"PLN",
|
|
55
|
+
"SEK",
|
|
56
|
+
"SGD",
|
|
57
|
+
"THB",
|
|
58
|
+
"USD"
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
/** docs.mollie.com/docs/multicurrency (2026-07-29). */
|
|
62
|
+
const MOLLIE_CURRENCY_DIGITS = {
|
|
63
|
+
0: ["ISK", "JPY"],
|
|
64
|
+
2: [
|
|
65
|
+
"AED",
|
|
66
|
+
"AUD",
|
|
67
|
+
"BRL",
|
|
68
|
+
"CAD",
|
|
69
|
+
"CHF",
|
|
70
|
+
"CZK",
|
|
71
|
+
"DKK",
|
|
72
|
+
"EUR",
|
|
73
|
+
"GBP",
|
|
74
|
+
"HKD",
|
|
75
|
+
"HUF",
|
|
76
|
+
"ILS",
|
|
77
|
+
"MXN",
|
|
78
|
+
"MYR",
|
|
79
|
+
"NOK",
|
|
80
|
+
"NZD",
|
|
81
|
+
"PHP",
|
|
82
|
+
"PLN",
|
|
83
|
+
"RON",
|
|
84
|
+
"RUB",
|
|
85
|
+
"SEK",
|
|
86
|
+
"SGD",
|
|
87
|
+
"THB",
|
|
88
|
+
"TWD",
|
|
89
|
+
"USD",
|
|
90
|
+
"ZAR"
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
const AIRWALLEX_CURRENCY_DIGITS = {
|
|
94
|
+
0: [
|
|
95
|
+
"BIF",
|
|
96
|
+
"CLP",
|
|
97
|
+
"DJF",
|
|
98
|
+
"GNF",
|
|
99
|
+
"ISK",
|
|
100
|
+
"JPY",
|
|
101
|
+
"KMF",
|
|
102
|
+
"KRW",
|
|
103
|
+
"PYG",
|
|
104
|
+
"RWF",
|
|
105
|
+
"SLE",
|
|
106
|
+
"UGX",
|
|
107
|
+
"VND",
|
|
108
|
+
"VUV",
|
|
109
|
+
"XAF",
|
|
110
|
+
"XOF",
|
|
111
|
+
"XPF"
|
|
112
|
+
],
|
|
113
|
+
2: [
|
|
114
|
+
"AED",
|
|
115
|
+
"ALL",
|
|
116
|
+
"AMD",
|
|
117
|
+
"ANG",
|
|
118
|
+
"AOA",
|
|
119
|
+
"ARS",
|
|
120
|
+
"AUD",
|
|
121
|
+
"AWG",
|
|
122
|
+
"AZN",
|
|
123
|
+
"BAM",
|
|
124
|
+
"BBD",
|
|
125
|
+
"BDT",
|
|
126
|
+
"BGN",
|
|
127
|
+
"BMD",
|
|
128
|
+
"BND",
|
|
129
|
+
"BOB",
|
|
130
|
+
"BRL",
|
|
131
|
+
"BSD",
|
|
132
|
+
"BTN",
|
|
133
|
+
"BWP",
|
|
134
|
+
"BZD",
|
|
135
|
+
"CAD",
|
|
136
|
+
"CHF",
|
|
137
|
+
"CNY",
|
|
138
|
+
"COP",
|
|
139
|
+
"CRC",
|
|
140
|
+
"CVE",
|
|
141
|
+
"CZK",
|
|
142
|
+
"DKK",
|
|
143
|
+
"DOP",
|
|
144
|
+
"DZD",
|
|
145
|
+
"EGP",
|
|
146
|
+
"ERN",
|
|
147
|
+
"ETB",
|
|
148
|
+
"EUR",
|
|
149
|
+
"FJD",
|
|
150
|
+
"FKP",
|
|
151
|
+
"GBP",
|
|
152
|
+
"GEL",
|
|
153
|
+
"GHS",
|
|
154
|
+
"GIP",
|
|
155
|
+
"GMD",
|
|
156
|
+
"GTQ",
|
|
157
|
+
"GYD",
|
|
158
|
+
"HKD",
|
|
159
|
+
"HNL",
|
|
160
|
+
"HUF",
|
|
161
|
+
"IDR",
|
|
162
|
+
"ILS",
|
|
163
|
+
"INR",
|
|
164
|
+
"JMD",
|
|
165
|
+
"KES",
|
|
166
|
+
"KGS",
|
|
167
|
+
"KHR",
|
|
168
|
+
"KYD",
|
|
169
|
+
"KZT",
|
|
170
|
+
"LAK",
|
|
171
|
+
"LBP",
|
|
172
|
+
"LKR",
|
|
173
|
+
"LRD",
|
|
174
|
+
"LSL",
|
|
175
|
+
"MAD",
|
|
176
|
+
"MDL",
|
|
177
|
+
"MGA",
|
|
178
|
+
"MKD",
|
|
179
|
+
"MNT",
|
|
180
|
+
"MOP",
|
|
181
|
+
"MUR",
|
|
182
|
+
"MVR",
|
|
183
|
+
"MWK",
|
|
184
|
+
"MXN",
|
|
185
|
+
"MYR",
|
|
186
|
+
"NAD",
|
|
187
|
+
"NGN",
|
|
188
|
+
"NIO",
|
|
189
|
+
"NOK",
|
|
190
|
+
"NPR",
|
|
191
|
+
"NZD",
|
|
192
|
+
"PAB",
|
|
193
|
+
"PEN",
|
|
194
|
+
"PGK",
|
|
195
|
+
"PHP",
|
|
196
|
+
"PKR",
|
|
197
|
+
"PLN",
|
|
198
|
+
"QAR",
|
|
199
|
+
"RON",
|
|
200
|
+
"RSD",
|
|
201
|
+
"SAR",
|
|
202
|
+
"SBD",
|
|
203
|
+
"SCR",
|
|
204
|
+
"SEK",
|
|
205
|
+
"SGD",
|
|
206
|
+
"SHP",
|
|
207
|
+
"SRD",
|
|
208
|
+
"STD",
|
|
209
|
+
"SVC",
|
|
210
|
+
"SZL",
|
|
211
|
+
"THB",
|
|
212
|
+
"TJS",
|
|
213
|
+
"TMT",
|
|
214
|
+
"TOP",
|
|
215
|
+
"TRY",
|
|
216
|
+
"TTD",
|
|
217
|
+
"TWD",
|
|
218
|
+
"TZS",
|
|
219
|
+
"UAH",
|
|
220
|
+
"USD",
|
|
221
|
+
"UYU",
|
|
222
|
+
"UZS",
|
|
223
|
+
"WST",
|
|
224
|
+
"XCD",
|
|
225
|
+
"ZAR",
|
|
226
|
+
"ZMW",
|
|
227
|
+
"ZWL"
|
|
228
|
+
],
|
|
229
|
+
3: [
|
|
230
|
+
"BHD",
|
|
231
|
+
"JOD",
|
|
232
|
+
"KWD",
|
|
233
|
+
"LYD",
|
|
234
|
+
"OMR",
|
|
235
|
+
"TND"
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
/** docs.adyen.com/development-resources/currency-codes. Not read by the codec (overrides below); held to it by a test. */
|
|
239
|
+
const ADYEN_CURRENCY_DIGITS = {
|
|
240
|
+
0: [
|
|
241
|
+
"CVE",
|
|
242
|
+
"DJF",
|
|
243
|
+
"GNF",
|
|
244
|
+
"IDR",
|
|
245
|
+
"JPY",
|
|
246
|
+
"KMF",
|
|
247
|
+
"KRW",
|
|
248
|
+
"PYG",
|
|
249
|
+
"RWF",
|
|
250
|
+
"UGX",
|
|
251
|
+
"VND",
|
|
252
|
+
"VUV",
|
|
253
|
+
"XAF",
|
|
254
|
+
"XOF",
|
|
255
|
+
"XPF"
|
|
256
|
+
],
|
|
257
|
+
2: [
|
|
258
|
+
"AED",
|
|
259
|
+
"ALL",
|
|
260
|
+
"AMD",
|
|
261
|
+
"AOA",
|
|
262
|
+
"ARS",
|
|
263
|
+
"AUD",
|
|
264
|
+
"AWG",
|
|
265
|
+
"AZN",
|
|
266
|
+
"BAM",
|
|
267
|
+
"BBD",
|
|
268
|
+
"BDT",
|
|
269
|
+
"BMD",
|
|
270
|
+
"BND",
|
|
271
|
+
"BOB",
|
|
272
|
+
"BRL",
|
|
273
|
+
"BSD",
|
|
274
|
+
"BWP",
|
|
275
|
+
"BYN",
|
|
276
|
+
"BZD",
|
|
277
|
+
"CAD",
|
|
278
|
+
"CHF",
|
|
279
|
+
"CLP",
|
|
280
|
+
"CNH",
|
|
281
|
+
"CNY",
|
|
282
|
+
"COP",
|
|
283
|
+
"CRC",
|
|
284
|
+
"CUP",
|
|
285
|
+
"CZK",
|
|
286
|
+
"DKK",
|
|
287
|
+
"DOP",
|
|
288
|
+
"DZD",
|
|
289
|
+
"EGP",
|
|
290
|
+
"ETB",
|
|
291
|
+
"EUR",
|
|
292
|
+
"FJD",
|
|
293
|
+
"FKP",
|
|
294
|
+
"GBP",
|
|
295
|
+
"GEL",
|
|
296
|
+
"GHS",
|
|
297
|
+
"GIP",
|
|
298
|
+
"GMD",
|
|
299
|
+
"GTQ",
|
|
300
|
+
"GYD",
|
|
301
|
+
"HKD",
|
|
302
|
+
"HNL",
|
|
303
|
+
"HTG",
|
|
304
|
+
"HUF",
|
|
305
|
+
"ILS",
|
|
306
|
+
"INR",
|
|
307
|
+
"ISK",
|
|
308
|
+
"JMD",
|
|
309
|
+
"KES",
|
|
310
|
+
"KGS",
|
|
311
|
+
"KHR",
|
|
312
|
+
"KYD",
|
|
313
|
+
"KZT",
|
|
314
|
+
"LAK",
|
|
315
|
+
"LKR",
|
|
316
|
+
"MAD",
|
|
317
|
+
"MDL",
|
|
318
|
+
"MKD",
|
|
319
|
+
"MMK",
|
|
320
|
+
"MNT",
|
|
321
|
+
"MOP",
|
|
322
|
+
"MRU",
|
|
323
|
+
"MUR",
|
|
324
|
+
"MVR",
|
|
325
|
+
"MWK",
|
|
326
|
+
"MXN",
|
|
327
|
+
"MYR",
|
|
328
|
+
"MZN",
|
|
329
|
+
"NAD",
|
|
330
|
+
"NGN",
|
|
331
|
+
"NIO",
|
|
332
|
+
"NOK",
|
|
333
|
+
"NPR",
|
|
334
|
+
"NZD",
|
|
335
|
+
"PAB",
|
|
336
|
+
"PEN",
|
|
337
|
+
"PGK",
|
|
338
|
+
"PHP",
|
|
339
|
+
"PKR",
|
|
340
|
+
"PLN",
|
|
341
|
+
"QAR",
|
|
342
|
+
"RON",
|
|
343
|
+
"RSD",
|
|
344
|
+
"RUB",
|
|
345
|
+
"SAR",
|
|
346
|
+
"SBD",
|
|
347
|
+
"SCR",
|
|
348
|
+
"SEK",
|
|
349
|
+
"SGD",
|
|
350
|
+
"SHP",
|
|
351
|
+
"SLE",
|
|
352
|
+
"SOS",
|
|
353
|
+
"SRD",
|
|
354
|
+
"STN",
|
|
355
|
+
"SVC",
|
|
356
|
+
"SZL",
|
|
357
|
+
"THB",
|
|
358
|
+
"TOP",
|
|
359
|
+
"TRY",
|
|
360
|
+
"TTD",
|
|
361
|
+
"TWD",
|
|
362
|
+
"TZS",
|
|
363
|
+
"UAH",
|
|
364
|
+
"USD",
|
|
365
|
+
"UYU",
|
|
366
|
+
"UZS",
|
|
367
|
+
"VEF",
|
|
368
|
+
"WST",
|
|
369
|
+
"XCD",
|
|
370
|
+
"XCG",
|
|
371
|
+
"YER",
|
|
372
|
+
"ZAR",
|
|
373
|
+
"ZMW"
|
|
374
|
+
],
|
|
375
|
+
3: [
|
|
376
|
+
"BHD",
|
|
377
|
+
"IQD",
|
|
378
|
+
"JOD",
|
|
379
|
+
"KWD",
|
|
380
|
+
"LYD",
|
|
381
|
+
"OMR",
|
|
382
|
+
"TND"
|
|
383
|
+
]
|
|
384
|
+
};
|
|
385
|
+
/** razorpay.com/docs international currency exponents. */
|
|
386
|
+
const RAZORPAY_CURRENCY_EXPONENTS = {
|
|
387
|
+
0: [
|
|
388
|
+
"BIF",
|
|
389
|
+
"CLP",
|
|
390
|
+
"DJF",
|
|
391
|
+
"GNF",
|
|
392
|
+
"ISK",
|
|
393
|
+
"JPY",
|
|
394
|
+
"KMF",
|
|
395
|
+
"KRW",
|
|
396
|
+
"PYG",
|
|
397
|
+
"RWF",
|
|
398
|
+
"UGX",
|
|
399
|
+
"VND",
|
|
400
|
+
"VUV",
|
|
401
|
+
"XAF",
|
|
402
|
+
"XOF",
|
|
403
|
+
"XPF"
|
|
404
|
+
],
|
|
405
|
+
2: [
|
|
406
|
+
"AED",
|
|
407
|
+
"ALL",
|
|
408
|
+
"AMD",
|
|
409
|
+
"AUD",
|
|
410
|
+
"AWG",
|
|
411
|
+
"AZN",
|
|
412
|
+
"BAM",
|
|
413
|
+
"BBD",
|
|
414
|
+
"BDT",
|
|
415
|
+
"BGN",
|
|
416
|
+
"BMD",
|
|
417
|
+
"BND",
|
|
418
|
+
"BOB",
|
|
419
|
+
"BRL",
|
|
420
|
+
"BSD",
|
|
421
|
+
"BTN",
|
|
422
|
+
"BWP",
|
|
423
|
+
"BZD",
|
|
424
|
+
"CAD",
|
|
425
|
+
"CHF",
|
|
426
|
+
"CNY",
|
|
427
|
+
"COP",
|
|
428
|
+
"CRC",
|
|
429
|
+
"CUP",
|
|
430
|
+
"CVE",
|
|
431
|
+
"CZK",
|
|
432
|
+
"DKK",
|
|
433
|
+
"DOP",
|
|
434
|
+
"DZD",
|
|
435
|
+
"EGP",
|
|
436
|
+
"ETB",
|
|
437
|
+
"EUR",
|
|
438
|
+
"FJD",
|
|
439
|
+
"GBP",
|
|
440
|
+
"GHS",
|
|
441
|
+
"GIP",
|
|
442
|
+
"GMD",
|
|
443
|
+
"GTQ",
|
|
444
|
+
"GYD",
|
|
445
|
+
"HKD",
|
|
446
|
+
"HNL",
|
|
447
|
+
"HRK",
|
|
448
|
+
"HTG",
|
|
449
|
+
"HUF",
|
|
450
|
+
"IDR",
|
|
451
|
+
"ILS",
|
|
452
|
+
"INR",
|
|
453
|
+
"JMD",
|
|
454
|
+
"KES",
|
|
455
|
+
"KGS",
|
|
456
|
+
"KHR",
|
|
457
|
+
"KYD",
|
|
458
|
+
"KZT",
|
|
459
|
+
"LAK",
|
|
460
|
+
"LKR",
|
|
461
|
+
"LRD",
|
|
462
|
+
"LSL",
|
|
463
|
+
"MAD",
|
|
464
|
+
"MDL",
|
|
465
|
+
"MGA",
|
|
466
|
+
"MKD",
|
|
467
|
+
"MMK",
|
|
468
|
+
"MNT",
|
|
469
|
+
"MOP",
|
|
470
|
+
"MUR",
|
|
471
|
+
"MVR",
|
|
472
|
+
"MWK",
|
|
473
|
+
"MXN",
|
|
474
|
+
"MYR",
|
|
475
|
+
"MZN",
|
|
476
|
+
"NAD",
|
|
477
|
+
"NGN",
|
|
478
|
+
"NIO",
|
|
479
|
+
"NOK",
|
|
480
|
+
"NPR",
|
|
481
|
+
"NZD",
|
|
482
|
+
"PEN",
|
|
483
|
+
"PGK",
|
|
484
|
+
"PHP",
|
|
485
|
+
"PKR",
|
|
486
|
+
"PLN",
|
|
487
|
+
"QAR",
|
|
488
|
+
"RON",
|
|
489
|
+
"RSD",
|
|
490
|
+
"RUB",
|
|
491
|
+
"SAR",
|
|
492
|
+
"SCR",
|
|
493
|
+
"SEK",
|
|
494
|
+
"SGD",
|
|
495
|
+
"SLL",
|
|
496
|
+
"SOS",
|
|
497
|
+
"SVC",
|
|
498
|
+
"SZL",
|
|
499
|
+
"THB",
|
|
500
|
+
"TRY",
|
|
501
|
+
"TTD",
|
|
502
|
+
"TWD",
|
|
503
|
+
"TZS",
|
|
504
|
+
"UAH",
|
|
505
|
+
"USD",
|
|
506
|
+
"UYU",
|
|
507
|
+
"UZS",
|
|
508
|
+
"XCD",
|
|
509
|
+
"YER",
|
|
510
|
+
"ZAR",
|
|
511
|
+
"ZMW"
|
|
512
|
+
],
|
|
513
|
+
3: [
|
|
514
|
+
"BHD",
|
|
515
|
+
"IQD",
|
|
516
|
+
"JOD",
|
|
517
|
+
"KWD",
|
|
518
|
+
"OMR",
|
|
519
|
+
"TND"
|
|
520
|
+
]
|
|
521
|
+
};
|
|
522
|
+
/** Paystack: "If we notice a fractional part during processing, we ignore the fractional part." */
|
|
523
|
+
const PAYSTACK_CURRENCY_DIGITS = { 2: [
|
|
524
|
+
"GHS",
|
|
525
|
+
"KES",
|
|
526
|
+
"NGN",
|
|
527
|
+
"USD",
|
|
528
|
+
"XOF",
|
|
529
|
+
"ZAR"
|
|
530
|
+
] };
|
|
531
|
+
/** Factor-1 overrides: ISO zero-decimal codes the PSP wants at two decimals (storage and wire agree). */
|
|
532
|
+
const STRIPE_SENT_AS_TWO_DECIMAL = ["ISK", "UGX"];
|
|
533
|
+
const ADYEN_SENT_AS_TWO_DECIMAL = ["ISK", "CLP"];
|
|
534
|
+
const RAZORPAY_SENT_AS_TWO_DECIMAL = ["MGA"];
|
|
535
|
+
const PAYSTACK_SENT_AS_TWO_DECIMAL = ["XOF"];
|
|
536
|
+
const VNPAY_SENT_AS_TWO_DECIMAL = ["VND"];
|
|
537
|
+
/** Factor-100 override: ISO two-decimal codes Adyen wants in whole units. */
|
|
538
|
+
const ADYEN_SENT_AS_ZERO_DECIMAL = ["IDR", "CVE"];
|
|
539
|
+
/** docs.stripe.com/currencies retrieved 2026-10-07 (union over 45 countries; IQD, LYD absent). */
|
|
540
|
+
const STRIPE_PRESENTMENT_CURRENCIES = [
|
|
541
|
+
"AED",
|
|
542
|
+
"AFN",
|
|
543
|
+
"ALL",
|
|
544
|
+
"AMD",
|
|
545
|
+
"ANG",
|
|
546
|
+
"AOA",
|
|
547
|
+
"ARS",
|
|
548
|
+
"AUD",
|
|
549
|
+
"AWG",
|
|
550
|
+
"AZN",
|
|
551
|
+
"BAM",
|
|
552
|
+
"BBD",
|
|
553
|
+
"BDT",
|
|
554
|
+
"BHD",
|
|
555
|
+
"BIF",
|
|
556
|
+
"BMD",
|
|
557
|
+
"BND",
|
|
558
|
+
"BOB",
|
|
559
|
+
"BRL",
|
|
560
|
+
"BSD",
|
|
561
|
+
"BWP",
|
|
562
|
+
"BYN",
|
|
563
|
+
"BZD",
|
|
564
|
+
"CAD",
|
|
565
|
+
"CDF",
|
|
566
|
+
"CHF",
|
|
567
|
+
"CLP",
|
|
568
|
+
"CNY",
|
|
569
|
+
"COP",
|
|
570
|
+
"CRC",
|
|
571
|
+
"CVE",
|
|
572
|
+
"CZK",
|
|
573
|
+
"DJF",
|
|
574
|
+
"DKK",
|
|
575
|
+
"DOP",
|
|
576
|
+
"DZD",
|
|
577
|
+
"EGP",
|
|
578
|
+
"ETB",
|
|
579
|
+
"EUR",
|
|
580
|
+
"FJD",
|
|
581
|
+
"FKP",
|
|
582
|
+
"GBP",
|
|
583
|
+
"GEL",
|
|
584
|
+
"GIP",
|
|
585
|
+
"GMD",
|
|
586
|
+
"GNF",
|
|
587
|
+
"GTQ",
|
|
588
|
+
"GYD",
|
|
589
|
+
"HKD",
|
|
590
|
+
"HNL",
|
|
591
|
+
"HTG",
|
|
592
|
+
"HUF",
|
|
593
|
+
"IDR",
|
|
594
|
+
"ILS",
|
|
595
|
+
"INR",
|
|
596
|
+
"ISK",
|
|
597
|
+
"JMD",
|
|
598
|
+
"JOD",
|
|
599
|
+
"JPY",
|
|
600
|
+
"KES",
|
|
601
|
+
"KGS",
|
|
602
|
+
"KHR",
|
|
603
|
+
"KMF",
|
|
604
|
+
"KRW",
|
|
605
|
+
"KWD",
|
|
606
|
+
"KYD",
|
|
607
|
+
"KZT",
|
|
608
|
+
"LAK",
|
|
609
|
+
"LBP",
|
|
610
|
+
"LKR",
|
|
611
|
+
"LRD",
|
|
612
|
+
"LSL",
|
|
613
|
+
"MAD",
|
|
614
|
+
"MDL",
|
|
615
|
+
"MGA",
|
|
616
|
+
"MKD",
|
|
617
|
+
"MMK",
|
|
618
|
+
"MNT",
|
|
619
|
+
"MOP",
|
|
620
|
+
"MUR",
|
|
621
|
+
"MVR",
|
|
622
|
+
"MWK",
|
|
623
|
+
"MXN",
|
|
624
|
+
"MYR",
|
|
625
|
+
"MZN",
|
|
626
|
+
"NAD",
|
|
627
|
+
"NGN",
|
|
628
|
+
"NIO",
|
|
629
|
+
"NOK",
|
|
630
|
+
"NPR",
|
|
631
|
+
"NZD",
|
|
632
|
+
"OMR",
|
|
633
|
+
"PAB",
|
|
634
|
+
"PEN",
|
|
635
|
+
"PGK",
|
|
636
|
+
"PHP",
|
|
637
|
+
"PKR",
|
|
638
|
+
"PLN",
|
|
639
|
+
"PYG",
|
|
640
|
+
"QAR",
|
|
641
|
+
"RON",
|
|
642
|
+
"RSD",
|
|
643
|
+
"RUB",
|
|
644
|
+
"RWF",
|
|
645
|
+
"SAR",
|
|
646
|
+
"SBD",
|
|
647
|
+
"SCR",
|
|
648
|
+
"SEK",
|
|
649
|
+
"SGD",
|
|
650
|
+
"SHP",
|
|
651
|
+
"SLE",
|
|
652
|
+
"SOS",
|
|
653
|
+
"SRD",
|
|
654
|
+
"STD",
|
|
655
|
+
"SZL",
|
|
656
|
+
"THB",
|
|
657
|
+
"TJS",
|
|
658
|
+
"TND",
|
|
659
|
+
"TOP",
|
|
660
|
+
"TRY",
|
|
661
|
+
"TTD",
|
|
662
|
+
"TWD",
|
|
663
|
+
"TZS",
|
|
664
|
+
"UAH",
|
|
665
|
+
"UGX",
|
|
666
|
+
"USD",
|
|
667
|
+
"UYU",
|
|
668
|
+
"UZS",
|
|
669
|
+
"VND",
|
|
670
|
+
"VUV",
|
|
671
|
+
"WST",
|
|
672
|
+
"XAF",
|
|
673
|
+
"XCD",
|
|
674
|
+
"XCG",
|
|
675
|
+
"XOF",
|
|
676
|
+
"XPF",
|
|
677
|
+
"YER",
|
|
678
|
+
"ZAR",
|
|
679
|
+
"ZMW"
|
|
680
|
+
];
|
|
681
|
+
/**
|
|
682
|
+
* Presentment currencies only accounts registered in the named countries can charge.
|
|
683
|
+
* Stripe: support.stripe.com/questions/which-payments-methods-and-products-are-available-in-the-uae
|
|
684
|
+
* (retrieved 2026-10-07): "We also support charges in the following three-decimal currencies:
|
|
685
|
+
* Bahraini dinar (BHD), Jordanian dinar (JOD), Kuwaiti dinar (KWD), Omani rial (OMR), Tunisian
|
|
686
|
+
* dinar (TND)"; no other country's list on docs.stripe.com/currencies carries them.
|
|
687
|
+
* PayPal: developer.paypal.com/reference/currency-codes (retrieved 2026-10-07) footnotes BRL, CNY
|
|
688
|
+
* and MYR as supported "only for in-country PayPal accounts" (Medusa models the conservative
|
|
689
|
+
* reading: an out-of-country account cannot charge them; unverified by a sandbox probe).
|
|
690
|
+
*/
|
|
691
|
+
const PRESENTMENT_REQUIRES_ACCOUNT_COUNTRY = {
|
|
692
|
+
stripe: {
|
|
693
|
+
BHD: ["AE"],
|
|
694
|
+
JOD: ["AE"],
|
|
695
|
+
KWD: ["AE"],
|
|
696
|
+
OMR: ["AE"],
|
|
697
|
+
TND: ["AE"]
|
|
698
|
+
},
|
|
699
|
+
paypal: {
|
|
700
|
+
BRL: ["BR"],
|
|
701
|
+
CNY: ["CN"],
|
|
702
|
+
MYR: ["MY"]
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
/**
|
|
706
|
+
* Account countries whose card processing takes only the listed currencies.
|
|
707
|
+
* Airwallex: airwallex.com/docs/payments/payment-methods/global/cards (retrieved 2026-10-26,
|
|
708
|
+
* matching Medusa's 2026-10-07 copy): "Supported processing currencies can differ based on where
|
|
709
|
+
* your business is registered and the card scheme." US and MX are listed below; AU, CA, SG, HK,
|
|
710
|
+
* NZ, JP, MY, the EEA, CH, UK and IL take "Visa, MasterCard: All currencies".
|
|
711
|
+
*/
|
|
712
|
+
const PRESENTMENT_ACCOUNT_COUNTRY_ALLOWS_ONLY = { airwallex: {
|
|
713
|
+
US: [
|
|
714
|
+
"USD",
|
|
715
|
+
"EUR",
|
|
716
|
+
"GBP",
|
|
717
|
+
"AUD",
|
|
718
|
+
"SGD",
|
|
719
|
+
"JPY",
|
|
720
|
+
"CHF",
|
|
721
|
+
"NOK",
|
|
722
|
+
"DKK",
|
|
723
|
+
"CAD",
|
|
724
|
+
"NZD",
|
|
725
|
+
"HKD",
|
|
726
|
+
"KRW",
|
|
727
|
+
"PHP",
|
|
728
|
+
"MYR",
|
|
729
|
+
"TWD",
|
|
730
|
+
"VND",
|
|
731
|
+
"EGP",
|
|
732
|
+
"SAR",
|
|
733
|
+
"THB",
|
|
734
|
+
"MXN",
|
|
735
|
+
"TRY",
|
|
736
|
+
"INR",
|
|
737
|
+
"BRL",
|
|
738
|
+
"CNY",
|
|
739
|
+
"PKR",
|
|
740
|
+
"PLN",
|
|
741
|
+
"SEK",
|
|
742
|
+
"NGN",
|
|
743
|
+
"AED",
|
|
744
|
+
"IDR",
|
|
745
|
+
"BDT",
|
|
746
|
+
"ILS",
|
|
747
|
+
"ISK",
|
|
748
|
+
"MAD",
|
|
749
|
+
"ZAR",
|
|
750
|
+
"CZK",
|
|
751
|
+
"HUF",
|
|
752
|
+
"RON",
|
|
753
|
+
"CLP",
|
|
754
|
+
"QAR",
|
|
755
|
+
"COP",
|
|
756
|
+
"RUB",
|
|
757
|
+
"MWK",
|
|
758
|
+
"PEN",
|
|
759
|
+
"BGN",
|
|
760
|
+
"UAH",
|
|
761
|
+
"ARS",
|
|
762
|
+
"MOP",
|
|
763
|
+
"HRK",
|
|
764
|
+
"KZT",
|
|
765
|
+
"ALL",
|
|
766
|
+
"CRC",
|
|
767
|
+
"XOF",
|
|
768
|
+
"KES",
|
|
769
|
+
"UYU",
|
|
770
|
+
"LKR",
|
|
771
|
+
"BOB",
|
|
772
|
+
"PAB",
|
|
773
|
+
"GTQ",
|
|
774
|
+
"DOP",
|
|
775
|
+
"MMK",
|
|
776
|
+
"RSD",
|
|
777
|
+
"BZD",
|
|
778
|
+
"DZD",
|
|
779
|
+
"SHP",
|
|
780
|
+
"HNL",
|
|
781
|
+
"GEL",
|
|
782
|
+
"BAM",
|
|
783
|
+
"MKD",
|
|
784
|
+
"JMD",
|
|
785
|
+
"AWG",
|
|
786
|
+
"BBD",
|
|
787
|
+
"TTD",
|
|
788
|
+
"PYG",
|
|
789
|
+
"XAF"
|
|
790
|
+
],
|
|
791
|
+
MX: ["MXN"]
|
|
792
|
+
} };
|
|
793
|
+
/** Stripe's not-two-decimal set (capabilities/stripe-connect.ts:47), incl. the UAE-only five. */
|
|
794
|
+
const STRIPE_NOT_TWO_DECIMAL = [
|
|
795
|
+
"BIF",
|
|
796
|
+
"CLP",
|
|
797
|
+
"DJF",
|
|
798
|
+
"GNF",
|
|
799
|
+
"JPY",
|
|
800
|
+
"KMF",
|
|
801
|
+
"KRW",
|
|
802
|
+
"MGA",
|
|
803
|
+
"PYG",
|
|
804
|
+
"RWF",
|
|
805
|
+
"UGX",
|
|
806
|
+
"VND",
|
|
807
|
+
"VUV",
|
|
808
|
+
"XAF",
|
|
809
|
+
"XOF",
|
|
810
|
+
"XPF",
|
|
811
|
+
"ISK",
|
|
812
|
+
"BHD",
|
|
813
|
+
"JOD",
|
|
814
|
+
"KWD",
|
|
815
|
+
"OMR",
|
|
816
|
+
"TND"
|
|
817
|
+
];
|
|
818
|
+
/**
|
|
819
|
+
* The ISO 4217 set Stripe's sendable rule is derived over (medusa common/money/iso-currencies.ts,
|
|
820
|
+
* 156 codes). Kept verbatim rather than read from the registry's active list: it still holds BGN,
|
|
821
|
+
* which the registry retired, and Stripe still accepts it.
|
|
822
|
+
*/
|
|
823
|
+
const STRIPE_SENDABLE_ISO_BASE = [
|
|
824
|
+
"AED",
|
|
825
|
+
"AFN",
|
|
826
|
+
"ALL",
|
|
827
|
+
"AMD",
|
|
828
|
+
"AOA",
|
|
829
|
+
"ARS",
|
|
830
|
+
"AUD",
|
|
831
|
+
"AWG",
|
|
832
|
+
"AZN",
|
|
833
|
+
"BAM",
|
|
834
|
+
"BBD",
|
|
835
|
+
"BDT",
|
|
836
|
+
"BGN",
|
|
837
|
+
"BHD",
|
|
838
|
+
"BIF",
|
|
839
|
+
"BMD",
|
|
840
|
+
"BND",
|
|
841
|
+
"BOB",
|
|
842
|
+
"BRL",
|
|
843
|
+
"BSD",
|
|
844
|
+
"BTN",
|
|
845
|
+
"BWP",
|
|
846
|
+
"BYN",
|
|
847
|
+
"BZD",
|
|
848
|
+
"CAD",
|
|
849
|
+
"CDF",
|
|
850
|
+
"CHF",
|
|
851
|
+
"CLP",
|
|
852
|
+
"CNY",
|
|
853
|
+
"COP",
|
|
854
|
+
"CRC",
|
|
855
|
+
"CUP",
|
|
856
|
+
"CVE",
|
|
857
|
+
"CZK",
|
|
858
|
+
"DJF",
|
|
859
|
+
"DKK",
|
|
860
|
+
"DOP",
|
|
861
|
+
"DZD",
|
|
862
|
+
"EGP",
|
|
863
|
+
"ERN",
|
|
864
|
+
"ETB",
|
|
865
|
+
"EUR",
|
|
866
|
+
"FJD",
|
|
867
|
+
"FKP",
|
|
868
|
+
"GBP",
|
|
869
|
+
"GEL",
|
|
870
|
+
"GHS",
|
|
871
|
+
"GIP",
|
|
872
|
+
"GMD",
|
|
873
|
+
"GNF",
|
|
874
|
+
"GTQ",
|
|
875
|
+
"GYD",
|
|
876
|
+
"HKD",
|
|
877
|
+
"HNL",
|
|
878
|
+
"HTG",
|
|
879
|
+
"HUF",
|
|
880
|
+
"IDR",
|
|
881
|
+
"ILS",
|
|
882
|
+
"INR",
|
|
883
|
+
"IQD",
|
|
884
|
+
"IRR",
|
|
885
|
+
"ISK",
|
|
886
|
+
"JMD",
|
|
887
|
+
"JOD",
|
|
888
|
+
"JPY",
|
|
889
|
+
"KES",
|
|
890
|
+
"KGS",
|
|
891
|
+
"KHR",
|
|
892
|
+
"KMF",
|
|
893
|
+
"KPW",
|
|
894
|
+
"KRW",
|
|
895
|
+
"KWD",
|
|
896
|
+
"KYD",
|
|
897
|
+
"KZT",
|
|
898
|
+
"LAK",
|
|
899
|
+
"LBP",
|
|
900
|
+
"LKR",
|
|
901
|
+
"LRD",
|
|
902
|
+
"LSL",
|
|
903
|
+
"LYD",
|
|
904
|
+
"MAD",
|
|
905
|
+
"MDL",
|
|
906
|
+
"MGA",
|
|
907
|
+
"MKD",
|
|
908
|
+
"MMK",
|
|
909
|
+
"MNT",
|
|
910
|
+
"MOP",
|
|
911
|
+
"MRU",
|
|
912
|
+
"MUR",
|
|
913
|
+
"MVR",
|
|
914
|
+
"MWK",
|
|
915
|
+
"MXN",
|
|
916
|
+
"MYR",
|
|
917
|
+
"MZN",
|
|
918
|
+
"NAD",
|
|
919
|
+
"NGN",
|
|
920
|
+
"NIO",
|
|
921
|
+
"NOK",
|
|
922
|
+
"NPR",
|
|
923
|
+
"NZD",
|
|
924
|
+
"OMR",
|
|
925
|
+
"PAB",
|
|
926
|
+
"PEN",
|
|
927
|
+
"PGK",
|
|
928
|
+
"PHP",
|
|
929
|
+
"PKR",
|
|
930
|
+
"PLN",
|
|
931
|
+
"PYG",
|
|
932
|
+
"QAR",
|
|
933
|
+
"RON",
|
|
934
|
+
"RSD",
|
|
935
|
+
"RUB",
|
|
936
|
+
"RWF",
|
|
937
|
+
"SAR",
|
|
938
|
+
"SBD",
|
|
939
|
+
"SCR",
|
|
940
|
+
"SDG",
|
|
941
|
+
"SEK",
|
|
942
|
+
"SGD",
|
|
943
|
+
"SHP",
|
|
944
|
+
"SLE",
|
|
945
|
+
"SOS",
|
|
946
|
+
"SRD",
|
|
947
|
+
"SSP",
|
|
948
|
+
"STN",
|
|
949
|
+
"SVC",
|
|
950
|
+
"SYP",
|
|
951
|
+
"SZL",
|
|
952
|
+
"THB",
|
|
953
|
+
"TJS",
|
|
954
|
+
"TMT",
|
|
955
|
+
"TND",
|
|
956
|
+
"TOP",
|
|
957
|
+
"TRY",
|
|
958
|
+
"TTD",
|
|
959
|
+
"TWD",
|
|
960
|
+
"TZS",
|
|
961
|
+
"UAH",
|
|
962
|
+
"UGX",
|
|
963
|
+
"USD",
|
|
964
|
+
"UYU",
|
|
965
|
+
"UZS",
|
|
966
|
+
"VED",
|
|
967
|
+
"VES",
|
|
968
|
+
"VND",
|
|
969
|
+
"VUV",
|
|
970
|
+
"WST",
|
|
971
|
+
"XAF",
|
|
972
|
+
"XCD",
|
|
973
|
+
"XCG",
|
|
974
|
+
"XOF",
|
|
975
|
+
"XPF",
|
|
976
|
+
"YER",
|
|
977
|
+
"ZAR",
|
|
978
|
+
"ZMW",
|
|
979
|
+
"ZWG"
|
|
980
|
+
];
|
|
981
|
+
const IYZICO_CURRENCIES = [
|
|
982
|
+
"TRY",
|
|
983
|
+
"USD",
|
|
984
|
+
"EUR",
|
|
985
|
+
"GBP",
|
|
986
|
+
"NOK",
|
|
987
|
+
"CHF"
|
|
988
|
+
];
|
|
989
|
+
/** developer.authorize.net createTransactionRequest `currencyCode`, retrieved 2026-10-07. */
|
|
990
|
+
const AUTHORIZE_NET_CURRENCIES = [
|
|
991
|
+
"USD",
|
|
992
|
+
"CAD",
|
|
993
|
+
"GBP",
|
|
994
|
+
"EUR",
|
|
995
|
+
"AUD",
|
|
996
|
+
"NZD",
|
|
997
|
+
"DKK",
|
|
998
|
+
"NOK",
|
|
999
|
+
"SEK",
|
|
1000
|
+
"PLN"
|
|
1001
|
+
];
|
|
1002
|
+
const VND_ONLY = ["VND"];
|
|
1003
|
+
/** orch `MINIMUM_AMOUNT_BY_CURRENCY`, majors. */
|
|
1004
|
+
const PSP_MINIMUM_MAJOR = { paystack: { USD: "2.00" } };
|
|
1005
|
+
/** RBI e-mandate AFA-free cap: INR 15,000 (razorpay-vault `15_000_00` paise, seam `1_500_000`). */
|
|
1006
|
+
const RAZORPAY_AFA_FREE_CAP_MAJOR = "15000";
|
|
1007
|
+
/**
|
|
1008
|
+
* Provider-only codes: currencies a PSP table lists that are not ISO 4217 registry entries (and
|
|
1009
|
+
* not Medusa store currencies), so no Amboras store can sell in them and no Money can carry them.
|
|
1010
|
+
* /wire knows them only to refuse them by name: `pspSupportsCurrency` is false, `pspCurrencies`
|
|
1011
|
+
* leaves them out, and decoding a provider-reported amount in one throws `wire_unsupported`.
|
|
1012
|
+
*/
|
|
1013
|
+
const PROVIDER_ONLY_CURRENCIES = { CNH: {
|
|
1014
|
+
name: "Chinese Yuan (offshore)",
|
|
1015
|
+
psps: ["adyen"]
|
|
1016
|
+
} };
|
|
1017
|
+
//#endregion
|
|
1018
|
+
//#region src/wire/currency.ts
|
|
1019
|
+
/** The registry entry (any case, historic included) for `input`, else null. Never throws. */
|
|
1020
|
+
function wireCurrency(input) {
|
|
1021
|
+
return require_money.findCurrency(input, { allowHistoric: true });
|
|
1022
|
+
}
|
|
1023
|
+
/** The upper-cased code when `input` is a provider-only currency (Adyen CNH), else null. */
|
|
1024
|
+
function providerOnlyCode(input) {
|
|
1025
|
+
if (typeof input !== "string") return null;
|
|
1026
|
+
const code = input.trim().toUpperCase();
|
|
1027
|
+
return require_own.hasOwnKey(PROVIDER_ONLY_CURRENCIES, code) ? code : null;
|
|
1028
|
+
}
|
|
1029
|
+
/** `[<Tag>] CNH is a provider-only currency code ...`: why a provider-only amount is refused. */
|
|
1030
|
+
function providerOnlyRefusal(tag, code) {
|
|
1031
|
+
return `[${tag}] ${code} is a provider-only currency code (${PROVIDER_ONLY_CURRENCIES[code].name}): ${tag} can report it, but it is not an ISO 4217 currency, no Amboras store sells in it, and no Money can hold it.`;
|
|
1032
|
+
}
|
|
1033
|
+
/** `wireCurrency`, throwing `currency_invalid` for an unknown code. */
|
|
1034
|
+
function resolveWireCurrency(input) {
|
|
1035
|
+
const info = wireCurrency(input);
|
|
1036
|
+
if (!info) throw new require_money.MoneyError("currency_invalid", `Unknown currency code: ${require_money.describeValue(input)}`);
|
|
1037
|
+
return info;
|
|
1038
|
+
}
|
|
1039
|
+
/** A Money's currency entry and its stored units, validated like the core's `storedDecimal`. */
|
|
1040
|
+
function wireStored(m) {
|
|
1041
|
+
const info = resolveWireCurrency(m.currency);
|
|
1042
|
+
if (!Number.isSafeInteger(m.amount)) throw new require_money.MoneyError("amount_invalid", `Stored amount must be a safe integer, got ${require_money.describeValue(m.amount)}`);
|
|
1043
|
+
return {
|
|
1044
|
+
info,
|
|
1045
|
+
units: BigInt(m.amount)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
const SAFE_LIMIT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
1049
|
+
/**
|
|
1050
|
+
* A Money's or FractionalMoney's currency entry and its exact stored amount, normalized (scale 0
|
|
1051
|
+
* when whole, so a whole amount reads exactly as `wireStored` reads it). A number must be finite
|
|
1052
|
+
* and safe when whole, text plain decimal; the amount must round within the safe integers.
|
|
1053
|
+
*/
|
|
1054
|
+
function wireStoredDecimal(m) {
|
|
1055
|
+
const info = resolveWireCurrency(m.currency);
|
|
1056
|
+
const { amount } = m;
|
|
1057
|
+
if (typeof amount === "string") {
|
|
1058
|
+
const parsed = require_money.parseDecimal(amount, "plain");
|
|
1059
|
+
if (parsed === null) throw new require_money.MoneyError("amount_invalid", `Stored amount must be plain decimal text, got ${require_money.describeValue(amount)}`);
|
|
1060
|
+
if (!require_money.unitsWithin(require_money.roundToScale(parsed, 0, "up"), SAFE_LIMIT)) throw new require_money.MoneyError("amount_invalid", `Stored amount is beyond the safe integers: ${amount}`);
|
|
1061
|
+
return {
|
|
1062
|
+
info,
|
|
1063
|
+
stored: require_money.normalizeDecimal(parsed)
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
const parsed = typeof amount === "number" ? require_money.strictNumberDecimal(amount) : null;
|
|
1067
|
+
if (parsed === null) throw new require_money.MoneyError("amount_invalid", `Stored amount must be a safe integer, got ${require_money.describeValue(amount)}`);
|
|
1068
|
+
return {
|
|
1069
|
+
info,
|
|
1070
|
+
stored: require_money.normalizeDecimal(parsed)
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/wire/descriptors.ts
|
|
1075
|
+
function digitTable(groups) {
|
|
1076
|
+
const table = /* @__PURE__ */ new Map();
|
|
1077
|
+
for (const digits of [
|
|
1078
|
+
0,
|
|
1079
|
+
2,
|
|
1080
|
+
3
|
|
1081
|
+
]) for (const code of groups[digits] ?? []) table.set(code, digits);
|
|
1082
|
+
return table;
|
|
1083
|
+
}
|
|
1084
|
+
function genericUnsupported(tag) {
|
|
1085
|
+
return (code) => `[${tag}] ${code} is not in ${tag}'s supported currency list.`;
|
|
1086
|
+
}
|
|
1087
|
+
function vndOnly(tag, subject, negation) {
|
|
1088
|
+
return (code) => {
|
|
1089
|
+
return `[${tag}] ${tag} only supports VND - ${subject} ${code}. Switch the region's currency to VND, or ${negation} offer ${tag} in this region.`;
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
const scalesAnyCurrency = () => true;
|
|
1093
|
+
const vndOnlyFacade = (code) => code === "VND";
|
|
1094
|
+
const NONE = /* @__PURE__ */ new Set();
|
|
1095
|
+
const NO_DIGITS = /* @__PURE__ */ new Map();
|
|
1096
|
+
function describe(id, input) {
|
|
1097
|
+
const currencies = new Set(input.currencies);
|
|
1098
|
+
return Object.freeze({
|
|
1099
|
+
id,
|
|
1100
|
+
tag: input.tag,
|
|
1101
|
+
family: input.family,
|
|
1102
|
+
gated: input.gated ?? false,
|
|
1103
|
+
encodeGated: input.encodeGated ?? false,
|
|
1104
|
+
preCutoverNeedsFactorOne: input.preCutoverNeedsFactorOne ?? false,
|
|
1105
|
+
roundsToStoredUnitFirst: input.roundsToStoredUnitFirst ?? false,
|
|
1106
|
+
currencies,
|
|
1107
|
+
presentmentCurrencies: input.presentmentCurrencies ? new Set(input.presentmentCurrencies) : currencies,
|
|
1108
|
+
digits: input.digits ?? NO_DIGITS,
|
|
1109
|
+
sentAsTwoDecimal: input.sentAsTwoDecimal ? new Set(input.sentAsTwoDecimal) : NONE,
|
|
1110
|
+
sentAsZeroDecimal: input.sentAsZeroDecimal ? new Set(input.sentAsZeroDecimal) : NONE,
|
|
1111
|
+
threeDecimalStepOfTen: input.threeDecimalStepOfTen ?? false,
|
|
1112
|
+
wholeUnit: input.wholeUnit ?? null,
|
|
1113
|
+
singleCurrency: input.singleCurrency ?? null,
|
|
1114
|
+
unsupported: input.unsupported ?? genericUnsupported(input.tag),
|
|
1115
|
+
facadeAccepts: input.facadeAccepts ?? scalesAnyCurrency,
|
|
1116
|
+
inboundWholePositive: input.inboundWholePositive ?? false
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
const notTwoDecimalAtStripe = new Set(STRIPE_NOT_TWO_DECIMAL);
|
|
1120
|
+
/**
|
|
1121
|
+
* Stripe's presentment list plus every ISO code that is two-decimal in both storage and display
|
|
1122
|
+
* and not on Stripe's not-two-decimal list (capabilities/stripe-connect.ts:60).
|
|
1123
|
+
*/
|
|
1124
|
+
const STRIPE_SENDABLE = [...STRIPE_PRESENTMENT_CURRENCIES, ...STRIPE_SENDABLE_ISO_BASE.filter((code) => {
|
|
1125
|
+
const info = require_money.findCurrency(code, { allowHistoric: true });
|
|
1126
|
+
return info !== null && !notTwoDecimalAtStripe.has(code) && info.storageDigits === 2 && info.displayDigits === 2;
|
|
1127
|
+
})];
|
|
1128
|
+
const whole = (info, digits) => info.storageDigits === digits;
|
|
1129
|
+
function paystackZeroDecimal(code) {
|
|
1130
|
+
return `[Paystack] ${code} is zero-decimal and Amboras stores it at a x100 scale, so it cannot be sent to Paystack without a currency-aware amount codec. Charging it would overcharge the buyer 100x. Verify the currency against Paystack docs and add an override set before enabling it.`;
|
|
1131
|
+
}
|
|
1132
|
+
const RAZORPAY_DIGITS = digitTable(RAZORPAY_CURRENCY_EXPONENTS);
|
|
1133
|
+
const PSP_DESCRIPTORS = Object.freeze({
|
|
1134
|
+
stripe: describe("stripe", {
|
|
1135
|
+
tag: "StripeConnect",
|
|
1136
|
+
family: "iso-minor",
|
|
1137
|
+
gated: true,
|
|
1138
|
+
encodeGated: true,
|
|
1139
|
+
roundsToStoredUnitFirst: true,
|
|
1140
|
+
currencies: STRIPE_SENDABLE,
|
|
1141
|
+
presentmentCurrencies: STRIPE_PRESENTMENT_CURRENCIES,
|
|
1142
|
+
sentAsTwoDecimal: STRIPE_SENT_AS_TWO_DECIMAL
|
|
1143
|
+
}),
|
|
1144
|
+
adyen: describe("adyen", {
|
|
1145
|
+
tag: "Adyen",
|
|
1146
|
+
family: "iso-minor",
|
|
1147
|
+
gated: true,
|
|
1148
|
+
encodeGated: true,
|
|
1149
|
+
roundsToStoredUnitFirst: true,
|
|
1150
|
+
currencies: digitTable(ADYEN_CURRENCY_DIGITS).keys(),
|
|
1151
|
+
digits: digitTable(ADYEN_CURRENCY_DIGITS),
|
|
1152
|
+
sentAsTwoDecimal: ADYEN_SENT_AS_TWO_DECIMAL,
|
|
1153
|
+
sentAsZeroDecimal: ADYEN_SENT_AS_ZERO_DECIMAL
|
|
1154
|
+
}),
|
|
1155
|
+
razorpay: describe("razorpay", {
|
|
1156
|
+
tag: "Razorpay",
|
|
1157
|
+
family: "iso-minor",
|
|
1158
|
+
gated: true,
|
|
1159
|
+
encodeGated: true,
|
|
1160
|
+
preCutoverNeedsFactorOne: true,
|
|
1161
|
+
threeDecimalStepOfTen: true,
|
|
1162
|
+
currencies: RAZORPAY_DIGITS.keys(),
|
|
1163
|
+
digits: RAZORPAY_DIGITS,
|
|
1164
|
+
sentAsTwoDecimal: RAZORPAY_SENT_AS_TWO_DECIMAL,
|
|
1165
|
+
facadeAccepts: (code) => RAZORPAY_DIGITS.has(code),
|
|
1166
|
+
unsupported: (code) => {
|
|
1167
|
+
return `[Razorpay] ${code} is not in Razorpay's published currency table, so it has no verified minor-unit exponent and cannot be charged through Razorpay.`;
|
|
1168
|
+
}
|
|
1169
|
+
}),
|
|
1170
|
+
paystack: describe("paystack", {
|
|
1171
|
+
tag: "Paystack",
|
|
1172
|
+
family: "iso-minor",
|
|
1173
|
+
gated: true,
|
|
1174
|
+
currencies: digitTable(PAYSTACK_CURRENCY_DIGITS).keys(),
|
|
1175
|
+
digits: digitTable(PAYSTACK_CURRENCY_DIGITS),
|
|
1176
|
+
sentAsTwoDecimal: PAYSTACK_SENT_AS_TWO_DECIMAL,
|
|
1177
|
+
facadeAccepts: (code, info) => info === null || !whole(info, 0) || PAYSTACK_SENT_AS_TWO_DECIMAL.includes(code),
|
|
1178
|
+
wholeUnit: {
|
|
1179
|
+
applies: (info) => PAYSTACK_SENT_AS_TWO_DECIMAL.includes(info.code),
|
|
1180
|
+
message: (info, _stored, _label, major) => {
|
|
1181
|
+
return `[Paystack] ${info.code} ${major} cannot be charged: Paystack takes a whole number of francs and ignores any fraction.`;
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
unsupported: (code, info) => info !== null && whole(info, 0) ? paystackZeroDecimal(code) : genericUnsupported("Paystack")(code)
|
|
1185
|
+
}),
|
|
1186
|
+
momo: describe("momo", {
|
|
1187
|
+
tag: "MoMo",
|
|
1188
|
+
family: "iso-minor",
|
|
1189
|
+
gated: true,
|
|
1190
|
+
currencies: VND_ONLY,
|
|
1191
|
+
singleCurrency: "VND",
|
|
1192
|
+
facadeAccepts: vndOnlyFacade,
|
|
1193
|
+
wholeUnit: {
|
|
1194
|
+
applies: () => true,
|
|
1195
|
+
message: (_info, stored, label) => {
|
|
1196
|
+
return `[MoMo] VND ${label} must resolve to a whole dong value after division by 100 (Medusa stores VND at that scale): ${stored}`;
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1199
|
+
unsupported: vndOnly("MoMo", "this amount is", "do not")
|
|
1200
|
+
}),
|
|
1201
|
+
vnpay: describe("vnpay", {
|
|
1202
|
+
tag: "VNPay",
|
|
1203
|
+
family: "iso-minor",
|
|
1204
|
+
currencies: VND_ONLY,
|
|
1205
|
+
singleCurrency: "VND",
|
|
1206
|
+
facadeAccepts: vndOnlyFacade,
|
|
1207
|
+
sentAsTwoDecimal: VNPAY_SENT_AS_TWO_DECIMAL,
|
|
1208
|
+
inboundWholePositive: true,
|
|
1209
|
+
wholeUnit: {
|
|
1210
|
+
applies: () => true,
|
|
1211
|
+
message: (_info, stored, label) => `[VNPay] VND ${label} must resolve to a whole dong value: ${stored}`
|
|
1212
|
+
},
|
|
1213
|
+
unsupported: vndOnly("VNPay", "this store's region currency is", "don't")
|
|
1214
|
+
}),
|
|
1215
|
+
paypal: describe("paypal", {
|
|
1216
|
+
tag: "PayPal",
|
|
1217
|
+
family: "major-string",
|
|
1218
|
+
currencies: digitTable(PAYPAL_CURRENCY_DIGITS).keys(),
|
|
1219
|
+
digits: digitTable(PAYPAL_CURRENCY_DIGITS)
|
|
1220
|
+
}),
|
|
1221
|
+
mollie: describe("mollie", {
|
|
1222
|
+
tag: "Mollie",
|
|
1223
|
+
family: "major-string",
|
|
1224
|
+
currencies: digitTable(MOLLIE_CURRENCY_DIGITS).keys(),
|
|
1225
|
+
digits: digitTable(MOLLIE_CURRENCY_DIGITS)
|
|
1226
|
+
}),
|
|
1227
|
+
airwallex: describe("airwallex", {
|
|
1228
|
+
tag: "Airwallex",
|
|
1229
|
+
family: "major-number",
|
|
1230
|
+
currencies: digitTable(AIRWALLEX_CURRENCY_DIGITS).keys(),
|
|
1231
|
+
digits: digitTable(AIRWALLEX_CURRENCY_DIGITS)
|
|
1232
|
+
}),
|
|
1233
|
+
iyzico: describe("iyzico", {
|
|
1234
|
+
tag: "iyzico",
|
|
1235
|
+
family: "major-string",
|
|
1236
|
+
currencies: IYZICO_CURRENCIES,
|
|
1237
|
+
unsupported: (code) => {
|
|
1238
|
+
return `[iyzico] Unsupported currency ${code}. Supported currencies: ${IYZICO_CURRENCIES.join(", ")}.`;
|
|
1239
|
+
}
|
|
1240
|
+
}),
|
|
1241
|
+
"authorize-net": describe("authorize-net", {
|
|
1242
|
+
tag: "authorize-net",
|
|
1243
|
+
family: "major-string",
|
|
1244
|
+
currencies: AUTHORIZE_NET_CURRENCIES,
|
|
1245
|
+
unsupported: (code) => `Authorize.Net does not settle ${code}.`
|
|
1246
|
+
}),
|
|
1247
|
+
payoo: describe("payoo", {
|
|
1248
|
+
tag: "Payoo",
|
|
1249
|
+
family: "major-number",
|
|
1250
|
+
currencies: VND_ONLY,
|
|
1251
|
+
singleCurrency: "VND",
|
|
1252
|
+
facadeAccepts: vndOnlyFacade,
|
|
1253
|
+
wholeUnit: {
|
|
1254
|
+
applies: () => true,
|
|
1255
|
+
message: (_info, stored) => {
|
|
1256
|
+
return `[Payoo] VND amount must resolve to a whole dong value after /100 (Medusa stores VND x100): ${stored}`;
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
unsupported: vndOnly("Payoo", "this store's region currency is", "don't")
|
|
1260
|
+
})
|
|
1261
|
+
});
|
|
1262
|
+
/** Every PSP id, in a stable order. */
|
|
1263
|
+
const PSP_IDS = Object.freeze(Object.keys(PSP_DESCRIPTORS));
|
|
1264
|
+
/** The descriptor for `psp`; an unknown id throws `wire_unsupported`. */
|
|
1265
|
+
function pspDescriptor(psp) {
|
|
1266
|
+
if (typeof psp === "string" && require_own.hasOwnKey(PSP_DESCRIPTORS, psp)) return PSP_DESCRIPTORS[psp];
|
|
1267
|
+
throw new require_money.MoneyError("wire_unsupported", `Unknown payment provider: ${require_money.describeValue(psp)}`);
|
|
1268
|
+
}
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/wire/marker.ts
|
|
1271
|
+
const WIRE_SCALE_KEY = "amboras_wire_scale";
|
|
1272
|
+
const WIRE_SCALE_ISO_MINOR_V2 = "iso-minor-v2";
|
|
1273
|
+
const WIRE_SCALE_MARKER = Object.freeze({ [WIRE_SCALE_KEY]: WIRE_SCALE_ISO_MINOR_V2 });
|
|
1274
|
+
const ISO_MINOR_V2 = Object.freeze({ kind: "iso-minor-v2" });
|
|
1275
|
+
const PRE_CUTOVER = Object.freeze({ kind: "pre-cutover" });
|
|
1276
|
+
/**
|
|
1277
|
+
* `ISO_MINOR_V2` iff ANY record is a non-null object whose `amboras_wire_scale` is strictly
|
|
1278
|
+
* `'iso-minor-v2'` (a Stripe charge OR its PaymentIntent); else `PRE_CUTOVER`. Reads already
|
|
1279
|
+
* fetched records only: it never queries (FORCE RLS would hide the row and the marker with it).
|
|
1280
|
+
*/
|
|
1281
|
+
function conventionOf(...records) {
|
|
1282
|
+
return records.some((r) => typeof r === "object" && r !== null && r["amboras_wire_scale"] === "iso-minor-v2") ? ISO_MINOR_V2 : PRE_CUTOVER;
|
|
1283
|
+
}
|
|
1284
|
+
/** Internal: the convention's kind, or null when `value` is not a Convention. */
|
|
1285
|
+
function conventionKind(value) {
|
|
1286
|
+
const kind = typeof value === "object" && value !== null ? value.kind : void 0;
|
|
1287
|
+
return kind === "iso-minor-v2" || kind === "pre-cutover" ? kind : null;
|
|
1288
|
+
}
|
|
1289
|
+
//#endregion
|
|
1290
|
+
//#region src/wire/codec.ts
|
|
1291
|
+
const isBlank = (value) => value === null || value === void 0 || typeof value === "string" && value.trim() === "";
|
|
1292
|
+
const shownCode = (currency) => typeof currency === "string" ? currency.trim().toUpperCase() : require_money.describeValue(currency);
|
|
1293
|
+
function missingCurrency(d) {
|
|
1294
|
+
const message = `[${d.tag}] Cannot scale an amount without a currency code; session data is missing currency_code.`;
|
|
1295
|
+
return new require_money.MoneyError("currency_missing", message);
|
|
1296
|
+
}
|
|
1297
|
+
function supported(d, currency) {
|
|
1298
|
+
const info = wireCurrency(currency);
|
|
1299
|
+
return info !== null && d.currencies.has(info.code) ? info : null;
|
|
1300
|
+
}
|
|
1301
|
+
/** Whether `psp` takes `currency` (any case, historic codes included). Never throws for a bad code. */
|
|
1302
|
+
function pspSupportsCurrency(psp, currency) {
|
|
1303
|
+
return supported(pspDescriptor(psp), currency) !== null;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Every currency `psp` takes. Provider-only codes a PSP table lists (Adyen offshore CNH) are left
|
|
1307
|
+
* out: they are not ISO 4217 currencies, no store sells in them and no Money can carry them.
|
|
1308
|
+
*/
|
|
1309
|
+
function pspCurrencies(psp) {
|
|
1310
|
+
const d = pspDescriptor(psp);
|
|
1311
|
+
const codes = [];
|
|
1312
|
+
for (const code of d.currencies) {
|
|
1313
|
+
const info = wireCurrency(code);
|
|
1314
|
+
if (info) codes.push(info.code);
|
|
1315
|
+
}
|
|
1316
|
+
return new Set(codes.sort());
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* `currency` as a CurrencyCode `psp` takes. Empty throws `currency_missing` (`[<Tag>] Cannot
|
|
1320
|
+
* scale an amount without a currency code; ...`); anything the PSP does not take throws
|
|
1321
|
+
* `wire_unsupported` with the provider's own message.
|
|
1322
|
+
*/
|
|
1323
|
+
function assertPspCurrency(psp, currency) {
|
|
1324
|
+
const d = pspDescriptor(psp);
|
|
1325
|
+
if (isBlank(currency)) throw missingCurrency(d);
|
|
1326
|
+
const info = supported(d, currency);
|
|
1327
|
+
if (info) return info.code;
|
|
1328
|
+
const providerOnly = providerOnlyCode(currency);
|
|
1329
|
+
if (providerOnly !== null) throw new require_money.MoneyError("wire_unsupported", providerOnlyRefusal(d.tag, providerOnly));
|
|
1330
|
+
throw new require_money.MoneyError("wire_unsupported", d.unsupported(shownCode(currency), wireCurrency(currency)));
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* The gate Medusa's amount facade for `psp` applies, on encode and decode alike: most scale any
|
|
1334
|
+
* currency (the PSP's list is eligibility's job: `assertPspCurrency`); Razorpay, Paystack, MoMo,
|
|
1335
|
+
* Payoo and VNPay refuse with their own message (provider-only CNH included, as in Medusa).
|
|
1336
|
+
* Returns the registry entry, else throws (`wire_unsupported` for a facade refusal or a
|
|
1337
|
+
* provider-only code, `currency_invalid` for an unknown code).
|
|
1338
|
+
*/
|
|
1339
|
+
function facadeCurrency(d, currency) {
|
|
1340
|
+
const info = wireCurrency(currency);
|
|
1341
|
+
const code = info?.code ?? shownCode(currency);
|
|
1342
|
+
if (!d.facadeAccepts(code, info)) throw new require_money.MoneyError("wire_unsupported", d.unsupported(code, info));
|
|
1343
|
+
const providerOnly = providerOnlyCode(currency);
|
|
1344
|
+
if (providerOnly !== null) throw new require_money.MoneyError("wire_unsupported", providerOnlyRefusal(d.tag, providerOnly));
|
|
1345
|
+
return info ?? resolveWireCurrency(currency);
|
|
1346
|
+
}
|
|
1347
|
+
/** `'VND'` for the single-currency providers (MoMo, Payoo, VNPay), else null. */
|
|
1348
|
+
function pspDefaultCurrency(psp) {
|
|
1349
|
+
const single = pspDescriptor(psp).singleCurrency;
|
|
1350
|
+
return single === null ? null : single;
|
|
1351
|
+
}
|
|
1352
|
+
function factorOf(d, info) {
|
|
1353
|
+
if (d.sentAsTwoDecimal.has(info.code)) return 1n;
|
|
1354
|
+
if (d.sentAsZeroDecimal.has(info.code)) return 100n;
|
|
1355
|
+
return require_iso_minor.isoMinorFactor(info, info.storageDigits);
|
|
1356
|
+
}
|
|
1357
|
+
function requireIsoMinor(d) {
|
|
1358
|
+
if (d.family !== "iso-minor") throw new require_money.MoneyError("wire_unsupported", `[${d.tag}] ${d.tag} does not take ISO minor units`);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Stored units per wire unit for an ISO-minor PSP: the PSP's override (Stripe ISK/UGX, Adyen
|
|
1362
|
+
* ISK/CLP, Razorpay MGA, Paystack XOF, VNPay VND give 1; Adyen IDR/CVE give 100), else
|
|
1363
|
+
* `storageScale / 10^storageDigits` (100 for a zero-decimal currency, else 1).
|
|
1364
|
+
*/
|
|
1365
|
+
function wireFactor(psp, currency) {
|
|
1366
|
+
const d = pspDescriptor(psp);
|
|
1367
|
+
requireIsoMinor(d);
|
|
1368
|
+
return Number(factorOf(d, resolveWireCurrency(currency)));
|
|
1369
|
+
}
|
|
1370
|
+
function wireDigitsOf(d, info) {
|
|
1371
|
+
const table = d.digits.get(info.code);
|
|
1372
|
+
if (table !== void 0) return table;
|
|
1373
|
+
if (d.family === "iso-minor") return factorOf(d, info) === 1n ? require_money.storageExponent(info) : 0;
|
|
1374
|
+
return info.displayDigits;
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Fraction digits of `psp`'s wire amount: its digit table, else (ISO-minor PSPs) the digits
|
|
1378
|
+
* its factor implies, else (iyzico, Authorize.Net, Payoo) the display digits. Null when the
|
|
1379
|
+
* PSP does not take the currency.
|
|
1380
|
+
*/
|
|
1381
|
+
function pspWireDigits(psp, currency) {
|
|
1382
|
+
const d = pspDescriptor(psp);
|
|
1383
|
+
const info = supported(d, currency);
|
|
1384
|
+
return info === null ? null : wireDigitsOf(d, info);
|
|
1385
|
+
}
|
|
1386
|
+
/** The PSP's minimum charge in `currency` (Paystack USD 2.00), or null when it has none. */
|
|
1387
|
+
function pspMinimumAmount(psp, currency) {
|
|
1388
|
+
const d = pspDescriptor(psp);
|
|
1389
|
+
const info = wireCurrency(currency);
|
|
1390
|
+
if (info === null) return null;
|
|
1391
|
+
const major = PSP_MINIMUM_MAJOR[d.id]?.[info.code];
|
|
1392
|
+
return major === void 0 ? null : require_money.fromMajor(major, info.code);
|
|
1393
|
+
}
|
|
1394
|
+
function requireConvention(d, convention) {
|
|
1395
|
+
const kind = conventionKind(convention);
|
|
1396
|
+
if (kind === null) {
|
|
1397
|
+
const message = `[${d.tag}] A wire convention is required: pass conventionOf(record) for this payment.`;
|
|
1398
|
+
throw new require_money.MoneyError("wire_refused", message);
|
|
1399
|
+
}
|
|
1400
|
+
return kind;
|
|
1401
|
+
}
|
|
1402
|
+
function encodeWire(psp, m, options = {}) {
|
|
1403
|
+
const d = pspDescriptor(psp);
|
|
1404
|
+
const { info, stored: held } = wireStoredDecimal(m);
|
|
1405
|
+
facadeCurrency(d, info.code);
|
|
1406
|
+
const label = options.label ?? "amount";
|
|
1407
|
+
const shown = require_money.formatDecimal(held);
|
|
1408
|
+
const exponent = require_money.storageExponent(info);
|
|
1409
|
+
const majorText = require_money.formatDecimal({
|
|
1410
|
+
units: held.units,
|
|
1411
|
+
scale: exponent + held.scale
|
|
1412
|
+
});
|
|
1413
|
+
if (held.units < 0n || held.units === 0n && !options.allowZero) throw new require_money.MoneyError("wire_refused", `[${d.tag}] Invalid ${label}: ${shown}`);
|
|
1414
|
+
const stored = d.roundsToStoredUnitFirst ? require_money.roundToScale(held, 0, "half-up") : held;
|
|
1415
|
+
const positive = held.units > 0n;
|
|
1416
|
+
const factor = d.family === "iso-minor" ? factorOf(d, info) : 1n;
|
|
1417
|
+
if (d.encodeGated && requireConvention(d, options.convention) === "pre-cutover") {
|
|
1418
|
+
if (!(stored.scale === 0 && (!d.preCutoverNeedsFactorOne || require_money.divRound(stored.units, factor, "half-up") === stored.units))) throw new require_money.MoneyError("wire_refused", preCutoverRefusal(d.tag, info.code));
|
|
1419
|
+
if (stored.units === 0n && positive) throw subUnitRefusal(d, info, majorText, exponent, label);
|
|
1420
|
+
return Number(stored.units);
|
|
1421
|
+
}
|
|
1422
|
+
if (d.wholeUnit?.applies(info) && stored.units % (BigInt(info.storageScale) * require_money.pow10(stored.scale)) !== 0n) throw new require_money.MoneyError("wire_refused", d.wholeUnit.message(info, shown, label, majorText));
|
|
1423
|
+
const digits = wireDigitsOf(d, info);
|
|
1424
|
+
if (d.family === "iso-minor") {
|
|
1425
|
+
const exact = require_money.divRound(stored.units, factor * require_money.pow10(stored.scale), "half-up");
|
|
1426
|
+
if (exact === 0n && positive) throw subUnitRefusal(d, info, majorText, digits, label);
|
|
1427
|
+
if (!d.threeDecimalStepOfTen || d.digits.get(info.code) !== 3) return Number(exact);
|
|
1428
|
+
const stepped = exact - exact % 10n;
|
|
1429
|
+
if (stepped === 0n && exact > 0n) throw new require_money.MoneyError("wire_refused", roundsToNothing(d.tag, info.code, majorText));
|
|
1430
|
+
return Number(stepped);
|
|
1431
|
+
}
|
|
1432
|
+
const rounded = require_money.roundToScale({
|
|
1433
|
+
units: stored.units,
|
|
1434
|
+
scale: exponent + stored.scale
|
|
1435
|
+
}, digits, "half-up");
|
|
1436
|
+
if (rounded.units === 0n && positive) throw subUnitRefusal(d, info, majorText, digits, label);
|
|
1437
|
+
const text = require_money.formatDecimal(rounded);
|
|
1438
|
+
return d.family === "major-number" ? Number(text) : text;
|
|
1439
|
+
}
|
|
1440
|
+
function subUnitRefusal(d, info, major, digits, label) {
|
|
1441
|
+
const unit = require_money.formatDecimal({
|
|
1442
|
+
units: 1n,
|
|
1443
|
+
scale: digits
|
|
1444
|
+
});
|
|
1445
|
+
const message = `[${d.tag}] Invalid ${label}: ${info.code} ${major} rounds to 0 at ${d.tag}, whose smallest ${info.code} unit is ${unit}; refusing to send a zero amount.`;
|
|
1446
|
+
return new require_money.MoneyError("wire_refused", message);
|
|
1447
|
+
}
|
|
1448
|
+
function preCutoverRefusal(tag, code) {
|
|
1449
|
+
return `[${tag}] This ${code} session predates the wire-scale marker, so ${tag} may hold storage units for it; converting would move 1/100th of the amount. Refusing rather than guessing.`;
|
|
1450
|
+
}
|
|
1451
|
+
function roundsToNothing(tag, code, major) {
|
|
1452
|
+
return `[${tag}] ${code} ${major} rounds to nothing at ${tag}, which takes three-decimal amounts in steps of 0.010.`;
|
|
1453
|
+
}
|
|
1454
|
+
/** A wire value as an exact Decimal: a finite number (a whole one must be safe) or strict decimal text. Else null. */
|
|
1455
|
+
function wireDecimal(wire) {
|
|
1456
|
+
return typeof wire === "number" ? require_money.strictNumberDecimal(wire) : require_money.parseDecimal(wire, "plain");
|
|
1457
|
+
}
|
|
1458
|
+
/** `w` at storage scale, half-up; with `exact`, a wire naming a fraction of a storage unit throws `amount_invalid`. */
|
|
1459
|
+
function decodeWith(d, w, info, kind, exact = false) {
|
|
1460
|
+
const scale = require_money.pow10(w.scale);
|
|
1461
|
+
let multiplier;
|
|
1462
|
+
if (d.family !== "iso-minor") multiplier = BigInt(info.storageScale);
|
|
1463
|
+
else multiplier = kind === "pre-cutover" ? 1n : factorOf(d, info);
|
|
1464
|
+
if (exact && w.units * multiplier % scale !== 0n) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] ${require_money.formatDecimal(w)} is not a whole ${info.code} storage amount`);
|
|
1465
|
+
return require_money.boundedMoney(require_money.mulDiv(w.units, multiplier, scale, "half-up"), info);
|
|
1466
|
+
}
|
|
1467
|
+
/** VNPay: a positive whole safe integer, as Medusa's `vnpayWireAmountToStorage` reads one. */
|
|
1468
|
+
function isWholePositive(w) {
|
|
1469
|
+
const scale = require_money.pow10(w.scale);
|
|
1470
|
+
return w.units > 0n && w.units % scale === 0n && w.units / scale <= BigInt(Number.MAX_SAFE_INTEGER);
|
|
1471
|
+
}
|
|
1472
|
+
/** Every inbound check: facade currency gate, convention, strict wire text, VNPay's "no amount", the sign. */
|
|
1473
|
+
function decodeChecked(d, wire, currency, convention, allowNegative, exact = false) {
|
|
1474
|
+
if (isBlank(currency)) throw missingCurrency(d);
|
|
1475
|
+
const info = facadeCurrency(d, currency);
|
|
1476
|
+
const kind = d.gated ? requireConvention(d, convention) : null;
|
|
1477
|
+
const w = wireDecimal(wire);
|
|
1478
|
+
if (!w) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] Not a wire amount: ${require_money.describeValue(wire)}`);
|
|
1479
|
+
if (d.inboundWholePositive && !isWholePositive(w)) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] No amount: ${d.tag} amounts are positive whole numbers, got ${require_money.describeValue(wire)}`);
|
|
1480
|
+
if (w.units < 0n && !allowNegative) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] Negative wire amount: ${require_money.describeValue(wire)}`);
|
|
1481
|
+
return decodeWith(d, w, info, kind, exact);
|
|
1482
|
+
}
|
|
1483
|
+
function decodeWire(psp, wire, currency, convention) {
|
|
1484
|
+
return decodeChecked(pspDescriptor(psp), wire, currency, convention, true);
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* `decodeWire` that never throws: undefined wherever it would throw (null, undefined, `''`,
|
|
1488
|
+
* whitespace, unparseable or unsafe input, a missing, unknown or provider-only currency, one the
|
|
1489
|
+
* PSP's inbound gate refuses, VNPay's "no amount", an unknown PSP, a gated PSP without
|
|
1490
|
+
* `convention`: no hidden default) and for a negative amount unless `allowNegative`.
|
|
1491
|
+
*/
|
|
1492
|
+
function tryDecodeWire(psp, wire, currency, options = {}) {
|
|
1493
|
+
try {
|
|
1494
|
+
return decodeChecked(pspDescriptor(psp), wire, currency, options.convention, options.allowNegative === true);
|
|
1495
|
+
} catch {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Internal: `tryDecodeWire` (no negatives) that is also undefined for a wire naming a fraction of a
|
|
1501
|
+
* storage unit (Airwallex 43.004 is not USD 43.00): the comparators' reading of a reported amount.
|
|
1502
|
+
*/
|
|
1503
|
+
function tryDecodeWireExactly(psp, wire, currency, convention) {
|
|
1504
|
+
try {
|
|
1505
|
+
return decodeChecked(pspDescriptor(psp), wire, currency, convention, false, true);
|
|
1506
|
+
} catch {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/** Internal: the major-unit text of `m` at its display digits, half-up (the pre-PSP-table ISO spelling). */
|
|
1511
|
+
function legacyIsoMajor(m) {
|
|
1512
|
+
const { info, stored } = wireStoredDecimal(m);
|
|
1513
|
+
const major = {
|
|
1514
|
+
units: stored.units,
|
|
1515
|
+
scale: require_money.storageExponent(info) + stored.scale
|
|
1516
|
+
};
|
|
1517
|
+
return require_money.formatDecimal(require_money.roundToScale(major, info.displayDigits, "half-up"));
|
|
1518
|
+
}
|
|
1519
|
+
/** Internal: one unit of the PSP's last wire digit as Money. */
|
|
1520
|
+
function wireUnit(psp, currency, convention) {
|
|
1521
|
+
const d = pspDescriptor(psp);
|
|
1522
|
+
const info = resolveWireCurrency(currency);
|
|
1523
|
+
if (d.family === "iso-minor") return decodeWith(d, {
|
|
1524
|
+
units: 1n,
|
|
1525
|
+
scale: 0
|
|
1526
|
+
}, info, d.gated ? requireConvention(d, convention) : null);
|
|
1527
|
+
return require_money.boundedMoney(require_iso_minor.isoMinorFactor(info, wireDigitsOf(d, info)), info);
|
|
1528
|
+
}
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/wire/breakdown.ts
|
|
1531
|
+
/**
|
|
1532
|
+
* `total` and its `parts` (same currency, non-negative, adding up to `total`) as integer wire
|
|
1533
|
+
* amounts whose sum is exactly `encodeWire(psp, total)`: the wire total split by largest
|
|
1534
|
+
* remainder over the stored parts. Throws `currency_mismatch`, `amount_invalid` (parts that do not
|
|
1535
|
+
* add up, or a negative part) or whatever `encodeWire` refuses.
|
|
1536
|
+
*/
|
|
1537
|
+
function wireBreakdown(psp, total, parts, convention) {
|
|
1538
|
+
const { info } = wireStored(total);
|
|
1539
|
+
const names = Object.keys(parts);
|
|
1540
|
+
const weights = names.map((name) => {
|
|
1541
|
+
const part = wireStored(parts[name]);
|
|
1542
|
+
if (part.info.code !== info.code) throw new require_money.MoneyError("currency_mismatch", `Part ${name} is ${part.info.code}, total is ${info.code}`);
|
|
1543
|
+
return part.units;
|
|
1544
|
+
});
|
|
1545
|
+
const partsTotal = weights.reduce((a, b) => a + b, 0n);
|
|
1546
|
+
if (partsTotal !== BigInt(total.amount)) throw new require_money.MoneyError("amount_invalid", `Parts add up to ${partsTotal}, not the total ${total.amount}`);
|
|
1547
|
+
const wireTotal = encodeWire(psp, total, {
|
|
1548
|
+
convention,
|
|
1549
|
+
allowZero: true
|
|
1550
|
+
});
|
|
1551
|
+
const shares = require_allocate.allocateUnits(BigInt(wireTotal), weights);
|
|
1552
|
+
const out = {};
|
|
1553
|
+
names.forEach((name, i) => {
|
|
1554
|
+
out[name] = Number(shares[i]);
|
|
1555
|
+
});
|
|
1556
|
+
return {
|
|
1557
|
+
total: wireTotal,
|
|
1558
|
+
parts: out
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
//#endregion
|
|
1562
|
+
//#region src/wire/compare.ts
|
|
1563
|
+
/**
|
|
1564
|
+
* Whether a PSP-reported amount is `expected`, a Money or a FractionalMoney (Medusa's unrounded
|
|
1565
|
+
* session total). The expected wire is `encodeWire(psp, expected, { convention, allowZero: true })`
|
|
1566
|
+
* (a refusal means false); the reported one must decode exactly (strict like `tryDecodeWire`:
|
|
1567
|
+
* never `''` or an exponent, no negatives, and never a fraction of a storage unit) to the same
|
|
1568
|
+
* stored amount, so `'44.00'` matches a sent `'44'` and Airwallex `43.004` does not match `43`.
|
|
1569
|
+
* Never throws.
|
|
1570
|
+
*/
|
|
1571
|
+
function sameWireAmount(psp, reported, expected, options = {}) {
|
|
1572
|
+
const { convention } = options;
|
|
1573
|
+
if (options.reportedCurrency !== void 0) {
|
|
1574
|
+
const reportedInfo = wireCurrency(options.reportedCurrency);
|
|
1575
|
+
if (reportedInfo === null || reportedInfo !== wireCurrency(expected.currency)) return false;
|
|
1576
|
+
}
|
|
1577
|
+
const targets = expectedTargets(psp, expected, convention, options.acceptLegacyIsoDigits === true);
|
|
1578
|
+
if (targets === null) return false;
|
|
1579
|
+
const { target, legacy } = targets;
|
|
1580
|
+
const decode = (value) => options.textOnly && typeof value !== "string" ? void 0 : tryDecodeWireExactly(psp, value, expected.currency, convention);
|
|
1581
|
+
if (options.policy === "requested-exact-collected-covers" && options.requested !== void 0 && options.requested !== null) {
|
|
1582
|
+
const requested = decode(options.requested);
|
|
1583
|
+
const collected = decode(reported);
|
|
1584
|
+
if (requested === void 0 || collected === void 0) return false;
|
|
1585
|
+
return requested.amount === target.amount && collected.amount >= target.amount;
|
|
1586
|
+
}
|
|
1587
|
+
const got = decode(reported);
|
|
1588
|
+
if (got === void 0) return false;
|
|
1589
|
+
return got.amount === target.amount || got.amount === legacy?.amount;
|
|
1590
|
+
}
|
|
1591
|
+
/** What `expected` decodes to once sent (and in its legacy ISO-digit spelling), or null when it cannot be sent. */
|
|
1592
|
+
function expectedTargets(psp, expected, convention, acceptLegacy) {
|
|
1593
|
+
try {
|
|
1594
|
+
const d = pspDescriptor(psp);
|
|
1595
|
+
const wire = encodeWire(psp, expected, {
|
|
1596
|
+
convention,
|
|
1597
|
+
allowZero: true
|
|
1598
|
+
});
|
|
1599
|
+
const target = decodeAt(psp, wire, expected, convention);
|
|
1600
|
+
if (!acceptLegacy || d.family === "iso-minor") return {
|
|
1601
|
+
target,
|
|
1602
|
+
legacy: void 0
|
|
1603
|
+
};
|
|
1604
|
+
const spelling = legacyIsoMajor(expected);
|
|
1605
|
+
return {
|
|
1606
|
+
target,
|
|
1607
|
+
legacy: spelling === String(wire) ? void 0 : decodeAt(psp, spelling, expected, convention)
|
|
1608
|
+
};
|
|
1609
|
+
} catch {
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
function decodeAt(psp, wire, expected, convention) {
|
|
1614
|
+
return decodeWire(psp, wire, expected.currency, convention);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* One unit of the PSP's last wire digit, as Money: for ISO-minor PSPs `decodeWire(psp, 1)` (the
|
|
1618
|
+
* factor on an iso-minor-v2 payment, 1 storage unit on a pre-cutover one; gated PSPs need the
|
|
1619
|
+
* convention), for major PSPs `10^-wireDigits` major. The tolerance for `excessOver`.
|
|
1620
|
+
*/
|
|
1621
|
+
function oneWireUnit(psp, currency, convention) {
|
|
1622
|
+
return wireUnit(psp, currency, convention);
|
|
1623
|
+
}
|
|
1624
|
+
/** The wire amount as it goes into Redis skip keys and idempotency keys: `String(wire)`, byte for byte. */
|
|
1625
|
+
function wireKeyPart(wire) {
|
|
1626
|
+
return String(wire);
|
|
1627
|
+
}
|
|
1628
|
+
//#endregion
|
|
1629
|
+
//#region src/data/destinations.ts
|
|
1630
|
+
const pixel = (currencyCase, unit = "major-number") => ({
|
|
1631
|
+
unit,
|
|
1632
|
+
currencyCase,
|
|
1633
|
+
accepts: "non-negative",
|
|
1634
|
+
verified: true
|
|
1635
|
+
});
|
|
1636
|
+
const marketing = (currencyCase) => ({
|
|
1637
|
+
unit: "major-number",
|
|
1638
|
+
currencyCase,
|
|
1639
|
+
accepts: "non-negative",
|
|
1640
|
+
verified: true
|
|
1641
|
+
});
|
|
1642
|
+
const ledger = (unit = "major-number") => ({
|
|
1643
|
+
unit,
|
|
1644
|
+
currencyCase: "upper",
|
|
1645
|
+
accepts: "any",
|
|
1646
|
+
verified: true
|
|
1647
|
+
});
|
|
1648
|
+
const DESTINATIONS = {
|
|
1649
|
+
meta: {
|
|
1650
|
+
unit: "major-number",
|
|
1651
|
+
currencyCase: "lower",
|
|
1652
|
+
accepts: "positive",
|
|
1653
|
+
verified: true
|
|
1654
|
+
},
|
|
1655
|
+
ga4: pixel("upper"),
|
|
1656
|
+
"google-ads": pixel("upper"),
|
|
1657
|
+
tiktok: pixel("upper"),
|
|
1658
|
+
snapchat: pixel("upper"),
|
|
1659
|
+
pinterest: pixel("upper", "major-string"),
|
|
1660
|
+
klaviyo: marketing("lower"),
|
|
1661
|
+
omnisend: marketing("lower"),
|
|
1662
|
+
"omnisend-viewed-product": {
|
|
1663
|
+
unit: "cents",
|
|
1664
|
+
cents: "two-decimal",
|
|
1665
|
+
currencyCase: "lower",
|
|
1666
|
+
accepts: "non-negative",
|
|
1667
|
+
verified: true
|
|
1668
|
+
},
|
|
1669
|
+
mailchimp: marketing("upper"),
|
|
1670
|
+
goaffpro: marketing("lower"),
|
|
1671
|
+
profitmetrics: marketing("upper"),
|
|
1672
|
+
pixi: marketing("lower"),
|
|
1673
|
+
flow: ledger(),
|
|
1674
|
+
sendcloud: ledger(),
|
|
1675
|
+
shipstation: ledger(),
|
|
1676
|
+
shiprocket: ledger(),
|
|
1677
|
+
shipbob: ledger(),
|
|
1678
|
+
printful: ledger("major-string"),
|
|
1679
|
+
ahamove: ledger(),
|
|
1680
|
+
"amazon-mcf": ledger(),
|
|
1681
|
+
skydropx: ledger(),
|
|
1682
|
+
"royal-mail": ledger(),
|
|
1683
|
+
sevdesk: ledger(),
|
|
1684
|
+
"shopify-ajax": {
|
|
1685
|
+
unit: "cents",
|
|
1686
|
+
cents: "x100-storage",
|
|
1687
|
+
currencyCase: "upper",
|
|
1688
|
+
accepts: "any",
|
|
1689
|
+
verified: true
|
|
1690
|
+
}
|
|
1691
|
+
};
|
|
1692
|
+
//#endregion
|
|
1693
|
+
//#region src/wire/destinations.ts
|
|
1694
|
+
function descriptorOf(dest) {
|
|
1695
|
+
if (typeof dest === "string" && require_own.hasOwnKey(DESTINATIONS, dest)) return DESTINATIONS[dest];
|
|
1696
|
+
throw new require_money.MoneyError("wire_unsupported", `Unknown destination: ${require_money.describeValue(dest)}`);
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* `m` as `dest` wants it: major units at the display digits (half-up) as a number or text per
|
|
1700
|
+
* destination (`as` overrides), or integer "cents" for Shopify AJAX and Omnisend `viewed product`;
|
|
1701
|
+
* the currency upper- or lower-case per destination. Undefined when the destination must not get
|
|
1702
|
+
* this amount (Meta: not positive; GA4, Google Ads, TikTok, Snapchat, Pinterest and marketing:
|
|
1703
|
+
* negative). Throws `wire_unsupported` for a currency the destination's unit is not defined for.
|
|
1704
|
+
*/
|
|
1705
|
+
function destinationAmount(dest, m, options = {}) {
|
|
1706
|
+
const d = descriptorOf(dest);
|
|
1707
|
+
const { info, value } = require_money.storedDecimal(m);
|
|
1708
|
+
if (d.accepts === "positive" ? value.units <= 0n : d.accepts === "non-negative" && value.units < 0n) return void 0;
|
|
1709
|
+
let amount;
|
|
1710
|
+
if (d.unit === "cents") {
|
|
1711
|
+
if (!(d.cents === "two-decimal" ? info.displayDigits === 2 : info.storageScale === 100)) throw new require_money.MoneyError("wire_unsupported", `${dest} has no documented integer unit for ${info.code}`);
|
|
1712
|
+
const cents = require_money.mulDiv(value.units, 100n, BigInt(info.storageScale), "half-up");
|
|
1713
|
+
amount = options.as === "string" ? cents.toString() : Number(cents);
|
|
1714
|
+
} else {
|
|
1715
|
+
const text = require_money.toMajorDecimal(m, { digits: "display" });
|
|
1716
|
+
amount = (options.as ? options.as === "string" : d.unit === "major-string") ? text : Number(text);
|
|
1717
|
+
}
|
|
1718
|
+
return {
|
|
1719
|
+
value: amount,
|
|
1720
|
+
currency: d.currencyCase === "lower" ? require_money.medusaCurrencyCode(info.code) : info.code
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
/** A shopping-feed price: `'12.345 KWD'`, `'1200 JPY'`, `'19.99 USD'` (display digits, half-up). */
|
|
1724
|
+
function feedPrice(m) {
|
|
1725
|
+
return `${require_money.toMajorDecimal(m, { digits: "display" })} ${require_money.storedDecimal(m).info.code}`;
|
|
1726
|
+
}
|
|
1727
|
+
/** Google micros (major x 10^6) as integer text, exactly: USD 1999 gives `'19990000'`. */
|
|
1728
|
+
function googleMicros(m) {
|
|
1729
|
+
const { info, value } = require_money.storedDecimal(m);
|
|
1730
|
+
return require_money.mulDiv(value.units, 1000000n, BigInt(info.storageScale), "half-up").toString();
|
|
1731
|
+
}
|
|
1732
|
+
/** First-party analytics money: the stored integer and the lower-case code (the ClickHouse `Int64` contract). */
|
|
1733
|
+
function eventMoney(m) {
|
|
1734
|
+
const { info } = require_money.storedDecimal(m);
|
|
1735
|
+
return {
|
|
1736
|
+
value: m.amount,
|
|
1737
|
+
currency: require_money.medusaCurrencyCode(info.code)
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
//#endregion
|
|
1741
|
+
//#region src/wire/presentment.ts
|
|
1742
|
+
/** Registry codes only, sorted: a provider-only code (Adyen CNH) or one the registry does not know is left out. */
|
|
1743
|
+
function knownCodes(codes) {
|
|
1744
|
+
const known = [];
|
|
1745
|
+
for (const code of codes) {
|
|
1746
|
+
const info = wireCurrency(code);
|
|
1747
|
+
if (info) known.push(info.code);
|
|
1748
|
+
}
|
|
1749
|
+
return known.sort();
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* The PSP's presentment rules, as data: the union of currencies some account can charge, the
|
|
1753
|
+
* currencies that need an account in particular countries, and the account countries limited to
|
|
1754
|
+
* a list. For capability declarations that hold the rules themselves; `pspPresentmentCurrencies`
|
|
1755
|
+
* applies them to one account. Fresh collections on every call. An unknown PSP throws
|
|
1756
|
+
* `wire_unsupported`.
|
|
1757
|
+
*/
|
|
1758
|
+
function pspPresentment(psp) {
|
|
1759
|
+
const d = pspDescriptor(psp);
|
|
1760
|
+
const requires = PRESENTMENT_REQUIRES_ACCOUNT_COUNTRY[d.id] ?? {};
|
|
1761
|
+
const allowsOnly = PRESENTMENT_ACCOUNT_COUNTRY_ALLOWS_ONLY[d.id] ?? {};
|
|
1762
|
+
return Object.freeze({
|
|
1763
|
+
currencies: new Set(knownCodes(d.presentmentCurrencies)),
|
|
1764
|
+
currencyRequiresAccountCountry: new Map(knownCodes(Object.keys(requires)).map((code) => [code, new Set(requires[code])])),
|
|
1765
|
+
accountCountryAllowsOnly: new Map(Object.keys(allowsOnly).map((country) => [country, new Set(knownCodes(allowsOnly[country]))]))
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* The currencies `psp` lets an account present (charge buyers in). Without `accountCountry`, the
|
|
1770
|
+
* union over every account country; with it (ISO 3166 alpha-2, any case), only what an account
|
|
1771
|
+
* registered there can charge: Stripe's three-decimal five need a UAE account, PayPal's BRL, CNY
|
|
1772
|
+
* and MYR an in-country one, Airwallex cards on a US account take its documented 76 and on an MX
|
|
1773
|
+
* account MXN only. Not `pspCurrencies`, which is what the codec scales and the facade lets
|
|
1774
|
+
* through (Stripe's adds every two-decimal ISO code it accepts on the wire).
|
|
1775
|
+
*/
|
|
1776
|
+
function pspPresentmentCurrencies(psp, options = {}) {
|
|
1777
|
+
const rules = pspPresentment(psp);
|
|
1778
|
+
if (options.accountCountry === void 0) return rules.currencies;
|
|
1779
|
+
const country = options.accountCountry.trim().toUpperCase();
|
|
1780
|
+
const only = rules.accountCountryAllowsOnly.get(country);
|
|
1781
|
+
const codes = [...rules.currencies].filter((code) => {
|
|
1782
|
+
const required = rules.currencyRequiresAccountCountry.get(code);
|
|
1783
|
+
return (required === void 0 || required.has(country)) && (only === void 0 || only.has(code));
|
|
1784
|
+
});
|
|
1785
|
+
return new Set(codes);
|
|
1786
|
+
}
|
|
1787
|
+
//#endregion
|
|
1788
|
+
//#region src/wire/provider-amount.ts
|
|
1789
|
+
/**
|
|
1790
|
+
* A provider's major-unit amount (finite number, or text read by the separator engine, which
|
|
1791
|
+
* strips the currency's symbol and code) as Money, rounded once to the storage digits. Null on
|
|
1792
|
+
* anything invalid (empty, unparseable, non-finite, negative without `allowNegative`, too large,
|
|
1793
|
+
* unknown currency), never 0.
|
|
1794
|
+
*/
|
|
1795
|
+
function parseProviderAmount(value, currency, options = {}) {
|
|
1796
|
+
let major;
|
|
1797
|
+
try {
|
|
1798
|
+
if (typeof value === "number") {
|
|
1799
|
+
if (!Number.isFinite(value)) return null;
|
|
1800
|
+
major = value;
|
|
1801
|
+
} else {
|
|
1802
|
+
const read = require_number_text.readNumberText(value, {
|
|
1803
|
+
decimalSeparator: options.decimalSeparator ?? ".",
|
|
1804
|
+
allowTrailingText: options.allowTrailingText,
|
|
1805
|
+
currency,
|
|
1806
|
+
rounding: options.rounding ?? "half-up"
|
|
1807
|
+
});
|
|
1808
|
+
if ("reason" in read) return null;
|
|
1809
|
+
major = require_money.formatDecimal({
|
|
1810
|
+
units: BigInt(read.sign) * read.units,
|
|
1811
|
+
scale: read.scale
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
if (!options.allowNegative && (typeof major === "number" ? major < 0 : major.startsWith("-"))) return null;
|
|
1815
|
+
return require_money.fromMajor(major, currency, {
|
|
1816
|
+
precision: "round",
|
|
1817
|
+
rounding: options.rounding ?? "half-up"
|
|
1818
|
+
});
|
|
1819
|
+
} catch {
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
//#endregion
|
|
1824
|
+
//#region src/wire/providers.ts
|
|
1825
|
+
/** RBI e-mandate cap Razorpay token registrations carry as `max_amount`: INR 15,000 (1,500,000 paise = stored). */
|
|
1826
|
+
const RAZORPAY_AFA_FREE_CAP = Object.freeze(require_money.fromMajor(RAZORPAY_AFA_FREE_CAP_MAJOR, "INR"));
|
|
1827
|
+
/**
|
|
1828
|
+
* The symbolic charge a saved-card setup makes: Paystack one major unit, Razorpay one INR (any
|
|
1829
|
+
* other currency is `wire_unsupported`), Mollie one smallest wire unit (`'0.01'`, `'1'` for ISK
|
|
1830
|
+
* and JPY), Adyen zero. Vault charges are always new, so the iso-minor-v2 convention applies.
|
|
1831
|
+
*/
|
|
1832
|
+
function verificationCharge(psp, currency) {
|
|
1833
|
+
const code = assertPspCurrency(psp, currency);
|
|
1834
|
+
let charge;
|
|
1835
|
+
if (psp === "paystack") charge = require_money.fromMajor("1", code);
|
|
1836
|
+
else if (psp === "razorpay") {
|
|
1837
|
+
if (code !== "INR") throw new require_money.MoneyError("wire_unsupported", `[Razorpay] Verification charges are made in INR only, not ${code}.`);
|
|
1838
|
+
charge = require_money.fromMajor("1", code);
|
|
1839
|
+
} else if (psp === "mollie") charge = wireUnit("mollie", code);
|
|
1840
|
+
else charge = require_money.boundedMoney(0n, resolveWireCurrency(code));
|
|
1841
|
+
return {
|
|
1842
|
+
wire: encodeWire(psp, charge, {
|
|
1843
|
+
convention: ISO_MINOR_V2,
|
|
1844
|
+
allowZero: true
|
|
1845
|
+
}),
|
|
1846
|
+
money: charge
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* iyzico's price spelling, on the text (no float): canonical decimal (no trailing fraction zeros),
|
|
1851
|
+
* then `'.0'` when there is no `.` (`'10.50'` gives `'10.5'`, `'10.00'` gives `'10.0'`). Text that
|
|
1852
|
+
* is not a plain decimal throws `parse`.
|
|
1853
|
+
*/
|
|
1854
|
+
function iyzicoPrice(wire) {
|
|
1855
|
+
const parsed = require_money.parseDecimal(wire, "plain");
|
|
1856
|
+
if (!parsed) throw new require_money.MoneyError("parse", `Not an iyzico price: ${require_money.describeValue(wire)}`);
|
|
1857
|
+
const text = require_money.toDecimalText(parsed);
|
|
1858
|
+
return text.includes(".") ? text : `${text}.0`;
|
|
1859
|
+
}
|
|
1860
|
+
//#endregion
|
|
1861
|
+
//#region src/wire/slot.ts
|
|
1862
|
+
/** `m` for a slot context's `money` key. */
|
|
1863
|
+
function slotMoney(m) {
|
|
1864
|
+
const { info } = require_money.storedDecimal(m);
|
|
1865
|
+
return {
|
|
1866
|
+
amount: m.amount,
|
|
1867
|
+
currency_code: require_money.medusaCurrencyCode(info.code)
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
1871
|
+
function read(amount, ...currencies) {
|
|
1872
|
+
const info = currencies.map((c) => require_money.findCurrency(c, { allowHistoric: true })).find((c) => c !== null);
|
|
1873
|
+
if (!info || amount === void 0 || amount === null) return void 0;
|
|
1874
|
+
const stored = require_money.readStored(amount, info.code, { mode: "lenient" });
|
|
1875
|
+
return stored ? slotMoney(stored) : void 0;
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* The money a slot context carries: `context.money` when it is valid, else the legacy
|
|
1879
|
+
* `total ?? price` with the first known of `currency_code`, `currency`, read leniently (numbers, numeric text,
|
|
1880
|
+
* Medusa BigNumber shapes). Undefined when the amount or the currency is missing or unreadable.
|
|
1881
|
+
*/
|
|
1882
|
+
function readSlotMoney(context) {
|
|
1883
|
+
if (!isRecord(context)) return void 0;
|
|
1884
|
+
const money = context.money;
|
|
1885
|
+
return (isRecord(money) ? read(money.amount, money.currency_code) : void 0) ?? read(context.total ?? context.price, context.currency_code, context.currency);
|
|
1886
|
+
}
|
|
1887
|
+
//#endregion
|
|
1888
|
+
Object.defineProperty(exports, "ISO_MINOR_V2", {
|
|
1889
|
+
enumerable: true,
|
|
1890
|
+
get: function() {
|
|
1891
|
+
return ISO_MINOR_V2;
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
Object.defineProperty(exports, "PRE_CUTOVER", {
|
|
1895
|
+
enumerable: true,
|
|
1896
|
+
get: function() {
|
|
1897
|
+
return PRE_CUTOVER;
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
1900
|
+
Object.defineProperty(exports, "PSP_IDS", {
|
|
1901
|
+
enumerable: true,
|
|
1902
|
+
get: function() {
|
|
1903
|
+
return PSP_IDS;
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
Object.defineProperty(exports, "RAZORPAY_AFA_FREE_CAP", {
|
|
1907
|
+
enumerable: true,
|
|
1908
|
+
get: function() {
|
|
1909
|
+
return RAZORPAY_AFA_FREE_CAP;
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
Object.defineProperty(exports, "WIRE_SCALE_ISO_MINOR_V2", {
|
|
1913
|
+
enumerable: true,
|
|
1914
|
+
get: function() {
|
|
1915
|
+
return WIRE_SCALE_ISO_MINOR_V2;
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
Object.defineProperty(exports, "WIRE_SCALE_KEY", {
|
|
1919
|
+
enumerable: true,
|
|
1920
|
+
get: function() {
|
|
1921
|
+
return WIRE_SCALE_KEY;
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
Object.defineProperty(exports, "WIRE_SCALE_MARKER", {
|
|
1925
|
+
enumerable: true,
|
|
1926
|
+
get: function() {
|
|
1927
|
+
return WIRE_SCALE_MARKER;
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
Object.defineProperty(exports, "assertPspCurrency", {
|
|
1931
|
+
enumerable: true,
|
|
1932
|
+
get: function() {
|
|
1933
|
+
return assertPspCurrency;
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
Object.defineProperty(exports, "authorizeNetReversibleAmount", {
|
|
1937
|
+
enumerable: true,
|
|
1938
|
+
get: function() {
|
|
1939
|
+
return authorizeNetReversibleAmount;
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
Object.defineProperty(exports, "conventionOf", {
|
|
1943
|
+
enumerable: true,
|
|
1944
|
+
get: function() {
|
|
1945
|
+
return conventionOf;
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
Object.defineProperty(exports, "decodeWire", {
|
|
1949
|
+
enumerable: true,
|
|
1950
|
+
get: function() {
|
|
1951
|
+
return decodeWire;
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
Object.defineProperty(exports, "destinationAmount", {
|
|
1955
|
+
enumerable: true,
|
|
1956
|
+
get: function() {
|
|
1957
|
+
return destinationAmount;
|
|
1958
|
+
}
|
|
1959
|
+
});
|
|
1960
|
+
Object.defineProperty(exports, "encodeWire", {
|
|
1961
|
+
enumerable: true,
|
|
1962
|
+
get: function() {
|
|
1963
|
+
return encodeWire;
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
Object.defineProperty(exports, "eventMoney", {
|
|
1967
|
+
enumerable: true,
|
|
1968
|
+
get: function() {
|
|
1969
|
+
return eventMoney;
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
Object.defineProperty(exports, "feedPrice", {
|
|
1973
|
+
enumerable: true,
|
|
1974
|
+
get: function() {
|
|
1975
|
+
return feedPrice;
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
Object.defineProperty(exports, "googleMicros", {
|
|
1979
|
+
enumerable: true,
|
|
1980
|
+
get: function() {
|
|
1981
|
+
return googleMicros;
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
Object.defineProperty(exports, "iyzicoPrice", {
|
|
1985
|
+
enumerable: true,
|
|
1986
|
+
get: function() {
|
|
1987
|
+
return iyzicoPrice;
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
Object.defineProperty(exports, "jsonDecimalLiteral", {
|
|
1991
|
+
enumerable: true,
|
|
1992
|
+
get: function() {
|
|
1993
|
+
return jsonDecimalLiteral;
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
Object.defineProperty(exports, "oneWireUnit", {
|
|
1997
|
+
enumerable: true,
|
|
1998
|
+
get: function() {
|
|
1999
|
+
return oneWireUnit;
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
Object.defineProperty(exports, "parseProviderAmount", {
|
|
2003
|
+
enumerable: true,
|
|
2004
|
+
get: function() {
|
|
2005
|
+
return parseProviderAmount;
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
Object.defineProperty(exports, "pspCurrencies", {
|
|
2009
|
+
enumerable: true,
|
|
2010
|
+
get: function() {
|
|
2011
|
+
return pspCurrencies;
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
Object.defineProperty(exports, "pspDefaultCurrency", {
|
|
2015
|
+
enumerable: true,
|
|
2016
|
+
get: function() {
|
|
2017
|
+
return pspDefaultCurrency;
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
Object.defineProperty(exports, "pspMinimumAmount", {
|
|
2021
|
+
enumerable: true,
|
|
2022
|
+
get: function() {
|
|
2023
|
+
return pspMinimumAmount;
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
Object.defineProperty(exports, "pspPresentment", {
|
|
2027
|
+
enumerable: true,
|
|
2028
|
+
get: function() {
|
|
2029
|
+
return pspPresentment;
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
Object.defineProperty(exports, "pspPresentmentCurrencies", {
|
|
2033
|
+
enumerable: true,
|
|
2034
|
+
get: function() {
|
|
2035
|
+
return pspPresentmentCurrencies;
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
Object.defineProperty(exports, "pspSupportsCurrency", {
|
|
2039
|
+
enumerable: true,
|
|
2040
|
+
get: function() {
|
|
2041
|
+
return pspSupportsCurrency;
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
Object.defineProperty(exports, "pspWireDigits", {
|
|
2045
|
+
enumerable: true,
|
|
2046
|
+
get: function() {
|
|
2047
|
+
return pspWireDigits;
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
Object.defineProperty(exports, "readSlotMoney", {
|
|
2051
|
+
enumerable: true,
|
|
2052
|
+
get: function() {
|
|
2053
|
+
return readSlotMoney;
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
Object.defineProperty(exports, "sameWireAmount", {
|
|
2057
|
+
enumerable: true,
|
|
2058
|
+
get: function() {
|
|
2059
|
+
return sameWireAmount;
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
Object.defineProperty(exports, "slotMoney", {
|
|
2063
|
+
enumerable: true,
|
|
2064
|
+
get: function() {
|
|
2065
|
+
return slotMoney;
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
Object.defineProperty(exports, "tryDecodeWire", {
|
|
2069
|
+
enumerable: true,
|
|
2070
|
+
get: function() {
|
|
2071
|
+
return tryDecodeWire;
|
|
2072
|
+
}
|
|
2073
|
+
});
|
|
2074
|
+
Object.defineProperty(exports, "verificationCharge", {
|
|
2075
|
+
enumerable: true,
|
|
2076
|
+
get: function() {
|
|
2077
|
+
return verificationCharge;
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
Object.defineProperty(exports, "wireBreakdown", {
|
|
2081
|
+
enumerable: true,
|
|
2082
|
+
get: function() {
|
|
2083
|
+
return wireBreakdown;
|
|
2084
|
+
}
|
|
2085
|
+
});
|
|
2086
|
+
Object.defineProperty(exports, "wireFactor", {
|
|
2087
|
+
enumerable: true,
|
|
2088
|
+
get: function() {
|
|
2089
|
+
return wireFactor;
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
2092
|
+
Object.defineProperty(exports, "wireKeyPart", {
|
|
2093
|
+
enumerable: true,
|
|
2094
|
+
get: function() {
|
|
2095
|
+
return wireKeyPart;
|
|
2096
|
+
}
|
|
2097
|
+
});
|