@ducatjs/money 0.0.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 (398) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +51 -0
  3. package/README.md +1034 -3
  4. package/assets/CREDITS.md +12 -0
  5. package/data/adyen-fx.json +19 -0
  6. package/data/adyen.json +171 -0
  7. package/data/braintree.json +223 -0
  8. package/data/cash-rounding.json +520 -0
  9. package/data/checkout.json +230 -0
  10. package/data/cldr-cash.json +101 -0
  11. package/data/historic.json +78 -0
  12. package/data/iso4217-list-one.xml +1956 -0
  13. package/data/iso4217-list-three.xml +1185 -0
  14. package/data/klarna.json +95 -0
  15. package/data/paypal.json +65 -0
  16. package/data/stripe-fx.json +17 -0
  17. package/data/stripe.json +285 -0
  18. package/dist/adyen-fx.d.ts +25 -0
  19. package/dist/adyen-fx.d.ts.map +1 -0
  20. package/dist/adyen-fx.js +140 -0
  21. package/dist/adyen-fx.js.map +1 -0
  22. package/dist/adyen.d.ts +25 -0
  23. package/dist/adyen.d.ts.map +1 -0
  24. package/dist/adyen.js +34 -0
  25. package/dist/adyen.js.map +1 -0
  26. package/dist/allocate.d.ts +72 -0
  27. package/dist/allocate.d.ts.map +1 -0
  28. package/dist/allocate.js +314 -0
  29. package/dist/allocate.js.map +1 -0
  30. package/dist/bag.d.ts +58 -0
  31. package/dist/bag.d.ts.map +1 -0
  32. package/dist/bag.js +122 -0
  33. package/dist/bag.js.map +1 -0
  34. package/dist/boc.d.ts +31 -0
  35. package/dist/boc.d.ts.map +1 -0
  36. package/dist/boc.js +114 -0
  37. package/dist/boc.js.map +1 -0
  38. package/dist/boe.d.ts +33 -0
  39. package/dist/boe.d.ts.map +1 -0
  40. package/dist/boe.js +114 -0
  41. package/dist/boe.js.map +1 -0
  42. package/dist/braintree.d.ts +34 -0
  43. package/dist/braintree.d.ts.map +1 -0
  44. package/dist/braintree.js +45 -0
  45. package/dist/braintree.js.map +1 -0
  46. package/dist/cash.d.ts +64 -0
  47. package/dist/cash.d.ts.map +1 -0
  48. package/dist/cash.js +134 -0
  49. package/dist/cash.js.map +1 -0
  50. package/dist/checkout.d.ts +42 -0
  51. package/dist/checkout.d.ts.map +1 -0
  52. package/dist/checkout.js +52 -0
  53. package/dist/checkout.js.map +1 -0
  54. package/dist/convert.d.ts +52 -0
  55. package/dist/convert.d.ts.map +1 -0
  56. package/dist/convert.js +65 -0
  57. package/dist/convert.js.map +1 -0
  58. package/dist/currency.d.ts +22 -0
  59. package/dist/currency.d.ts.map +1 -0
  60. package/dist/currency.js +38 -0
  61. package/dist/currency.js.map +1 -0
  62. package/dist/decimal.d.ts +52 -0
  63. package/dist/decimal.d.ts.map +1 -0
  64. package/dist/decimal.js +251 -0
  65. package/dist/decimal.js.map +1 -0
  66. package/dist/drizzle/mysql.d.ts +105 -0
  67. package/dist/drizzle/mysql.d.ts.map +1 -0
  68. package/dist/drizzle/mysql.js +60 -0
  69. package/dist/drizzle/mysql.js.map +1 -0
  70. package/dist/drizzle/pg.d.ts +103 -0
  71. package/dist/drizzle/pg.d.ts.map +1 -0
  72. package/dist/drizzle/pg.js +60 -0
  73. package/dist/drizzle/pg.js.map +1 -0
  74. package/dist/drizzle/sqlite.d.ts +102 -0
  75. package/dist/drizzle/sqlite.d.ts.map +1 -0
  76. package/dist/drizzle/sqlite.js +59 -0
  77. package/dist/drizzle/sqlite.js.map +1 -0
  78. package/dist/ecb.d.ts +52 -0
  79. package/dist/ecb.d.ts.map +1 -0
  80. package/dist/ecb.js +285 -0
  81. package/dist/ecb.js.map +1 -0
  82. package/dist/exact.d.ts +62 -0
  83. package/dist/exact.d.ts.map +1 -0
  84. package/dist/exact.js +147 -0
  85. package/dist/exact.js.map +1 -0
  86. package/dist/fed.d.ts +35 -0
  87. package/dist/fed.d.ts.map +1 -0
  88. package/dist/fed.js +134 -0
  89. package/dist/fed.js.map +1 -0
  90. package/dist/fetch.d.ts +103 -0
  91. package/dist/fetch.d.ts.map +1 -0
  92. package/dist/fetch.js +222 -0
  93. package/dist/fetch.js.map +1 -0
  94. package/dist/fixed.d.ts +121 -0
  95. package/dist/fixed.d.ts.map +1 -0
  96. package/dist/fixed.js +420 -0
  97. package/dist/fixed.js.map +1 -0
  98. package/dist/fn.d.ts +34 -0
  99. package/dist/fn.d.ts.map +1 -0
  100. package/dist/fn.js +37 -0
  101. package/dist/fn.js.map +1 -0
  102. package/dist/format.d.ts +13 -0
  103. package/dist/format.d.ts.map +1 -0
  104. package/dist/format.js +90 -0
  105. package/dist/format.js.map +1 -0
  106. package/dist/generated/adyen.d.ts +3 -0
  107. package/dist/generated/adyen.d.ts.map +1 -0
  108. package/dist/generated/adyen.js +144 -0
  109. package/dist/generated/adyen.js.map +1 -0
  110. package/dist/generated/braintree.d.ts +3 -0
  111. package/dist/generated/braintree.d.ts.map +1 -0
  112. package/dist/generated/braintree.js +140 -0
  113. package/dist/generated/braintree.js.map +1 -0
  114. package/dist/generated/cash.d.ts +14 -0
  115. package/dist/generated/cash.d.ts.map +1 -0
  116. package/dist/generated/cash.js +465 -0
  117. package/dist/generated/cash.js.map +1 -0
  118. package/dist/generated/checkout.d.ts +3 -0
  119. package/dist/generated/checkout.d.ts.map +1 -0
  120. package/dist/generated/checkout.js +151 -0
  121. package/dist/generated/checkout.js.map +1 -0
  122. package/dist/generated/historic.d.ts +337 -0
  123. package/dist/generated/historic.d.ts.map +1 -0
  124. package/dist/generated/historic.js +66 -0
  125. package/dist/generated/historic.js.map +1 -0
  126. package/dist/generated/iso4217.d.ts +174 -0
  127. package/dist/generated/iso4217.d.ts.map +1 -0
  128. package/dist/generated/iso4217.js +175 -0
  129. package/dist/generated/iso4217.js.map +1 -0
  130. package/dist/generated/kernel.d.ts +17 -0
  131. package/dist/generated/kernel.d.ts.map +1 -0
  132. package/dist/generated/kernel.js +208 -0
  133. package/dist/generated/kernel.js.map +1 -0
  134. package/dist/generated/klarna.d.ts +3 -0
  135. package/dist/generated/klarna.d.ts.map +1 -0
  136. package/dist/generated/klarna.js +21 -0
  137. package/dist/generated/klarna.js.map +1 -0
  138. package/dist/generated/paypal.d.ts +3 -0
  139. package/dist/generated/paypal.d.ts.map +1 -0
  140. package/dist/generated/paypal.js +31 -0
  141. package/dist/generated/paypal.js.map +1 -0
  142. package/dist/generated/stripe.d.ts +8 -0
  143. package/dist/generated/stripe.d.ts.map +1 -0
  144. package/dist/generated/stripe.js +150 -0
  145. package/dist/generated/stripe.js.map +1 -0
  146. package/dist/historic.d.ts +40 -0
  147. package/dist/historic.d.ts.map +1 -0
  148. package/dist/historic.js +43 -0
  149. package/dist/historic.js.map +1 -0
  150. package/dist/history.d.ts +96 -0
  151. package/dist/history.d.ts.map +1 -0
  152. package/dist/history.js +213 -0
  153. package/dist/history.js.map +1 -0
  154. package/dist/hmrc.d.ts +28 -0
  155. package/dist/hmrc.d.ts.map +1 -0
  156. package/dist/hmrc.js +108 -0
  157. package/dist/hmrc.js.map +1 -0
  158. package/dist/index.d.ts +11 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +11 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/klarna.d.ts +31 -0
  163. package/dist/klarna.d.ts.map +1 -0
  164. package/dist/klarna.js +38 -0
  165. package/dist/klarna.js.map +1 -0
  166. package/dist/kysely.d.ts +120 -0
  167. package/dist/kysely.d.ts.map +1 -0
  168. package/dist/kysely.js +309 -0
  169. package/dist/kysely.js.map +1 -0
  170. package/dist/money.d.ts +302 -0
  171. package/dist/money.d.ts.map +1 -0
  172. package/dist/money.js +703 -0
  173. package/dist/money.js.map +1 -0
  174. package/dist/parse.d.ts +55 -0
  175. package/dist/parse.d.ts.map +1 -0
  176. package/dist/parse.js +419 -0
  177. package/dist/parse.js.map +1 -0
  178. package/dist/paypal.d.ts +41 -0
  179. package/dist/paypal.d.ts.map +1 -0
  180. package/dist/paypal.js +58 -0
  181. package/dist/paypal.js.map +1 -0
  182. package/dist/pg.d.ts +100 -0
  183. package/dist/pg.d.ts.map +1 -0
  184. package/dist/pg.js +343 -0
  185. package/dist/pg.js.map +1 -0
  186. package/dist/plugins/adyen.d.ts +15 -0
  187. package/dist/plugins/adyen.d.ts.map +1 -0
  188. package/dist/plugins/adyen.js +10 -0
  189. package/dist/plugins/adyen.js.map +1 -0
  190. package/dist/plugins/all.d.ts +29 -0
  191. package/dist/plugins/all.d.ts.map +1 -0
  192. package/dist/plugins/all.js +30 -0
  193. package/dist/plugins/all.js.map +1 -0
  194. package/dist/plugins/allocate.d.ts +20 -0
  195. package/dist/plugins/allocate.d.ts.map +1 -0
  196. package/dist/plugins/allocate.js +26 -0
  197. package/dist/plugins/allocate.js.map +1 -0
  198. package/dist/plugins/braintree.d.ts +12 -0
  199. package/dist/plugins/braintree.d.ts.map +1 -0
  200. package/dist/plugins/braintree.js +12 -0
  201. package/dist/plugins/braintree.js.map +1 -0
  202. package/dist/plugins/cash.d.ts +19 -0
  203. package/dist/plugins/cash.d.ts.map +1 -0
  204. package/dist/plugins/cash.js +16 -0
  205. package/dist/plugins/cash.js.map +1 -0
  206. package/dist/plugins/checkout.d.ts +21 -0
  207. package/dist/plugins/checkout.d.ts.map +1 -0
  208. package/dist/plugins/checkout.js +16 -0
  209. package/dist/plugins/checkout.js.map +1 -0
  210. package/dist/plugins/convert.d.ts +19 -0
  211. package/dist/plugins/convert.d.ts.map +1 -0
  212. package/dist/plugins/convert.js +13 -0
  213. package/dist/plugins/convert.js.map +1 -0
  214. package/dist/plugins/exact.d.ts +15 -0
  215. package/dist/plugins/exact.d.ts.map +1 -0
  216. package/dist/plugins/exact.js +13 -0
  217. package/dist/plugins/exact.js.map +1 -0
  218. package/dist/plugins/fixed.d.ts +17 -0
  219. package/dist/plugins/fixed.d.ts.map +1 -0
  220. package/dist/plugins/fixed.js +13 -0
  221. package/dist/plugins/fixed.js.map +1 -0
  222. package/dist/plugins/format.d.ts +9 -0
  223. package/dist/plugins/format.d.ts.map +1 -0
  224. package/dist/plugins/format.js +16 -0
  225. package/dist/plugins/format.js.map +1 -0
  226. package/dist/plugins/klarna.d.ts +10 -0
  227. package/dist/plugins/klarna.d.ts.map +1 -0
  228. package/dist/plugins/klarna.js +10 -0
  229. package/dist/plugins/klarna.js.map +1 -0
  230. package/dist/plugins/paypal.d.ts +14 -0
  231. package/dist/plugins/paypal.d.ts.map +1 -0
  232. package/dist/plugins/paypal.js +16 -0
  233. package/dist/plugins/paypal.js.map +1 -0
  234. package/dist/plugins/prisma.d.ts +8 -0
  235. package/dist/plugins/prisma.d.ts.map +1 -0
  236. package/dist/plugins/prisma.js +13 -0
  237. package/dist/plugins/prisma.js.map +1 -0
  238. package/dist/plugins/stripe.d.ts +14 -0
  239. package/dist/plugins/stripe.d.ts.map +1 -0
  240. package/dist/plugins/stripe.js +16 -0
  241. package/dist/plugins/stripe.js.map +1 -0
  242. package/dist/plugins/tax.d.ts +20 -0
  243. package/dist/plugins/tax.d.ts.map +1 -0
  244. package/dist/plugins/tax.js +18 -0
  245. package/dist/plugins/tax.js.map +1 -0
  246. package/dist/policy.d.ts +34 -0
  247. package/dist/policy.d.ts.map +1 -0
  248. package/dist/policy.js +90 -0
  249. package/dist/policy.js.map +1 -0
  250. package/dist/prisma.d.ts +77 -0
  251. package/dist/prisma.d.ts.map +1 -0
  252. package/dist/prisma.js +76 -0
  253. package/dist/prisma.js.map +1 -0
  254. package/dist/processor.d.ts +28 -0
  255. package/dist/processor.d.ts.map +1 -0
  256. package/dist/processor.js +118 -0
  257. package/dist/processor.js.map +1 -0
  258. package/dist/provider.d.ts +103 -0
  259. package/dist/provider.d.ts.map +1 -0
  260. package/dist/provider.js +280 -0
  261. package/dist/provider.js.map +1 -0
  262. package/dist/rate-text.d.ts +38 -0
  263. package/dist/rate-text.d.ts.map +1 -0
  264. package/dist/rate-text.js +143 -0
  265. package/dist/rate-text.js.map +1 -0
  266. package/dist/rate.d.ts +175 -0
  267. package/dist/rate.d.ts.map +1 -0
  268. package/dist/rate.js +371 -0
  269. package/dist/rate.js.map +1 -0
  270. package/dist/rounding-error.d.ts +9 -0
  271. package/dist/rounding-error.d.ts.map +1 -0
  272. package/dist/rounding-error.js +9 -0
  273. package/dist/rounding-error.js.map +1 -0
  274. package/dist/rounding.d.ts +50 -0
  275. package/dist/rounding.d.ts.map +1 -0
  276. package/dist/rounding.js +87 -0
  277. package/dist/rounding.js.map +1 -0
  278. package/dist/schema.d.ts +43 -0
  279. package/dist/schema.d.ts.map +1 -0
  280. package/dist/schema.js +77 -0
  281. package/dist/schema.js.map +1 -0
  282. package/dist/sql.d.ts +54 -0
  283. package/dist/sql.d.ts.map +1 -0
  284. package/dist/sql.js +131 -0
  285. package/dist/sql.js.map +1 -0
  286. package/dist/stripe-fx.d.ts +22 -0
  287. package/dist/stripe-fx.d.ts.map +1 -0
  288. package/dist/stripe-fx.js +30 -0
  289. package/dist/stripe-fx.js.map +1 -0
  290. package/dist/stripe.d.ts +36 -0
  291. package/dist/stripe.d.ts.map +1 -0
  292. package/dist/stripe.js +39 -0
  293. package/dist/stripe.js.map +1 -0
  294. package/dist/tax.d.ts +33 -0
  295. package/dist/tax.d.ts.map +1 -0
  296. package/dist/tax.js +55 -0
  297. package/dist/tax.js.map +1 -0
  298. package/dist/testing/arbitraries.d.ts +43 -0
  299. package/dist/testing/arbitraries.d.ts.map +1 -0
  300. package/dist/testing/arbitraries.js +98 -0
  301. package/dist/testing/arbitraries.js.map +1 -0
  302. package/dist/testing/index.d.ts +67 -0
  303. package/dist/testing/index.d.ts.map +1 -0
  304. package/dist/testing/index.js +159 -0
  305. package/dist/testing/index.js.map +1 -0
  306. package/dist/testing/jest.d.ts +2 -0
  307. package/dist/testing/jest.d.ts.map +1 -0
  308. package/dist/testing/jest.js +15 -0
  309. package/dist/testing/jest.js.map +1 -0
  310. package/dist/testing/vitest.d.ts +7 -0
  311. package/dist/testing/vitest.d.ts.map +1 -0
  312. package/dist/testing/vitest.js +12 -0
  313. package/dist/testing/vitest.js.map +1 -0
  314. package/dist/valibot.d.ts +33 -0
  315. package/dist/valibot.d.ts.map +1 -0
  316. package/dist/valibot.js +61 -0
  317. package/dist/valibot.js.map +1 -0
  318. package/dist/zod.d.ts +36 -0
  319. package/dist/zod.d.ts.map +1 -0
  320. package/dist/zod.js +58 -0
  321. package/dist/zod.js.map +1 -0
  322. package/package.json +280 -5
  323. package/src/adyen-fx.ts +175 -0
  324. package/src/adyen.ts +46 -0
  325. package/src/allocate.ts +393 -0
  326. package/src/bag.ts +170 -0
  327. package/src/boc.ts +136 -0
  328. package/src/boe.ts +131 -0
  329. package/src/braintree.ts +71 -0
  330. package/src/cash.ts +183 -0
  331. package/src/checkout.ts +77 -0
  332. package/src/convert.ts +177 -0
  333. package/src/currency.ts +61 -0
  334. package/src/decimal.ts +283 -0
  335. package/src/drizzle/mysql.ts +78 -0
  336. package/src/drizzle/pg.ts +78 -0
  337. package/src/drizzle/sqlite.ts +70 -0
  338. package/src/ecb.ts +328 -0
  339. package/src/exact.ts +182 -0
  340. package/src/fed.ts +146 -0
  341. package/src/fetch.ts +306 -0
  342. package/src/fixed.ts +558 -0
  343. package/src/fn.ts +94 -0
  344. package/src/format.ts +106 -0
  345. package/src/generated/adyen.ts +144 -0
  346. package/src/generated/braintree.ts +140 -0
  347. package/src/generated/cash.ts +478 -0
  348. package/src/generated/checkout.ts +151 -0
  349. package/src/generated/historic.ts +67 -0
  350. package/src/generated/iso4217.ts +179 -0
  351. package/src/generated/kernel.ts +230 -0
  352. package/src/generated/klarna.ts +21 -0
  353. package/src/generated/paypal.ts +31 -0
  354. package/src/generated/stripe.ts +150 -0
  355. package/src/historic.ts +77 -0
  356. package/src/history.ts +333 -0
  357. package/src/hmrc.ts +125 -0
  358. package/src/index.ts +41 -0
  359. package/src/klarna.ts +54 -0
  360. package/src/kysely.ts +588 -0
  361. package/src/money.ts +979 -0
  362. package/src/parse.ts +511 -0
  363. package/src/paypal.ts +93 -0
  364. package/src/pg.ts +410 -0
  365. package/src/plugins/adyen.ts +25 -0
  366. package/src/plugins/all.ts +57 -0
  367. package/src/plugins/allocate.ts +53 -0
  368. package/src/plugins/braintree.ts +23 -0
  369. package/src/plugins/cash.ts +33 -0
  370. package/src/plugins/checkout.ts +32 -0
  371. package/src/plugins/convert.ts +49 -0
  372. package/src/plugins/exact.ts +23 -0
  373. package/src/plugins/fixed.ts +28 -0
  374. package/src/plugins/format.ts +28 -0
  375. package/src/plugins/klarna.ts +21 -0
  376. package/src/plugins/paypal.ts +29 -0
  377. package/src/plugins/prisma.ts +24 -0
  378. package/src/plugins/stripe.ts +29 -0
  379. package/src/plugins/tax.ts +40 -0
  380. package/src/policy.ts +124 -0
  381. package/src/prisma.ts +199 -0
  382. package/src/processor.ts +181 -0
  383. package/src/provider.ts +389 -0
  384. package/src/rate-text.ts +151 -0
  385. package/src/rate.ts +546 -0
  386. package/src/rounding-error.ts +8 -0
  387. package/src/rounding.ts +122 -0
  388. package/src/schema.ts +119 -0
  389. package/src/sql.ts +178 -0
  390. package/src/stripe-fx.ts +49 -0
  391. package/src/stripe.ts +69 -0
  392. package/src/tax.ts +102 -0
  393. package/src/testing/arbitraries.ts +134 -0
  394. package/src/testing/index.ts +247 -0
  395. package/src/testing/jest.ts +18 -0
  396. package/src/testing/vitest.ts +18 -0
  397. package/src/valibot.ts +87 -0
  398. package/src/zod.ts +78 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@ducatjs/money/exact`: exact rational amounts that round once. Importing this module adds
3
+ * `Money#exact`.
4
+ */
5
+ import { exact, type ExactMoney } from '../exact.js';
6
+ import { extendMoney, type Money } from '../money.js';
7
+
8
+ export * from '../exact.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /**
13
+ * The exact value, for chained arithmetic that rounds once: `price.exact().divide(3).round()`.
14
+ */
15
+ exact(): ExactMoney<C>;
16
+ }
17
+ }
18
+
19
+ extendMoney({
20
+ exact(this: Money) {
21
+ return exact(this);
22
+ },
23
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `@ducatjs/money/fixed`: exact fixed-point decimals without a currency, on the same engine as
3
+ * Money. Importing this module adds `Money#toFixedPoint()`.
4
+ */
5
+ import { moneyToFixed, type Fixed } from '../fixed.js';
6
+ import { extendMoney, type Money } from '../money.js';
7
+
8
+ export * from '../fixed.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /**
13
+ * The exact Fixed of this amount, at its scale: `money('12.34', 'EUR').toFixedPoint()` is
14
+ * 12.34. Not `toFixed`, which Number, decimal.js and big.js all use for `toFixed(places):
15
+ * string`.
16
+ */
17
+ toFixedPoint(): Fixed;
18
+ }
19
+ }
20
+
21
+ extendMoney(
22
+ {
23
+ toFixedPoint(this: Money) {
24
+ return moneyToFixed(this);
25
+ },
26
+ },
27
+ { owner: '@ducatjs/money/fixed' },
28
+ );
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `@ducatjs/money/format`: locale formatting through Intl. Importing this module adds the methods
3
+ * below.
4
+ */
5
+ import { format, formatToParts } from '../format.js';
6
+ import { extendMoney, type Money } from '../money.js';
7
+
8
+ export * from '../format.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /** Locale formatting via Intl: `price.format('de-DE')` gives "19,99 €" */
13
+ format(locale?: string, options?: Intl.NumberFormatOptions): string;
14
+ formatToParts(locale?: string, options?: Intl.NumberFormatOptions): Intl.NumberFormatPart[];
15
+ }
16
+ }
17
+
18
+ extendMoney(
19
+ {
20
+ format(this: Money, locale?: string, options?: Intl.NumberFormatOptions) {
21
+ return format(this, locale, options);
22
+ },
23
+ formatToParts(this: Money, locale?: string, options?: Intl.NumberFormatOptions) {
24
+ return formatToParts(this, locale, options);
25
+ },
26
+ },
27
+ { owner: '@ducatjs/money/format' },
28
+ );
@@ -0,0 +1,21 @@
1
+ /** `@ducatjs/money/klarna`: Klarna's amount view. Importing this module adds `toKlarna`. */
2
+ import { toKlarna, type KlarnaAmountOptions } from '../klarna.js';
3
+ import { extendMoney, type Money } from '../money.js';
4
+
5
+ export * from '../klarna.js';
6
+
7
+ declare module '../money.js' {
8
+ interface Money<C extends string> {
9
+ /** The integer for any Klarna amount field: `money('25', 'EUR').toKlarna()` is 2500. */
10
+ toKlarna(options?: KlarnaAmountOptions): number;
11
+ }
12
+ }
13
+
14
+ extendMoney(
15
+ {
16
+ toKlarna(this: Money, options?: KlarnaAmountOptions) {
17
+ return toKlarna(this, options);
18
+ },
19
+ },
20
+ { owner: '@ducatjs/money/klarna' },
21
+ );
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@ducatjs/money/paypal`: PayPal's amount view. Importing this module adds `toPayPal` and
3
+ * `toPayPalValue`.
4
+ */
5
+ import { extendMoney, type Money } from '../money.js';
6
+ import { toPayPal, toPayPalValue, type PayPalAmountOptions } from '../paypal.js';
7
+
8
+ export * from '../paypal.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /** The exact `value` string for PayPal: `money('1000', 'HUF').toPayPalValue()` is '1000'. */
13
+ toPayPalValue(options?: PayPalAmountOptions): string;
14
+ /** A PayPal `money` object: `{ currency_code, value }`. */
15
+ toPayPal(options?: PayPalAmountOptions): { currency_code: string; value: string };
16
+ }
17
+ }
18
+
19
+ extendMoney(
20
+ {
21
+ toPayPalValue(this: Money, options?: PayPalAmountOptions) {
22
+ return toPayPalValue(this, options);
23
+ },
24
+ toPayPal(this: Money, options?: PayPalAmountOptions) {
25
+ return toPayPal(this, options);
26
+ },
27
+ },
28
+ { owner: '@ducatjs/money/paypal' },
29
+ );
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `@ducatjs/money/prisma`: Prisma and SQL column helpers. Importing this module adds
3
+ * `toDecimalValue`.
4
+ */
5
+ import { extendMoney, type Money } from '../money.js';
6
+ import { toDecimalValue } from '../prisma.js';
7
+
8
+ export * from '../prisma.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /** A Decimal for a NUMERIC column: `m.toDecimalValue(Prisma.Decimal)`. */
13
+ toDecimalValue<D>(Decimal: new (value: string) => D): D;
14
+ }
15
+ }
16
+
17
+ extendMoney(
18
+ {
19
+ toDecimalValue(this: Money, Decimal: new (value: string) => unknown) {
20
+ return toDecimalValue(this, Decimal);
21
+ },
22
+ },
23
+ { owner: '@ducatjs/money/prisma' },
24
+ );
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `@ducatjs/money/stripe`: Stripe's minor-unit view. Importing this module adds `toStripe` and
3
+ * `toStripeAmount`.
4
+ */
5
+ import { extendMoney, type Money } from '../money.js';
6
+ import { toStripe, toStripeAmount, type StripeAmountOptions } from '../stripe.js';
7
+
8
+ export * from '../stripe.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /** The integer `amount` to send to Stripe: `money('5', 'ISK').toStripeAmount()` is 500. */
13
+ toStripeAmount(options?: StripeAmountOptions): number;
14
+ /** `{ amount, currency }` with Stripe's lowercase currency code, ready for a PaymentIntent. */
15
+ toStripe(options?: StripeAmountOptions): { amount: number; currency: string };
16
+ }
17
+ }
18
+
19
+ extendMoney(
20
+ {
21
+ toStripeAmount(this: Money, options?: StripeAmountOptions) {
22
+ return toStripeAmount(this, options);
23
+ },
24
+ toStripe(this: Money, options?: StripeAmountOptions) {
25
+ return toStripe(this, options);
26
+ },
27
+ },
28
+ { owner: '@ducatjs/money/stripe' },
29
+ );
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `@ducatjs/money/tax`: percentages, discounts and tax breakdowns. Importing this module adds the
3
+ * methods below.
4
+ */
5
+ import type { Factor } from '../decimal.js';
6
+ import { extendMoney, type Money, type RoundingInput } from '../money.js';
7
+ import { addTax, discount, extractTax, percentage, type TaxBreakdown } from '../tax.js';
8
+
9
+ export * from '../tax.js';
10
+
11
+ declare module '../money.js' {
12
+ interface Money<C extends string> {
13
+ /** `percent`% of this, e.g. `price.percentage(15, 'halfEven')` */
14
+ percentage(percent: Factor, rounding?: RoundingInput): Money<C>;
15
+ /** this minus `percent`% */
16
+ discount(percent: Factor, rounding?: RoundingInput): Money<C>;
17
+ /** Treats this as net: `{ net, tax, gross }`, net + tax = gross exactly. */
18
+ addTax(percent: Factor, rounding?: RoundingInput): TaxBreakdown<C>;
19
+ /** Treats this as gross: `{ net, tax, gross }`, net + tax = gross exactly. */
20
+ extractTax(percent: Factor, rounding?: RoundingInput): TaxBreakdown<C>;
21
+ }
22
+ }
23
+
24
+ extendMoney(
25
+ {
26
+ percentage(this: Money, percent: Factor, rounding?: RoundingInput) {
27
+ return percentage(this, percent, rounding);
28
+ },
29
+ discount(this: Money, percent: Factor, rounding?: RoundingInput) {
30
+ return discount(this, percent, rounding);
31
+ },
32
+ addTax(this: Money, percent: Factor, rounding?: RoundingInput) {
33
+ return addTax(this, percent, rounding);
34
+ },
35
+ extractTax(this: Money, percent: Factor, rounding?: RoundingInput) {
36
+ return extractTax(this, percent, rounding);
37
+ },
38
+ },
39
+ { owner: '@ducatjs/money/tax' },
40
+ );
package/src/policy.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `@ducatjs/money/policy`: rounding policies, reusable values for a scale, an increment and a
3
+ * rounding mode ("CHF cash", "4 decimals, half even"), accepted wherever a rounding mode is. The
4
+ * core only hands the work to the policy, so none of this code is in a bundle that never imports
5
+ * this entry (or `cashPolicy` from `@ducatjs/money/cash`).
6
+ */
7
+ import { currencyExponent, isCurrency } from './currency.js';
8
+ import { MAX_DECIMAL_SCALE, type Ratio } from './decimal.js';
9
+ import {
10
+ CurrencyMismatchError,
11
+ multiply,
12
+ multiplyByRatio,
13
+ ratio,
14
+ ROUND_PRODUCT,
15
+ type Money,
16
+ type RoundingPolicy,
17
+ } from './money.js';
18
+ import { DEFAULT_ROUNDING, Rounding, type RoundingMode } from './rounding.js';
19
+
20
+ export type { RoundingInput, RoundingPolicy } from './money.js';
21
+
22
+ /** Options for `roundingPolicy`. Every field is optional. */
23
+ export interface RoundingPolicyOptions {
24
+ /** Decimal places of the result (default: the currency's ISO exponent). */
25
+ readonly scale?: number;
26
+ /** A positive safe integer count of 10^-scale units to round to (default 1). */
27
+ readonly increment?: number;
28
+ /** The rounding mode (default 'halfExpand'). */
29
+ readonly rounding?: RoundingMode;
30
+ /** Restrict the policy to one currency. */
31
+ readonly currency?: string;
32
+ }
33
+
34
+ const ONE = ratio(1);
35
+ const MODES = new Set<unknown>(Object.values(Rounding));
36
+
37
+ /**
38
+ * Builds a reusable rounding policy (see `RoundingPolicy`). The options are checked here, once, so
39
+ * applying the policy needs no checks.
40
+ *
41
+ * const unitPrice = roundingPolicy({ scale: 4, rounding: 'halfEven' })
42
+ * const fiveCents = roundingPolicy({ increment: 5 }) // 0.05 in a 2-decimal currency
43
+ */
44
+ export function roundingPolicy(options: RoundingPolicyOptions = {}): RoundingPolicy {
45
+ const scale = options.scale;
46
+ const increment = options.increment ?? 1;
47
+ const rounding = options.rounding ?? DEFAULT_ROUNDING;
48
+ const currency = options.currency;
49
+
50
+ if (
51
+ scale !== undefined &&
52
+ !(Number.isInteger(scale) && scale >= 0 && scale <= MAX_DECIMAL_SCALE)
53
+ ) {
54
+ throw new RangeError(`Invalid scale ${scale}`);
55
+ }
56
+ if (!(Number.isSafeInteger(increment) && increment >= 1)) {
57
+ throw new RangeError(`Invalid increment ${increment}; it must be a positive integer`);
58
+ }
59
+ if (!MODES.has(rounding)) {
60
+ throw new RangeError(`Invalid rounding mode ${String(rounding)}`);
61
+ }
62
+ // A typo such as 'chf' would otherwise make a policy that rejects every amount.
63
+ if (currency !== undefined && !isCurrency(currency)) {
64
+ throw new RangeError(`Unknown currency ${currency}`);
65
+ }
66
+
67
+ const incrementAsBigint = BigInt(increment);
68
+
69
+ // money × factor rounded once to the policy: with an increment, count whole increments
70
+ // (money × factor / increment, rounded at the policy's scale) and multiply back, which is
71
+ // rounding to the nearest multiple of the increment. The count's ratio is left unreduced: the
72
+ // division rounds the same either way, and a gcd per call would cost more than it saves.
73
+ const roundProduct = <C extends string>(money: Money<C>, factor: Ratio): Money<C> => {
74
+ if (currency !== undefined && currency !== money.currency) {
75
+ throw new CurrencyMismatchError(currency, money.currency);
76
+ }
77
+ const resultScale = scale ?? currencyExponent(money.currency);
78
+ if (increment === 1) {
79
+ return multiplyByRatio(money, factor, rounding, resultScale);
80
+ }
81
+ const stepDenominator = factor.d * increment;
82
+ const perStep: Ratio = {
83
+ num: factor.num,
84
+ den: factor.den * incrementAsBigint,
85
+ n: factor.n,
86
+ // NaN (not a safe integer) sends multiplyByRatio down its bigint path
87
+ d: Number.isSafeInteger(stepDenominator) ? stepDenominator : NaN,
88
+ };
89
+ return multiply(multiplyByRatio(money, perStep, rounding, resultScale), increment);
90
+ };
91
+
92
+ const policy = <C extends string>(money: Money<C>): Money<C> => roundProduct(money, ONE);
93
+ return Object.freeze(
94
+ Object.assign(policy, { scale, increment, rounding, currency, [ROUND_PRODUCT]: roundProduct }),
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Rounding to the currency's ISO exponent, half up: the policy most amounts should end with.
100
+ * `assertScale(m, isoPolicy)` checks that an amount is already there.
101
+ */
102
+ export const isoPolicy: RoundingPolicy = /* @__PURE__ */ roundingPolicy();
103
+
104
+ /**
105
+ * Throws a RangeError unless `money` is exactly what `policy` produces: at the policy's scale and a
106
+ * multiple of its increment (and in its currency, if it has one). Use it where an amount must
107
+ * already be rounded, such as before booking it or sending it to a processor.
108
+ */
109
+ export function assertScale(money: Money, policy: RoundingPolicy): void {
110
+ if (policy.currency !== undefined && policy.currency !== money.currency) {
111
+ throw new CurrencyMismatchError(policy.currency, money.currency);
112
+ }
113
+ const scale = policy.scale ?? currencyExponent(money.currency);
114
+ if (money.scale !== scale) {
115
+ throw new RangeError(`${money} has scale ${money.scale}, expected ${scale}`);
116
+ }
117
+ const minor = money.minor;
118
+ const increment = policy.increment;
119
+ const isMultiple =
120
+ typeof minor === 'number' ? minor % increment === 0 : minor % BigInt(increment) === 0n;
121
+ if (!isMultiple) {
122
+ throw new RangeError(`${money} is not a multiple of ${increment} at scale ${scale}`);
123
+ }
124
+ }
package/src/prisma.ts ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Prisma / SQL interop with no dependency on @prisma/client (works with any Decimal class that has
3
+ * toFixed(): Prisma.Decimal, decimal.js, big.js).
4
+ *
5
+ * Storage options (see README "Databases"):
6
+ * 1. two columns: amount_minor BIGINT + currency CHAR(3)
7
+ * -> minorMoneyField / toMinorColumns
8
+ * 2. a DECIMAL/NUMERIC column (+ a currency column or constant)
9
+ * -> decimalMoneyField / toDecimalValue
10
+ */
11
+ import { MAX_DECIMAL_SCALE } from './decimal.js';
12
+ import {
13
+ fromMinor,
14
+ money,
15
+ toDecimal,
16
+ toMinor,
17
+ type CurrencyInput,
18
+ type Money,
19
+ type MoneyOptions,
20
+ } from './money.js';
21
+
22
+ /** Structural type of Prisma.Decimal / decimal.js / big.js values. */
23
+ export interface DecimalLike {
24
+ toFixed(): string;
25
+ }
26
+
27
+ /**
28
+ * Money from a Decimal (or decimal string) column. Exact; throws if it has more decimals than the
29
+ * currency.
30
+ */
31
+ export function fromDecimal<C extends CurrencyInput>(
32
+ value: DecimalLike | string,
33
+ currency: C,
34
+ options?: MoneyOptions,
35
+ ): Money<C> {
36
+ return money(typeof value === 'string' ? value : value.toFixed(), currency, options);
37
+ }
38
+
39
+ /** A Decimal for a NUMERIC column: toDecimalValue(m, Prisma.Decimal). */
40
+ export function toDecimalValue<D>(money: Money, Decimal: new (value: string) => D): D {
41
+ return new Decimal(toDecimal(money));
42
+ }
43
+
44
+ type Row<A extends string, V> = { [P in A]: V };
45
+
46
+ /** Options for `minorMoneyField` and `toMinorColumns`: a column that records each amount's scale. */
47
+ export interface ScaleColumnOption<S extends string> {
48
+ /**
49
+ * The integer column holding the number of decimals the minor units are counted in (2 for
50
+ * cents). With it, any scale round-trips, and stored amounts keep their meaning even if a
51
+ * currency's exponent changes.
52
+ */
53
+ readonly scale: S;
54
+ }
55
+
56
+ /** A scale read from a row: a safe, non-negative integer (a SMALLINT or INT column). */
57
+ function readScale(value: unknown, column: string): number {
58
+ const scale = typeof value === 'bigint' ? Number(value) : value;
59
+ // capped like parsed decimals: a corrupt row must not make toDecimal build a huge power of ten
60
+ const isValid =
61
+ typeof scale === 'number' &&
62
+ Number.isInteger(scale) &&
63
+ scale >= 0 &&
64
+ scale <= MAX_DECIMAL_SCALE;
65
+ if (!isValid) {
66
+ throw new RangeError(
67
+ `Expected an integer scale from 0 to ${MAX_DECIMAL_SCALE} in "${column}", got ${String(value)}`,
68
+ );
69
+ }
70
+ return scale;
71
+ }
72
+
73
+ /**
74
+ * A Prisma result-extension field that exposes a Money built from `amount_minor` + `currency`
75
+ * columns, and optionally a scale column:
76
+ *
77
+ * const db = new PrismaClient().$extends({
78
+ * result: { order: { total: minorMoneyField('totalMinor', 'currency') } },
79
+ * });
80
+ * (await db.order.findFirstOrThrow()).total // Money
81
+ *
82
+ * Without a scale column the minor units are read at the currency's current exponent.
83
+ */
84
+ export function minorMoneyField<const A extends string, const K extends string>(
85
+ amount: A,
86
+ currency: K,
87
+ ): {
88
+ needs: { [P in A | K]: true };
89
+ compute: (row: Row<A, bigint | number> & Row<K, string>) => Money;
90
+ };
91
+ export function minorMoneyField<
92
+ const A extends string,
93
+ const K extends string,
94
+ const S extends string,
95
+ >(
96
+ amount: A,
97
+ currency: K,
98
+ options: ScaleColumnOption<S>,
99
+ ): {
100
+ needs: { [P in A | K | S]: true };
101
+ compute: (row: Row<A, bigint | number> & Row<K, string> & Row<S, bigint | number>) => Money;
102
+ };
103
+ export function minorMoneyField(
104
+ amount: string,
105
+ currency: string,
106
+ options?: ScaleColumnOption<string>,
107
+ ): { needs: Record<string, true>; compute: (row: Record<string, unknown>) => Money } {
108
+ if (options === undefined) {
109
+ return {
110
+ needs: { [amount]: true, [currency]: true },
111
+ compute: (row) => fromMinor(row[amount] as bigint | number, row[currency] as string),
112
+ };
113
+ }
114
+
115
+ const scaleColumn = options.scale;
116
+ return {
117
+ needs: { [amount]: true, [currency]: true, [scaleColumn]: true },
118
+ compute: (row) =>
119
+ fromMinor(
120
+ row[amount] as bigint | number,
121
+ row[currency] as string,
122
+ readScale(row[scaleColumn], scaleColumn),
123
+ ),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * A Prisma result-extension field for a Decimal column, with the currency taken from another column
129
+ * (`{ currencyField: 'currency' }`) or fixed (`{ currency: 'EUR' }`).
130
+ */
131
+ export function decimalMoneyField<const A extends string, const K extends string>(
132
+ amount: A,
133
+ currency: { currencyField: K },
134
+ ): { needs: { [P in A | K]: true }; compute: (row: Row<A, DecimalLike> & Row<K, string>) => Money };
135
+ export function decimalMoneyField<const A extends string, const C extends CurrencyInput>(
136
+ amount: A,
137
+ currency: { currency: C },
138
+ ): { needs: { [P in A]: true }; compute: (row: Row<A, DecimalLike>) => Money<C> };
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ export function decimalMoneyField(
141
+ amount: string,
142
+ currencySource: { currencyField: string } | { currency: string },
143
+ ): { needs: Record<string, true>; compute: (row: any) => Money } {
144
+ if ('currencyField' in currencySource) {
145
+ return {
146
+ needs: { [amount]: true, [currencySource.currencyField]: true },
147
+ compute: (row: Record<string, unknown>) =>
148
+ fromDecimal(row[amount] as DecimalLike, row[currencySource.currencyField] as string),
149
+ };
150
+ }
151
+ return {
152
+ needs: { [amount]: true },
153
+ compute: (row: Record<string, unknown>) =>
154
+ fromDecimal(row[amount] as DecimalLike, currencySource.currency),
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Column values for writes: `data: { ...toMinorColumns(total, 'totalMinor', 'currency') }`.
160
+ * Minor units are a bigint (Prisma BigInt). Without a scale column, throws if the value has a
161
+ * non-standard scale, because the columns cannot record it. With `{ scale: 'amountScale' }`, any
162
+ * scale is stored, next to the minor units it counts.
163
+ */
164
+ export function toMinorColumns<const A extends string, const K extends string>(
165
+ money: Money,
166
+ amount: A,
167
+ currency: K,
168
+ ): Row<A, bigint> & Row<K, string>;
169
+ export function toMinorColumns<
170
+ const A extends string,
171
+ const K extends string,
172
+ const S extends string,
173
+ >(
174
+ money: Money,
175
+ amount: A,
176
+ currency: K,
177
+ options: ScaleColumnOption<S>,
178
+ ): Row<A, bigint> & Row<K, string> & Row<S, number>;
179
+ export function toMinorColumns(
180
+ money: Money,
181
+ amount: string,
182
+ currency: string,
183
+ options?: ScaleColumnOption<string>,
184
+ ): Record<string, unknown> {
185
+ if (options === undefined) {
186
+ return { [amount]: toMinorAtIsoScale(money), [currency]: money.currency };
187
+ }
188
+ return { [amount]: toMinor(money), [currency]: money.currency, [options.scale]: money.scale };
189
+ }
190
+
191
+ function toMinorAtIsoScale(money: Money): bigint {
192
+ const iso = fromMinor(0, money.currency).scale;
193
+ if (money.scale !== iso) {
194
+ throw new RangeError(
195
+ `${money} has scale ${money.scale}; round() it to ${iso} decimals before storing minor units`,
196
+ );
197
+ }
198
+ return toMinor(money);
199
+ }