@amboras-dev/money 0.1.0 → 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.
Files changed (76) hide show
  1. package/README.md +14 -6
  2. package/dist/ai.cjs +4 -4
  3. package/dist/ai.d.cts +1 -1
  4. package/dist/ai.d.ts +1 -1
  5. package/dist/ai.js +4 -4
  6. package/dist/{allocate-D7tm_t_o.cjs → allocate-CZRg4TOP.cjs} +2 -2
  7. package/dist/{allocate-CdnhvAda.js → allocate-X7_jAtOj.js} +2 -2
  8. package/dist/{amount-DHtvZSSm.cjs → amount-BV0O-aN8.cjs} +3 -3
  9. package/dist/{amount-D7dj76nH.js → amount-BoYBMgK9.js} +3 -3
  10. package/dist/{arithmetic-C-Q_aI49.js → arithmetic-BhNwisjb.js} +3 -3
  11. package/dist/{arithmetic-Bq-8mSal.cjs → arithmetic-DjT4Kzgt.cjs} +3 -3
  12. package/dist/billing.cjs +2 -2
  13. package/dist/billing.d.cts +1 -1
  14. package/dist/billing.d.ts +1 -1
  15. package/dist/billing.js +2 -2
  16. package/dist/commerce.cjs +7 -7
  17. package/dist/commerce.d.cts +2 -2
  18. package/dist/commerce.d.ts +2 -2
  19. package/dist/commerce.js +7 -7
  20. package/dist/{format-BdB7LEIp.js → format-BdPnijvn.js} +11 -2
  21. package/dist/{format-BnA1aLJk.cjs → format-DLhno7eX.cjs} +16 -1
  22. package/dist/fx.cjs +5 -5
  23. package/dist/fx.d.cts +5 -5
  24. package/dist/fx.d.ts +5 -5
  25. package/dist/fx.js +5 -5
  26. package/dist/index.cjs +10 -7
  27. package/dist/index.d.cts +15 -3
  28. package/dist/index.d.ts +15 -3
  29. package/dist/index.js +8 -8
  30. package/dist/input.cjs +4 -4
  31. package/dist/input.d.cts +2 -2
  32. package/dist/input.d.ts +2 -2
  33. package/dist/input.js +4 -4
  34. package/dist/{iso-minor-CtaiIifs.js → iso-minor-CqkWDxHS.js} +1 -1
  35. package/dist/{iso-minor--gQWI1f2.cjs → iso-minor-wSwJN57T.cjs} +1 -1
  36. package/dist/{medusa-DuQ3-KoD.d.cts → medusa-4K8N1pUc.d.cts} +11 -2
  37. package/dist/{medusa-RwqxaDGq.d.ts → medusa-DBXJXXvF.d.ts} +11 -2
  38. package/dist/{medusa-CmxCni-G.js → medusa-DqJY1qpj.js} +17 -2
  39. package/dist/{medusa-OJ2TJOxt.cjs → medusa-lRY9xlsf.cjs} +22 -1
  40. package/dist/{money-CbhsT0fB.d.cts → money-B-3aFjNJ.d.cts} +19 -3
  41. package/dist/{money-CbhsT0fB.d.ts → money-B-3aFjNJ.d.ts} +19 -3
  42. package/dist/{money-D5-vlv0h.cjs → money-B3wH_Z4S.cjs} +35 -5
  43. package/dist/{money-CIgELekG.js → money-WhnGiI00.js} +18 -6
  44. package/dist/{number-text-BpvN6NAo.cjs → number-text-BWu67jUq.cjs} +1 -1
  45. package/dist/{number-text-C82oh6JB.js → number-text-DE-q4jO5.js} +1 -1
  46. package/dist/{percent-Dt8vNIAW.cjs → percent-C0InC2t2.cjs} +1 -1
  47. package/dist/{percent-CKVI_c-m.js → percent-D1VB9xXG.js} +1 -1
  48. package/dist/{rate-DGPfB_jc.cjs → rate-CVeMii4a.cjs} +1 -1
  49. package/dist/{rate-CmeTGNex.d.ts → rate-DBPiYgT1.d.ts} +1 -1
  50. package/dist/{rate-lLPViBuM.js → rate-DicStBue.js} +1 -1
  51. package/dist/{rate-toyS3EMf.d.cts → rate-tqK8ijG0.d.cts} +1 -1
  52. package/dist/{reporting-DEM97DhQ.d.cts → reporting-CG-ai2sM.d.cts} +2 -2
  53. package/dist/{reporting-5xby-Zas.d.ts → reporting-tpGJbqYp.d.ts} +2 -2
  54. package/dist/{retail-BsvpNv1F.js → retail-1mu888J1.js} +3 -3
  55. package/dist/{retail-Mqd-sPMF.d.cts → retail-C_BPLvMj.d.cts} +1 -1
  56. package/dist/{retail-DTk2XAMM.cjs → retail-Cml8YSKg.cjs} +3 -3
  57. package/dist/{retail-BwcFJcVN.d.ts → retail-DRUTTQoJ.d.ts} +1 -1
  58. package/dist/{rounding-oZ832yTD.cjs → rounding-DHBtNCHG.cjs} +1 -1
  59. package/dist/{rounding-CVAPEfq_.js → rounding-g9rvye0k.js} +1 -1
  60. package/dist/sql.cjs +3 -3
  61. package/dist/sql.d.cts +2 -2
  62. package/dist/sql.d.ts +2 -2
  63. package/dist/sql.js +3 -3
  64. package/dist/testing.cjs +33481 -4
  65. package/dist/testing.d.cts +31 -3
  66. package/dist/testing.d.ts +31 -3
  67. package/dist/testing.js +33481 -5
  68. package/dist/{wire-DQZ6fA3c.cjs → wire-Bhuut8cW.cjs} +259 -34
  69. package/dist/{wire-Cd4Q1Jmm.d.ts → wire-CaUN9Vf_.d.ts} +61 -21
  70. package/dist/{wire-CI3Sm1iC.js → wire-sW0yzqth.js} +248 -35
  71. package/dist/{wire-hWuixv3w.d.cts → wire-xjYXv4go.d.cts} +61 -21
  72. package/dist/wire.cjs +3 -1
  73. package/dist/wire.d.cts +2 -2
  74. package/dist/wire.d.ts +2 -2
  75. package/dist/wire.js +2 -2
  76. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
- const require_money = require("./money-D5-vlv0h.cjs");
2
- const require_allocate = require("./allocate-D7tm_t_o.cjs");
1
+ const require_money = require("./money-B3wH_Z4S.cjs");
2
+ const require_allocate = require("./allocate-CZRg4TOP.cjs");
3
3
  const require_own = require("./own-oP9DXFWv.cjs");
4
- const require_iso_minor = require("./iso-minor--gQWI1f2.cjs");
5
- const require_number_text = require("./number-text-BpvN6NAo.cjs");
4
+ const require_iso_minor = require("./iso-minor-wSwJN57T.cjs");
5
+ const require_number_text = require("./number-text-BWu67jUq.cjs");
6
6
  //#region src/wire/anet.ts
7
7
  const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8
8
  /**
@@ -678,6 +678,118 @@ const STRIPE_PRESENTMENT_CURRENCIES = [
678
678
  "ZAR",
679
679
  "ZMW"
680
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
+ } };
681
793
  /** Stripe's not-two-decimal set (capabilities/stripe-connect.ts:47), incl. the UAE-only five. */
682
794
  const STRIPE_NOT_TWO_DECIMAL = [
683
795
  "BIF",
@@ -933,6 +1045,31 @@ function wireStored(m) {
933
1045
  units: BigInt(m.amount)
934
1046
  };
935
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
+ }
936
1073
  //#endregion
937
1074
  //#region src/wire/descriptors.ts
938
1075
  function digitTable(groups) {
@@ -957,6 +1094,7 @@ const vndOnlyFacade = (code) => code === "VND";
957
1094
  const NONE = /* @__PURE__ */ new Set();
958
1095
  const NO_DIGITS = /* @__PURE__ */ new Map();
959
1096
  function describe(id, input) {
1097
+ const currencies = new Set(input.currencies);
960
1098
  return Object.freeze({
961
1099
  id,
962
1100
  tag: input.tag,
@@ -964,7 +1102,9 @@ function describe(id, input) {
964
1102
  gated: input.gated ?? false,
965
1103
  encodeGated: input.encodeGated ?? false,
966
1104
  preCutoverNeedsFactorOne: input.preCutoverNeedsFactorOne ?? false,
967
- currencies: new Set(input.currencies),
1105
+ roundsToStoredUnitFirst: input.roundsToStoredUnitFirst ?? false,
1106
+ currencies,
1107
+ presentmentCurrencies: input.presentmentCurrencies ? new Set(input.presentmentCurrencies) : currencies,
968
1108
  digits: input.digits ?? NO_DIGITS,
969
1109
  sentAsTwoDecimal: input.sentAsTwoDecimal ? new Set(input.sentAsTwoDecimal) : NONE,
970
1110
  sentAsZeroDecimal: input.sentAsZeroDecimal ? new Set(input.sentAsZeroDecimal) : NONE,
@@ -996,7 +1136,9 @@ const PSP_DESCRIPTORS = Object.freeze({
996
1136
  family: "iso-minor",
997
1137
  gated: true,
998
1138
  encodeGated: true,
1139
+ roundsToStoredUnitFirst: true,
999
1140
  currencies: STRIPE_SENDABLE,
1141
+ presentmentCurrencies: STRIPE_PRESENTMENT_CURRENCIES,
1000
1142
  sentAsTwoDecimal: STRIPE_SENT_AS_TWO_DECIMAL
1001
1143
  }),
1002
1144
  adyen: describe("adyen", {
@@ -1004,6 +1146,7 @@ const PSP_DESCRIPTORS = Object.freeze({
1004
1146
  family: "iso-minor",
1005
1147
  gated: true,
1006
1148
  encodeGated: true,
1149
+ roundsToStoredUnitFirst: true,
1007
1150
  currencies: digitTable(ADYEN_CURRENCY_DIGITS).keys(),
1008
1151
  digits: digitTable(ADYEN_CURRENCY_DIGITS),
1009
1152
  sentAsTwoDecimal: ADYEN_SENT_AS_TWO_DECIMAL,
@@ -1258,32 +1401,39 @@ function requireConvention(d, convention) {
1258
1401
  }
1259
1402
  function encodeWire(psp, m, options = {}) {
1260
1403
  const d = pspDescriptor(psp);
1261
- const { info, units } = wireStored(m);
1262
- const value = {
1263
- units,
1264
- scale: require_money.storageExponent(info)
1265
- };
1404
+ const { info, stored: held } = wireStoredDecimal(m);
1266
1405
  facadeCurrency(d, info.code);
1267
1406
  const label = options.label ?? "amount";
1268
- const stored = value.units;
1269
- if (stored < 0n || stored === 0n && !options.allowZero) throw new require_money.MoneyError("wire_refused", `[${d.tag}] Invalid ${label}: ${m.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;
1270
1416
  const factor = d.family === "iso-minor" ? factorOf(d, info) : 1n;
1271
1417
  if (d.encodeGated && requireConvention(d, options.convention) === "pre-cutover") {
1272
- if (d.preCutoverNeedsFactorOne && require_money.divRound(stored, factor, "half-up") !== stored) throw new require_money.MoneyError("wire_refused", preCutoverRefusal(d.tag, info.code));
1273
- return m.amount;
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);
1274
1421
  }
1275
- if (d.wholeUnit?.applies(info) && stored % BigInt(info.storageScale) !== 0n) throw new require_money.MoneyError("wire_refused", d.wholeUnit.message(info, m.amount, label, require_money.formatDecimal(value)));
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));
1276
1423
  const digits = wireDigitsOf(d, info);
1277
1424
  if (d.family === "iso-minor") {
1278
- const exact = require_money.divRound(stored, factor, "half-up");
1279
- if (exact === 0n && stored > 0n) throw subUnitRefusal(d, info, require_money.formatDecimal(value), digits, label);
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);
1280
1427
  if (!d.threeDecimalStepOfTen || d.digits.get(info.code) !== 3) return Number(exact);
1281
1428
  const stepped = exact - exact % 10n;
1282
- if (stepped === 0n && exact > 0n) throw new require_money.MoneyError("wire_refused", roundsToNothing(d.tag, info.code, require_money.formatDecimal(value)));
1429
+ if (stepped === 0n && exact > 0n) throw new require_money.MoneyError("wire_refused", roundsToNothing(d.tag, info.code, majorText));
1283
1430
  return Number(stepped);
1284
1431
  }
1285
- const rounded = require_money.roundToScale(value, digits, "half-up");
1286
- if (rounded.units === 0n && stored > 0n) throw subUnitRefusal(d, info, require_money.formatDecimal(value), digits, label);
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);
1287
1437
  const text = require_money.formatDecimal(rounded);
1288
1438
  return d.family === "major-number" ? Number(text) : text;
1289
1439
  }
@@ -1305,11 +1455,13 @@ function roundsToNothing(tag, code, major) {
1305
1455
  function wireDecimal(wire) {
1306
1456
  return typeof wire === "number" ? require_money.strictNumberDecimal(wire) : require_money.parseDecimal(wire, "plain");
1307
1457
  }
1308
- function decodeWith(d, w, info, kind) {
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) {
1309
1460
  const scale = require_money.pow10(w.scale);
1310
1461
  let multiplier;
1311
1462
  if (d.family !== "iso-minor") multiplier = BigInt(info.storageScale);
1312
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`);
1313
1465
  return require_money.boundedMoney(require_money.mulDiv(w.units, multiplier, scale, "half-up"), info);
1314
1466
  }
1315
1467
  /** VNPay: a positive whole safe integer, as Medusa's `vnpayWireAmountToStorage` reads one. */
@@ -1318,7 +1470,7 @@ function isWholePositive(w) {
1318
1470
  return w.units > 0n && w.units % scale === 0n && w.units / scale <= BigInt(Number.MAX_SAFE_INTEGER);
1319
1471
  }
1320
1472
  /** Every inbound check: facade currency gate, convention, strict wire text, VNPay's "no amount", the sign. */
1321
- function decodeChecked(d, wire, currency, convention, allowNegative) {
1473
+ function decodeChecked(d, wire, currency, convention, allowNegative, exact = false) {
1322
1474
  if (isBlank(currency)) throw missingCurrency(d);
1323
1475
  const info = facadeCurrency(d, currency);
1324
1476
  const kind = d.gated ? requireConvention(d, convention) : null;
@@ -1326,7 +1478,7 @@ function decodeChecked(d, wire, currency, convention, allowNegative) {
1326
1478
  if (!w) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] Not a wire amount: ${require_money.describeValue(wire)}`);
1327
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)}`);
1328
1480
  if (w.units < 0n && !allowNegative) throw new require_money.MoneyError("amount_invalid", `[${d.tag}] Negative wire amount: ${require_money.describeValue(wire)}`);
1329
- return decodeWith(d, w, info, kind);
1481
+ return decodeWith(d, w, info, kind, exact);
1330
1482
  }
1331
1483
  function decodeWire(psp, wire, currency, convention) {
1332
1484
  return decodeChecked(pspDescriptor(psp), wire, currency, convention, true);
@@ -1344,13 +1496,25 @@ function tryDecodeWire(psp, wire, currency, options = {}) {
1344
1496
  return;
1345
1497
  }
1346
1498
  }
1347
- /** Internal: the major-unit text of `m` at its display digits (the pre-PSP-table ISO spelling). */
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). */
1348
1511
  function legacyIsoMajor(m) {
1349
- const { info, units } = wireStored(m);
1350
- return require_money.formatDecimal(require_money.roundToScale({
1351
- units,
1352
- scale: require_money.storageExponent(info)
1353
- }, info.displayDigits, "half-up"));
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"));
1354
1518
  }
1355
1519
  /** Internal: one unit of the PSP's last wire digit as Money. */
1356
1520
  function wireUnit(psp, currency, convention) {
@@ -1397,10 +1561,12 @@ function wireBreakdown(psp, total, parts, convention) {
1397
1561
  //#endregion
1398
1562
  //#region src/wire/compare.ts
1399
1563
  /**
1400
- * Whether a PSP-reported amount is `expected`. The expected wire is `encodeWire(psp, expected,
1401
- * { convention, allowZero: true })` (a refusal means false); the reported one must decode
1402
- * (`tryDecodeWire`: strict, never `''` or an exponent, no negatives) to the same stored amount,
1403
- * so `'44.00'` matches a sent `'44'`. Never throws.
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.
1404
1570
  */
1405
1571
  function sameWireAmount(psp, reported, expected, options = {}) {
1406
1572
  const { convention } = options;
@@ -1411,7 +1577,7 @@ function sameWireAmount(psp, reported, expected, options = {}) {
1411
1577
  const targets = expectedTargets(psp, expected, convention, options.acceptLegacyIsoDigits === true);
1412
1578
  if (targets === null) return false;
1413
1579
  const { target, legacy } = targets;
1414
- const decode = (value) => tryDecodeWire(psp, value, expected.currency, { convention });
1580
+ const decode = (value) => options.textOnly && typeof value !== "string" ? void 0 : tryDecodeWireExactly(psp, value, expected.currency, convention);
1415
1581
  if (options.policy === "requested-exact-collected-covers" && options.requested !== void 0 && options.requested !== null) {
1416
1582
  const requested = decode(options.requested);
1417
1583
  const collected = decode(reported);
@@ -1572,6 +1738,53 @@ function eventMoney(m) {
1572
1738
  };
1573
1739
  }
1574
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
1575
1788
  //#region src/wire/provider-amount.ts
1576
1789
  /**
1577
1790
  * A provider's major-unit amount (finite number, or text read by the separator engine, which
@@ -1810,6 +2023,18 @@ Object.defineProperty(exports, "pspMinimumAmount", {
1810
2023
  return pspMinimumAmount;
1811
2024
  }
1812
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
+ });
1813
2038
  Object.defineProperty(exports, "pspSupportsCurrency", {
1814
2039
  enumerable: true,
1815
2040
  get: function() {
@@ -1,4 +1,4 @@
1
- import { R as RoundingMode, a as StoredAmount, h as CurrencyCode, r as Money, y as FractionDigits } from "./money-CbhsT0fB.js";
1
+ import { b as FractionDigits, g as CurrencyCode, i as Money, o as StoredAmount, t as FractionalMoney, z as RoundingMode } from "./money-B-3aFjNJ.js";
2
2
  //#region src/wire/anet.d.ts
3
3
  /**
4
4
  * The first `"<field>": <decimal>` (quoted or not) in a raw JSON body, verbatim: `JSON.parse`
@@ -102,26 +102,29 @@ interface EncodeWireOptions {
102
102
  allowZero?: boolean;
103
103
  }
104
104
  /**
105
- * A stored amount as `psp` receives it. In order: the currency must pass the PSP's Medusa amount
106
- * facade (`wire_unsupported` with the facade's message: Razorpay only its exponent table, Paystack
107
- * no zero-decimal code but XOF, MoMo, Payoo and VNPay only VND; every other PSP scales any
108
- * currency, so call `assertPspCurrency` for eligibility); the amount must be positive
109
- * (`wire_refused` `[<Tag>] Invalid <label>: <amount>`, zero allowed with `allowZero`); Stripe,
110
- * Adyen and Razorpay on a pre-cutover payment send the stored integer as is (Razorpay refuses
111
- * unless its factor is 1; MoMo and Paystack encode ignore the convention, as today); the
112
- * whole-unit rules refuse fractions of a dong (MoMo, Payoo, VNPay) or a franc (Paystack XOF);
113
- * then ISO minor `half-up(S / wireFactor)` (Razorpay three-decimal floored to steps of 10), or
114
- * major units exactly at the PSP's wire digits, half-up, as fixed-point text (a number for
115
- * Airwallex and Payoo). A positive amount whose wire rounds to zero is refused (`wire_refused`).
105
+ * A stored amount as `psp` receives it. `m` is a Money or a FractionalMoney (Medusa's unrounded
106
+ * session totals, 107.8175). In order: the currency must pass the PSP's Medusa amount facade
107
+ * (`wire_unsupported` with the facade's message: Razorpay only its exponent table, Paystack no
108
+ * zero-decimal code but XOF, MoMo, Payoo and VNPay only VND; every other PSP scales any currency,
109
+ * so call `assertPspCurrency` for eligibility); the amount must be positive (`wire_refused`
110
+ * `[<Tag>] Invalid <label>: <amount>`, zero allowed with `allowZero`); Stripe and Adyen round a
111
+ * fractional amount half-up to a whole storage unit first; Stripe, Adyen and Razorpay on a
112
+ * pre-cutover payment send the stored integer as is (Razorpay refuses unless its factor is 1, and
113
+ * refuses a fractional amount, which has no stored integer; MoMo and Paystack encode ignore the
114
+ * convention, as today); the whole-unit rules refuse fractions of a dong (MoMo, Payoo, VNPay) or a
115
+ * franc (Paystack XOF), naming the amount as held; then, rounding once and exactly, ISO minor
116
+ * `half-up(S / wireFactor)` (Razorpay three-decimal floored to steps of 10), or major units at the
117
+ * PSP's wire digits, half-up, as fixed-point text (a number for Airwallex and Payoo). A positive
118
+ * amount whose wire rounds to zero is refused (`wire_refused`), with `allowZero` too.
116
119
  * This is package policy, safer than Medusa today: Medusa puts 0 on the wire at initiate for
117
120
  * Stripe and Adyen, and for Razorpay, PayPal and Airwallex everywhere; it refuses at refund and
118
121
  * capture for Stripe and Adyen, and in Mollie, iyzico and Authorize.Net; Razorpay and Paystack
119
122
  * read a zero refund as a full one. Otherwise byte-identical to Medusa's codecs for every Money.
120
123
  */
121
- declare function encodeWire<P extends EncodeGatedPsp>(psp: P, m: Money, options: EncodeWireOptions & {
124
+ declare function encodeWire<P extends EncodeGatedPsp>(psp: P, m: Money | FractionalMoney, options: EncodeWireOptions & {
122
125
  convention: Convention;
123
126
  }): WireAmount<P>;
124
- declare function encodeWire<P extends Exclude<PspId, EncodeGatedPsp>>(psp: P, m: Money, options?: EncodeWireOptions): WireAmount<P>;
127
+ declare function encodeWire<P extends Exclude<PspId, EncodeGatedPsp>>(psp: P, m: Money | FractionalMoney, options?: EncodeWireOptions): WireAmount<P>;
125
128
  /**
126
129
  * A PSP wire amount as Money: ISO minor `half-up(w x wireFactor)` (pre-cutover: `half-up(w)`, the
127
130
  * PSP already held storage units), major `half-up(w x storageScale)`. `wire` is a finite number
@@ -155,9 +158,15 @@ interface SameWireAmountOptions {
155
158
  convention?: Convention;
156
159
  /**
157
160
  * Also accept the ISO-digit spelling held by a session created before the PSP digit tables
158
- * (PayPal, Mollie, Airwallex HUF/TWD...).
161
+ * (PayPal, Mollie, Airwallex HUF/TWD...): the expected amount in major units at its display
162
+ * digits, half-up (HUF 44.50 went as `'45'`). Major-unit PSPs only.
159
163
  */
160
164
  acceptLegacyIsoDigits?: boolean;
165
+ /**
166
+ * The PSP only ever reports decimal text (Mollie, PayPal): a reported (or `requested`) value
167
+ * that is not a string is not its amount, whatever number it holds.
168
+ */
169
+ textOnly?: boolean;
161
170
  /** When given, it must be the same currency as `expected` (any case). */
162
171
  reportedCurrency?: unknown;
163
172
  /** Paystack fee pass-through: the amount the buyer was asked for. */
@@ -169,12 +178,14 @@ interface SameWireAmountOptions {
169
178
  policy?: 'exact' | 'requested-exact-collected-covers';
170
179
  }
171
180
  /**
172
- * Whether a PSP-reported amount is `expected`. The expected wire is `encodeWire(psp, expected,
173
- * { convention, allowZero: true })` (a refusal means false); the reported one must decode
174
- * (`tryDecodeWire`: strict, never `''` or an exponent, no negatives) to the same stored amount,
175
- * so `'44.00'` matches a sent `'44'`. Never throws.
181
+ * Whether a PSP-reported amount is `expected`, a Money or a FractionalMoney (Medusa's unrounded
182
+ * session total). The expected wire is `encodeWire(psp, expected, { convention, allowZero: true })`
183
+ * (a refusal means false); the reported one must decode exactly (strict like `tryDecodeWire`:
184
+ * never `''` or an exponent, no negatives, and never a fraction of a storage unit) to the same
185
+ * stored amount, so `'44.00'` matches a sent `'44'` and Airwallex `43.004` does not match `43`.
186
+ * Never throws.
176
187
  */
177
- declare function sameWireAmount(psp: PspId, reported: unknown, expected: Money, options?: SameWireAmountOptions): boolean;
188
+ declare function sameWireAmount(psp: PspId, reported: unknown, expected: Money | FractionalMoney, options?: SameWireAmountOptions): boolean;
178
189
  /**
179
190
  * One unit of the PSP's last wire digit, as Money: for ISO-minor PSPs `decodeWire(psp, 1)` (the
180
191
  * factor on an iso-minor-v2 payment, 1 storage unit on a pre-cutover one; gated PSPs need the
@@ -213,6 +224,35 @@ interface EventMoney {
213
224
  /** First-party analytics money: the stored integer and the lower-case code (the ClickHouse `Int64` contract). */
214
225
  declare function eventMoney(m: Money): EventMoney;
215
226
  //#endregion
227
+ //#region src/wire/presentment.d.ts
228
+ interface PspPresentment {
229
+ /** Every currency some account of the PSP can present: the union over account countries. */
230
+ readonly currencies: ReadonlySet<CurrencyCode>;
231
+ /** Currencies only accounts registered in these countries can present (Stripe KWD: AE; PayPal BRL: BR). */
232
+ readonly currencyRequiresAccountCountry: ReadonlyMap<CurrencyCode, ReadonlySet<string>>;
233
+ /** Account countries limited to these currencies (Airwallex cards on a US or MX account). */
234
+ readonly accountCountryAllowsOnly: ReadonlyMap<string, ReadonlySet<CurrencyCode>>;
235
+ }
236
+ /**
237
+ * The PSP's presentment rules, as data: the union of currencies some account can charge, the
238
+ * currencies that need an account in particular countries, and the account countries limited to
239
+ * a list. For capability declarations that hold the rules themselves; `pspPresentmentCurrencies`
240
+ * applies them to one account. Fresh collections on every call. An unknown PSP throws
241
+ * `wire_unsupported`.
242
+ */
243
+ declare function pspPresentment(psp: PspId): PspPresentment;
244
+ /**
245
+ * The currencies `psp` lets an account present (charge buyers in). Without `accountCountry`, the
246
+ * union over every account country; with it (ISO 3166 alpha-2, any case), only what an account
247
+ * registered there can charge: Stripe's three-decimal five need a UAE account, PayPal's BRL, CNY
248
+ * and MYR an in-country one, Airwallex cards on a US account take its documented 76 and on an MX
249
+ * account MXN only. Not `pspCurrencies`, which is what the codec scales and the facade lets
250
+ * through (Stripe's adds every two-decimal ISO code it accepts on the wire).
251
+ */
252
+ declare function pspPresentmentCurrencies(psp: PspId, options?: {
253
+ accountCountry?: string;
254
+ }): ReadonlySet<CurrencyCode>;
255
+ //#endregion
216
256
  //#region src/wire/provider-amount.d.ts
217
257
  interface ProviderAmountOptions {
218
258
  /** Accept a negative amount (refunds, credits). Default false. */
@@ -266,4 +306,4 @@ declare function slotMoney(m: Money): SlotMoney;
266
306
  */
267
307
  declare function readSlotMoney(context: unknown): SlotMoney | undefined;
268
308
  //#endregion
269
- export { tryDecodeWire as A, EncodeGatedPsp as B, decodeWire as C, pspMinimumAmount as D, pspDefaultCurrency as E, PRE_CUTOVER as F, PSP_IDS as G, IsoMinorPsp as H, WIRE_SCALE_ISO_MINOR_V2 as I, AuthorizeNetAmounts as J, PspId as K, WIRE_SCALE_KEY as L, wireBreakdown as M, Convention as N, pspSupportsCurrency as O, ISO_MINOR_V2 as P, WIRE_SCALE_MARKER as R, assertPspCurrency as S, pspCurrencies as T, MajorNumberPsp as U, GatedPsp as V, MajorStringPsp as W, jsonDecimalLiteral as X, authorizeNetReversibleAmount as Y, oneWireUnit as _, iyzicoPrice as a, EncodeWireOptions as b, parseProviderAmount as c, destinationAmount as d, eventMoney as f, SameWireAmountOptions as g, DestinationId as h, RAZORPAY_AFA_FREE_CAP as i, wireFactor as j, pspWireDigits as k, DestinationValue as l, googleMicros as m, readSlotMoney as n, verificationCharge as o, feedPrice as p, WireAmount as q, slotMoney as r, ProviderAmountOptions as s, SlotMoney as t, EventMoney as u, sameWireAmount as v, encodeWire as w, TryDecodeWireOptions as x, wireKeyPart as y, conventionOf as z };
309
+ export { jsonDecimalLiteral as $, pspMinimumAmount as A, WIRE_SCALE_KEY as B, EncodeWireOptions as C, encodeWire as D, decodeWire as E, wireBreakdown as F, IsoMinorPsp as G, conventionOf as H, Convention as I, PSP_IDS as J, MajorNumberPsp as K, ISO_MINOR_V2 as L, pspWireDigits as M, tryDecodeWire as N, pspCurrencies as O, wireFactor as P, authorizeNetReversibleAmount as Q, PRE_CUTOVER as R, wireKeyPart as S, assertPspCurrency as T, EncodeGatedPsp as U, WIRE_SCALE_MARKER as V, GatedPsp as W, WireAmount as X, PspId as Y, AuthorizeNetAmounts as Z, googleMicros as _, iyzicoPrice as a, oneWireUnit as b, parseProviderAmount as c, pspPresentmentCurrencies as d, DestinationValue as f, feedPrice as g, eventMoney as h, RAZORPAY_AFA_FREE_CAP as i, pspSupportsCurrency as j, pspDefaultCurrency as k, PspPresentment as l, destinationAmount as m, readSlotMoney as n, verificationCharge as o, EventMoney as p, MajorStringPsp as q, slotMoney as r, ProviderAmountOptions as s, SlotMoney as t, pspPresentment as u, DestinationId as v, TryDecodeWireOptions as w, sameWireAmount as x, SameWireAmountOptions as y, WIRE_SCALE_ISO_MINOR_V2 as z };