@ripwords/myinvois-client 0.0.10 → 0.1.2

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 (364) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +128 -4
  3. package/bun.lock +138 -122
  4. package/dist/0X-CG1S1WKn.d.ts +210 -0
  5. package/dist/0X-ChfmeAYF.d.cts +211 -0
  6. package/dist/1X-CmlbG02M.d.ts +111 -0
  7. package/dist/1X-DFikQbP0.d.cts +20 -0
  8. package/dist/1X-DbFshzup.d.ts +19 -0
  9. package/dist/1X-jFrFCOs0.d.cts +112 -0
  10. package/dist/2X-5cE4ojEX.d.cts +140 -0
  11. package/dist/2X-DA6J6Wd7.d.ts +139 -0
  12. package/dist/2X-FWZ1rYVt.d.cts +40 -0
  13. package/dist/2X-eC_p0QJf.d.ts +39 -0
  14. package/dist/3X-D9duw0Ro.d.cts +21 -0
  15. package/dist/3X-DbfkwdkO.d.ts +20 -0
  16. package/dist/3X-GxuN79Ax.d.ts +61 -0
  17. package/dist/3X-r9xbWJ2l.d.cts +62 -0
  18. package/dist/4X-1ZkK-uH5.d.cts +31 -0
  19. package/dist/4X-CLO9OuKa.d.cts +265 -0
  20. package/dist/4X-CnHtgO6u.d.ts +30 -0
  21. package/dist/4X-DYz_nYSD.d.ts +264 -0
  22. package/dist/5X-BzPEdkbC.d.ts +21 -0
  23. package/dist/5X-BziH0a09.d.cts +77 -0
  24. package/dist/5X-Cfe39fFc.d.cts +22 -0
  25. package/dist/5X-kBF74qx1.d.ts +76 -0
  26. package/dist/6X-BceErbps.d.cts +105 -0
  27. package/dist/6X-C_xpeu6J.d.ts +15 -0
  28. package/dist/6X-DLWEe1bP.d.cts +16 -0
  29. package/dist/6X-sQZfT0ZK.d.ts +104 -0
  30. package/dist/7X-Bet2agJ6.d.ts +15 -0
  31. package/dist/7X-BoKQxMoO.d.ts +70 -0
  32. package/dist/7X-CucHYQsA.d.cts +16 -0
  33. package/dist/7X-CwPAqozm.d.cts +71 -0
  34. package/dist/8X-BIjJ9kaV.d.cts +110 -0
  35. package/dist/8X-BOEWATU3.d.ts +109 -0
  36. package/dist/8X-CJpqd5xp.d.cts +19 -0
  37. package/dist/8X-s5AzkHR-.d.ts +18 -0
  38. package/dist/9X-BBDP1gZ6.d.ts +14 -0
  39. package/dist/9X-CP5Xp6bE.d.cts +98 -0
  40. package/dist/9X-D3aOVCnz.d.cts +15 -0
  41. package/dist/9X-DAAZV4sr.d.ts +97 -0
  42. package/dist/AX-CmpMMhII.d.ts +110 -0
  43. package/dist/AX-DFzpxGOT.d.cts +111 -0
  44. package/dist/BX-CyiqNE6b.d.cts +115 -0
  45. package/dist/BX-j78z_ju_.d.ts +114 -0
  46. package/dist/CX-B6rEhQx7.d.ts +127 -0
  47. package/dist/CX-BTPvC3Dv.d.cts +128 -0
  48. package/dist/DX-BD4-hTCh.d.cts +115 -0
  49. package/dist/DX-CAmMnx5a.d.ts +114 -0
  50. package/dist/EX-CXhSC85J.d.ts +106 -0
  51. package/dist/EX-mTHCH0Yk.d.cts +107 -0
  52. package/dist/FX-DOOMoQl2.d.cts +127 -0
  53. package/dist/FX-DYVIJ3l0.d.ts +126 -0
  54. package/dist/GX-8URJ7WHz.d.cts +136 -0
  55. package/dist/GX-eBOVHqi6.d.ts +135 -0
  56. package/dist/HX-49z8CzsE.d.cts +126 -0
  57. package/dist/HX-Dz5T0xXI.d.ts +125 -0
  58. package/dist/IX-B6kblmBs.d.cts +23 -0
  59. package/dist/IX-PiKYro5a.d.ts +22 -0
  60. package/dist/JX-DMwHLGEE.d.cts +104 -0
  61. package/dist/JX-Dvnt0mSK.d.ts +103 -0
  62. package/dist/KX-BUb8e0yg.d.ts +150 -0
  63. package/dist/KX-DaJPkefl.d.cts +151 -0
  64. package/dist/LX-BnILxjdS.d.cts +123 -0
  65. package/dist/LX-l5ZBaGov.d.ts +122 -0
  66. package/dist/MX-BbPO8mTj.d.cts +153 -0
  67. package/dist/MX-C7C15AWi.d.ts +152 -0
  68. package/dist/NX-C7nFVC-D.d.ts +121 -0
  69. package/dist/NX-xy5uw7H3.d.cts +122 -0
  70. package/dist/OX-CIzARbON.d.cts +26 -0
  71. package/dist/OX-Dmkvcaij.d.ts +25 -0
  72. package/dist/PX-BKZ14N7N.d.ts +120 -0
  73. package/dist/PX-CL8waFgx.d.cts +121 -0
  74. package/dist/QX-6xdSNtxZ.d.ts +58 -0
  75. package/dist/QX-BowAYqZb.d.cts +59 -0
  76. package/dist/RX-DFoiOK5q.d.ts +22 -0
  77. package/dist/RX-fWoWcHTG.d.cts +23 -0
  78. package/dist/SX-CZ3e31OT.d.cts +40 -0
  79. package/dist/SX-D-Szi9wa.d.ts +39 -0
  80. package/dist/TX-CJhJ-HYX.d.cts +31 -0
  81. package/dist/TX-DTfkGsqn.d.ts +30 -0
  82. package/dist/UX-Bw4HHAj4.d.ts +17 -0
  83. package/dist/UX-P7-u2y70.d.cts +18 -0
  84. package/dist/VX-C8Eu5gQf.d.cts +17 -0
  85. package/dist/VX-DCv5x_hi.d.ts +16 -0
  86. package/dist/WX-BYtJ5_if.d.ts +25 -0
  87. package/dist/WX-DkiaqB2V.d.cts +26 -0
  88. package/dist/XX-D4p4iK-a.d.cts +828 -0
  89. package/dist/XX-Jo1yJO3A.d.ts +827 -0
  90. package/dist/YX-BvGmEsVG.d.ts +19 -0
  91. package/dist/YX-Du8zv9qz.d.cts +20 -0
  92. package/dist/ZX-B4jFD-21.d.cts +22 -0
  93. package/dist/ZX-B59AJcBY.d.ts +21 -0
  94. package/dist/api/platform/platformLogin.d.ts +65 -0
  95. package/dist/api/platform/platformLogin.js +3 -0
  96. package/dist/certificate-DFK-788s.cjs +62 -0
  97. package/dist/certificate-DFK-788s.cjs.map +1 -0
  98. package/dist/certificate-aooIRf9A.js +49 -0
  99. package/dist/chunk-CUT6urMc.cjs +30 -0
  100. package/dist/classification-codes-B15PbWxz.d.cts +118 -0
  101. package/dist/classification-codes-BKxV-rO9.d.ts +117 -0
  102. package/dist/country-code-CQuaiQm2.d.ts +542 -0
  103. package/dist/country-code-DPeNFMMi.d.cts +543 -0
  104. package/dist/currencies-BYJK-m6v.d.ts +207 -0
  105. package/dist/currencies-S5g1gzBU.d.cts +208 -0
  106. package/dist/document-DMIMRJV0.cjs +472 -0
  107. package/dist/document-DMIMRJV0.cjs.map +1 -0
  108. package/dist/document-NQos5fSr.js +405 -0
  109. package/dist/documents-BVG7KIKY.d.ts +847 -0
  110. package/dist/documents-C066EC9m.d.cts +848 -0
  111. package/dist/e-invoice-BuwtFnlI.d.cts +44 -0
  112. package/dist/e-invoice-CmbLQkHw.d.ts +43 -0
  113. package/dist/getBaseUrl-CO7Jp27d.cjs +14 -0
  114. package/dist/getBaseUrl-CO7Jp27d.cjs.map +1 -0
  115. package/dist/getBaseUrl-R3IdgCu3.js +7 -0
  116. package/dist/index-0-EvC6Nv.d.ts +15 -0
  117. package/dist/index-D2_HVwCz.d.cts +16 -0
  118. package/dist/index.cjs +256 -50
  119. package/dist/index.cjs.map +1 -1
  120. package/dist/index.d.ts +232 -5665
  121. package/dist/index.js +253 -46
  122. package/dist/index10.cjs +0 -0
  123. package/dist/index11.cjs +34 -0
  124. package/dist/index11.cjs.map +1 -0
  125. package/dist/index12.cjs +24 -0
  126. package/dist/index12.cjs.map +1 -0
  127. package/dist/index13.cjs +0 -0
  128. package/dist/index14.cjs +13 -0
  129. package/dist/index14.cjs.map +1 -0
  130. package/dist/index15.cjs +4 -0
  131. package/dist/index16.cjs +13 -0
  132. package/dist/index17.cjs +3 -0
  133. package/dist/index18.cjs +340 -0
  134. package/dist/index18.cjs.map +1 -0
  135. package/dist/index19.cjs +329 -0
  136. package/dist/index19.cjs.map +1 -0
  137. package/dist/index2.cjs +62 -0
  138. package/dist/index2.cjs.map +1 -0
  139. package/dist/index20.cjs +140 -0
  140. package/dist/index20.cjs.map +1 -0
  141. package/dist/index21.cjs +3 -0
  142. package/dist/index22.cjs +208 -0
  143. package/dist/index22.cjs.map +1 -0
  144. package/dist/index23.cjs +109 -0
  145. package/dist/index23.cjs.map +1 -0
  146. package/dist/index24.cjs +137 -0
  147. package/dist/index24.cjs.map +1 -0
  148. package/dist/index25.cjs +64 -0
  149. package/dist/index25.cjs.map +1 -0
  150. package/dist/index26.cjs +267 -0
  151. package/dist/index26.cjs.map +1 -0
  152. package/dist/index27.cjs +79 -0
  153. package/dist/index27.cjs.map +1 -0
  154. package/dist/index28.cjs +107 -0
  155. package/dist/index28.cjs.map +1 -0
  156. package/dist/index29.cjs +73 -0
  157. package/dist/index29.cjs.map +1 -0
  158. package/dist/index3.cjs +532 -0
  159. package/dist/index3.cjs.map +1 -0
  160. package/dist/index30.cjs +112 -0
  161. package/dist/index30.cjs.map +1 -0
  162. package/dist/index31.cjs +100 -0
  163. package/dist/index31.cjs.map +1 -0
  164. package/dist/index32.cjs +18 -0
  165. package/dist/index32.cjs.map +1 -0
  166. package/dist/index33.cjs +38 -0
  167. package/dist/index33.cjs.map +1 -0
  168. package/dist/index34.cjs +19 -0
  169. package/dist/index34.cjs.map +1 -0
  170. package/dist/index35.cjs +29 -0
  171. package/dist/index35.cjs.map +1 -0
  172. package/dist/index36.cjs +20 -0
  173. package/dist/index36.cjs.map +1 -0
  174. package/dist/index37.cjs +14 -0
  175. package/dist/index37.cjs.map +1 -0
  176. package/dist/index38.cjs +14 -0
  177. package/dist/index38.cjs.map +1 -0
  178. package/dist/index39.cjs +17 -0
  179. package/dist/index39.cjs.map +1 -0
  180. package/dist/index4.cjs +196 -0
  181. package/dist/index4.cjs.map +1 -0
  182. package/dist/index40.cjs +13 -0
  183. package/dist/index40.cjs.map +1 -0
  184. package/dist/index41.cjs +108 -0
  185. package/dist/index41.cjs.map +1 -0
  186. package/dist/index42.cjs +113 -0
  187. package/dist/index42.cjs.map +1 -0
  188. package/dist/index43.cjs +126 -0
  189. package/dist/index43.cjs.map +1 -0
  190. package/dist/index44.cjs +113 -0
  191. package/dist/index44.cjs.map +1 -0
  192. package/dist/index45.cjs +105 -0
  193. package/dist/index45.cjs.map +1 -0
  194. package/dist/index46.cjs +125 -0
  195. package/dist/index46.cjs.map +1 -0
  196. package/dist/index47.cjs +134 -0
  197. package/dist/index47.cjs.map +1 -0
  198. package/dist/index48.cjs +124 -0
  199. package/dist/index48.cjs.map +1 -0
  200. package/dist/index49.cjs +21 -0
  201. package/dist/index49.cjs.map +1 -0
  202. package/dist/index5.cjs +0 -0
  203. package/dist/index50.cjs +102 -0
  204. package/dist/index50.cjs.map +1 -0
  205. package/dist/index51.cjs +149 -0
  206. package/dist/index51.cjs.map +1 -0
  207. package/dist/index52.cjs +121 -0
  208. package/dist/index52.cjs.map +1 -0
  209. package/dist/index53.cjs +151 -0
  210. package/dist/index53.cjs.map +1 -0
  211. package/dist/index54.cjs +120 -0
  212. package/dist/index54.cjs.map +1 -0
  213. package/dist/index55.cjs +24 -0
  214. package/dist/index55.cjs.map +1 -0
  215. package/dist/index56.cjs +119 -0
  216. package/dist/index56.cjs.map +1 -0
  217. package/dist/index57.cjs +54 -0
  218. package/dist/index57.cjs.map +1 -0
  219. package/dist/index58.cjs +21 -0
  220. package/dist/index58.cjs.map +1 -0
  221. package/dist/index58.cts.map +1 -0
  222. package/dist/index59.cjs +35 -0
  223. package/dist/index59.cjs.map +1 -0
  224. package/dist/index59.cts.map +1 -0
  225. package/dist/index6.cjs +25 -0
  226. package/dist/index6.cjs.map +1 -0
  227. package/dist/index60.cjs +29 -0
  228. package/dist/index60.cjs.map +1 -0
  229. package/dist/index60.cts.map +1 -0
  230. package/dist/index61.cjs +16 -0
  231. package/dist/index61.cjs.map +1 -0
  232. package/dist/index61.cts.map +1 -0
  233. package/dist/index62.cjs +15 -0
  234. package/dist/index62.cjs.map +1 -0
  235. package/dist/index62.cts.map +1 -0
  236. package/dist/index63.cjs +24 -0
  237. package/dist/index63.cjs.map +1 -0
  238. package/dist/index63.cts.map +1 -0
  239. package/dist/index64.cjs +419 -0
  240. package/dist/index64.cjs.map +1 -0
  241. package/dist/index64.cts.map +1 -0
  242. package/dist/index65.cjs +15 -0
  243. package/dist/index65.cjs.map +1 -0
  244. package/dist/index65.cts.map +1 -0
  245. package/dist/index66.cjs +16 -0
  246. package/dist/index66.cjs.map +1 -0
  247. package/dist/index66.cts.map +1 -0
  248. package/dist/index7.cjs +0 -0
  249. package/dist/index8.cjs +0 -0
  250. package/dist/index9.cjs +25 -0
  251. package/dist/index9.cjs.map +1 -0
  252. package/dist/msic-codes-CPWVNnpq.d.cts +26 -0
  253. package/dist/msic-codes-DToUqTI6.d.ts +25 -0
  254. package/dist/payment-modes-DA7uBIGb.d.ts +43 -0
  255. package/dist/payment-modes-FX88uyhP.d.cts +44 -0
  256. package/dist/platformLogin-PGzMhw1X.cjs +37 -0
  257. package/dist/platformLogin-PGzMhw1X.cjs.map +1 -0
  258. package/dist/platformLogin-f0bNAoZI.js +30 -0
  259. package/dist/signatures-BKi9DP2K.d.cts +173 -0
  260. package/dist/signatures-CG175mpg.d.ts +172 -0
  261. package/dist/state-codes-CNp_6051.d.cts +62 -0
  262. package/dist/state-codes-dvoTe5pC.d.ts +61 -0
  263. package/dist/tax-types-CgwxONDS.d.cts +42 -0
  264. package/dist/tax-types-tosH5I0q.d.ts +41 -0
  265. package/dist/types/classification-codes.d.ts +2 -0
  266. package/dist/types/country-code.d.ts +2 -0
  267. package/dist/types/currencies.d.ts +2 -0
  268. package/dist/types/documents.d.ts +7 -0
  269. package/dist/types/e-invoice.d.ts +2 -0
  270. package/dist/types/index.d.ts +58 -0
  271. package/dist/types/msic/0X.d.ts +2 -0
  272. package/dist/types/msic/1X.d.ts +2 -0
  273. package/dist/types/msic/2X.d.ts +2 -0
  274. package/dist/types/msic/3X.d.ts +2 -0
  275. package/dist/types/msic/4X.d.ts +2 -0
  276. package/dist/types/msic/5X.d.ts +2 -0
  277. package/dist/types/msic/6X.d.ts +2 -0
  278. package/dist/types/msic/7X.d.ts +2 -0
  279. package/dist/types/msic/8X.d.ts +2 -0
  280. package/dist/types/msic/9X.d.ts +2 -0
  281. package/dist/types/msic-codes.d.ts +12 -0
  282. package/dist/types/payment-modes.d.ts +2 -0
  283. package/dist/types/signatures.d.ts +2 -0
  284. package/dist/types/state-codes.d.ts +2 -0
  285. package/dist/types/tax-types.d.ts +2 -0
  286. package/dist/types/unit/1X.d.ts +2 -0
  287. package/dist/types/unit/2X.d.ts +2 -0
  288. package/dist/types/unit/3X.d.ts +2 -0
  289. package/dist/types/unit/4X.d.ts +2 -0
  290. package/dist/types/unit/5X.d.ts +2 -0
  291. package/dist/types/unit/6X.d.ts +2 -0
  292. package/dist/types/unit/7X.d.ts +2 -0
  293. package/dist/types/unit/8X.d.ts +2 -0
  294. package/dist/types/unit/9X.d.ts +2 -0
  295. package/dist/types/unit/AX.d.ts +2 -0
  296. package/dist/types/unit/BX.d.ts +2 -0
  297. package/dist/types/unit/CX.d.ts +2 -0
  298. package/dist/types/unit/DX.d.ts +2 -0
  299. package/dist/types/unit/EX.d.ts +2 -0
  300. package/dist/types/unit/FX.d.ts +2 -0
  301. package/dist/types/unit/GX.d.ts +2 -0
  302. package/dist/types/unit/HX.d.ts +2 -0
  303. package/dist/types/unit/IX.d.ts +2 -0
  304. package/dist/types/unit/JX.d.ts +2 -0
  305. package/dist/types/unit/KX.d.ts +2 -0
  306. package/dist/types/unit/LX.d.ts +2 -0
  307. package/dist/types/unit/MX.d.ts +2 -0
  308. package/dist/types/unit/NX.d.ts +2 -0
  309. package/dist/types/unit/OX.d.ts +2 -0
  310. package/dist/types/unit/PX.d.ts +2 -0
  311. package/dist/types/unit/QX.d.ts +2 -0
  312. package/dist/types/unit/RX.d.ts +2 -0
  313. package/dist/types/unit/SX.d.ts +2 -0
  314. package/dist/types/unit/TX.d.ts +2 -0
  315. package/dist/types/unit/UX.d.ts +2 -0
  316. package/dist/types/unit/VX.d.ts +2 -0
  317. package/dist/types/unit/WX.d.ts +2 -0
  318. package/dist/types/unit/XX.d.ts +2 -0
  319. package/dist/types/unit/YX.d.ts +2 -0
  320. package/dist/types/unit/ZX.d.ts +2 -0
  321. package/dist/types/unit-types.d.ts +37 -0
  322. package/dist/unit-types-BKZxwVYQ.d.ts +55 -0
  323. package/dist/unit-types-CaOA5ZDG.d.cts +56 -0
  324. package/dist/utils/base64.d.ts +5 -0
  325. package/dist/utils/base64.js +10 -0
  326. package/dist/utils/certificate.d.ts +22 -0
  327. package/dist/utils/certificate.js +3 -0
  328. package/dist/utils/document.d.ts +135 -0
  329. package/dist/utils/document.js +3 -0
  330. package/dist/utils/getBaseUrl.d.ts +4 -0
  331. package/dist/utils/getBaseUrl.js +3 -0
  332. package/dist/utils/helpers.d.ts +271 -0
  333. package/dist/utils/helpers.js +330 -0
  334. package/dist/utils/signature-diagnostics.d.ts +93 -0
  335. package/dist/utils/signature-diagnostics.js +326 -0
  336. package/dist/utils/validation.d.ts +46 -0
  337. package/dist/utils/validation.js +134 -0
  338. package/myinvois-cert.conf.template +23 -0
  339. package/package.json +20 -7
  340. package/scripts/gen-cert.sh +159 -0
  341. package/src/api/platform/platformLogin.ts +7 -2
  342. package/src/index.ts +528 -1
  343. package/src/types/documents.d.ts +862 -0
  344. package/src/types/index.d.ts +1 -1
  345. package/src/types/msic-codes.d.ts +1 -7
  346. package/src/utils/base64.ts +7 -0
  347. package/src/utils/certificate.ts +60 -0
  348. package/src/utils/document.ts +852 -0
  349. package/src/utils/getBaseUrl.ts +1 -1
  350. package/src/utils/helpers.ts +552 -0
  351. package/src/utils/signature-diagnostics.ts +583 -0
  352. package/src/utils/validation.ts +268 -0
  353. package/test/MyInvoiClientWithRealData.test.ts +11 -2
  354. package/test/MyInvoisClient.test.ts +23 -9
  355. package/test/base64.test.ts +43 -0
  356. package/test/dynamicInvoiceFeatures.test.ts +449 -0
  357. package/test/signAndSubmitInvoice.test.ts +450 -0
  358. package/test/signature-diagnostics.test.ts +128 -0
  359. package/tsconfig.json +4 -1
  360. package/tsdown.config.ts +31 -0
  361. package/rolldown.config.ts +0 -26
  362. package/src/types/documents/index.d.ts +0 -1
  363. package/src/types/documents/invoice-1_1.d.ts +0 -349
  364. package/src/utils/MyInvoisClient.ts +0 -88
@@ -0,0 +1,268 @@
1
+ import type { InvoiceV1_1 } from '../types/documents/index.js'
2
+
3
+ /**
4
+ * MyInvois Invoice Validation Utilities
5
+ *
6
+ * Provides comprehensive validation for invoice data before document generation
7
+ * and submission to ensure compliance with MyInvois business rules and format requirements.
8
+ */
9
+
10
+ export interface ValidationResult {
11
+ isValid: boolean
12
+ errors: ValidationError[]
13
+ warnings: ValidationWarning[]
14
+ }
15
+
16
+ export interface ValidationError {
17
+ field: string
18
+ code: string
19
+ message: string
20
+ severity: 'error' | 'warning'
21
+ }
22
+
23
+ export interface ValidationWarning extends ValidationError {
24
+ severity: 'warning'
25
+ }
26
+
27
+ /**
28
+ * Validates TIN format based on registration type
29
+ */
30
+ export const validateTIN = (
31
+ tin: string,
32
+ registrationType?: string,
33
+ ): ValidationError[] => {
34
+ const errors: ValidationError[] = []
35
+
36
+ if (!tin) {
37
+ errors.push({
38
+ field: 'tin',
39
+ code: 'TIN_REQUIRED',
40
+ message: 'TIN is required',
41
+ severity: 'error',
42
+ })
43
+ return errors
44
+ }
45
+
46
+ // TIN format validation based on type
47
+ if (registrationType === 'BRN' && !tin.startsWith('C')) {
48
+ errors.push({
49
+ field: 'tin',
50
+ code: 'TIN_FORMAT_INVALID',
51
+ message: 'Company TIN should start with "C" for BRN registration',
52
+ severity: 'warning',
53
+ })
54
+ }
55
+
56
+ if (registrationType === 'NRIC' && !tin.startsWith('IG')) {
57
+ errors.push({
58
+ field: 'tin',
59
+ code: 'TIN_FORMAT_INVALID',
60
+ message: 'Individual TIN should start with "IG" for NRIC registration',
61
+ severity: 'warning',
62
+ })
63
+ }
64
+
65
+ // Length validation
66
+ if (tin.length > 14) {
67
+ errors.push({
68
+ field: 'tin',
69
+ code: 'TIN_LENGTH_INVALID',
70
+ message: 'TIN cannot exceed 14 characters',
71
+ severity: 'error',
72
+ })
73
+ }
74
+
75
+ return errors
76
+ }
77
+
78
+ /**
79
+ * Validates contact number format (E.164 standard)
80
+ */
81
+ export const validateContactNumber = (
82
+ contactNumber: string,
83
+ ): ValidationError[] => {
84
+ const errors: ValidationError[] = []
85
+
86
+ if (!contactNumber || contactNumber === 'NA') {
87
+ return errors // Allow NA for consolidated e-invoices
88
+ }
89
+
90
+ // E.164 format validation
91
+ const e164Regex = /^\+[1-9]\d{1,14}$/
92
+ if (!e164Regex.test(contactNumber)) {
93
+ errors.push({
94
+ field: 'contactNumber',
95
+ code: 'CONTACT_FORMAT_INVALID',
96
+ message: 'Contact number must be in E.164 format (e.g., +60123456789)',
97
+ severity: 'error',
98
+ })
99
+ }
100
+
101
+ if (contactNumber.length < 8) {
102
+ errors.push({
103
+ field: 'contactNumber',
104
+ code: 'CONTACT_LENGTH_INVALID',
105
+ message: 'Contact number must be at least 8 characters',
106
+ severity: 'error',
107
+ })
108
+ }
109
+
110
+ return errors
111
+ }
112
+
113
+ /**
114
+ * Validates monetary amounts
115
+ */
116
+ export const validateMonetaryAmount = (
117
+ amount: number,
118
+ fieldName: string,
119
+ maxDigits = 18,
120
+ maxDecimals = 2,
121
+ ): ValidationError[] => {
122
+ const errors: ValidationError[] = []
123
+
124
+ if (amount < 0) {
125
+ errors.push({
126
+ field: fieldName,
127
+ code: 'AMOUNT_NEGATIVE',
128
+ message: `${fieldName} cannot be negative`,
129
+ severity: 'error',
130
+ })
131
+ }
132
+
133
+ // Check total digits
134
+ const amountStr = amount.toString()
135
+ const [integerPart, decimalPart] = amountStr.split('.')
136
+
137
+ if (integerPart && integerPart.length > maxDigits - maxDecimals) {
138
+ errors.push({
139
+ field: fieldName,
140
+ code: 'AMOUNT_DIGITS_EXCEEDED',
141
+ message: `${fieldName} exceeds maximum ${maxDigits} digits`,
142
+ severity: 'error',
143
+ })
144
+ }
145
+
146
+ if (decimalPart && decimalPart.length > maxDecimals) {
147
+ errors.push({
148
+ field: fieldName,
149
+ code: 'AMOUNT_DECIMALS_EXCEEDED',
150
+ message: `${fieldName} exceeds maximum ${maxDecimals} decimal places`,
151
+ severity: 'error',
152
+ })
153
+ }
154
+
155
+ return errors
156
+ }
157
+
158
+ /**
159
+ * Validates tax calculation consistency
160
+ */
161
+ export const validateTaxCalculations = (
162
+ invoice: InvoiceV1_1,
163
+ ): ValidationError[] => {
164
+ const errors: ValidationError[] = []
165
+
166
+ // Calculate expected totals from line items
167
+ const expectedTaxExclusive = invoice.invoiceLineItems.reduce(
168
+ (sum, item) => sum + item.totalTaxableAmountPerLine,
169
+ 0,
170
+ )
171
+ const expectedTaxAmount = invoice.invoiceLineItems.reduce(
172
+ (sum, item) => sum + item.taxAmount,
173
+ 0,
174
+ )
175
+
176
+ // Allow small rounding differences (0.01)
177
+ const tolerance = 0.01
178
+
179
+ if (
180
+ Math.abs(
181
+ invoice.legalMonetaryTotal.taxExclusiveAmount - expectedTaxExclusive,
182
+ ) > tolerance
183
+ ) {
184
+ errors.push({
185
+ field: 'legalMonetaryTotal.taxExclusiveAmount',
186
+ code: 'TAX_EXCLUSIVE_MISMATCH',
187
+ message: `Tax exclusive amount (${invoice.legalMonetaryTotal.taxExclusiveAmount}) doesn't match sum of line items (${expectedTaxExclusive})`,
188
+ severity: 'error',
189
+ })
190
+ }
191
+
192
+ if (Math.abs(invoice.taxTotal.taxAmount - expectedTaxAmount) > tolerance) {
193
+ errors.push({
194
+ field: 'taxTotal.taxAmount',
195
+ code: 'TAX_AMOUNT_MISMATCH',
196
+ message: `Tax amount (${invoice.taxTotal.taxAmount}) doesn't match sum of line item taxes (${expectedTaxAmount})`,
197
+ severity: 'error',
198
+ })
199
+ }
200
+
201
+ return errors
202
+ }
203
+
204
+ /**
205
+ * Main validation function for complete invoice
206
+ */
207
+ export const validateInvoice = (invoice: InvoiceV1_1): ValidationResult => {
208
+ const allErrors: ValidationError[] = []
209
+
210
+ // Core field validations
211
+ allErrors.push(
212
+ ...validateTIN(invoice.supplier.tin, invoice.supplier.registrationType),
213
+ )
214
+ allErrors.push(
215
+ ...validateTIN(invoice.buyer.tin, invoice.buyer.registrationType),
216
+ )
217
+
218
+ allErrors.push(...validateContactNumber(invoice.supplier.contactNumber))
219
+ allErrors.push(...validateContactNumber(invoice.buyer.contactNumber))
220
+
221
+ // Monetary validations
222
+ allErrors.push(
223
+ ...validateMonetaryAmount(
224
+ invoice.legalMonetaryTotal.taxExclusiveAmount,
225
+ 'taxExclusiveAmount',
226
+ ),
227
+ )
228
+ allErrors.push(
229
+ ...validateMonetaryAmount(
230
+ invoice.legalMonetaryTotal.payableAmount,
231
+ 'payableAmount',
232
+ ),
233
+ )
234
+ allErrors.push(
235
+ ...validateMonetaryAmount(invoice.taxTotal.taxAmount, 'taxAmount'),
236
+ )
237
+
238
+ // Line item validations
239
+ invoice.invoiceLineItems.forEach((item, index) => {
240
+ allErrors.push(
241
+ ...validateMonetaryAmount(item.unitPrice, `lineItem[${index}].unitPrice`),
242
+ )
243
+ allErrors.push(
244
+ ...validateMonetaryAmount(item.taxAmount, `lineItem[${index}].taxAmount`),
245
+ )
246
+ allErrors.push(
247
+ ...validateMonetaryAmount(
248
+ item.totalTaxableAmountPerLine,
249
+ `lineItem[${index}].totalTaxableAmountPerLine`,
250
+ ),
251
+ )
252
+ })
253
+
254
+ // Business rule validations
255
+ allErrors.push(...validateTaxCalculations(invoice))
256
+
257
+ // Separate errors and warnings
258
+ const errors = allErrors.filter(e => e.severity === 'error')
259
+ const warnings = allErrors.filter(
260
+ e => e.severity === 'warning',
261
+ ) as ValidationWarning[]
262
+
263
+ return {
264
+ isValid: errors.length === 0,
265
+ errors,
266
+ warnings,
267
+ }
268
+ }
@@ -1,5 +1,11 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { MyInvoisClient } from '../src/utils/MyInvoisClient'
2
+ import { MyInvoisClient } from '../src'
3
+
4
+ /**
5
+ * ⚠️ SECURITY NOTICE: This file uses environment variables for sensitive data.
6
+ * Never hardcode actual TIN, NRIC, certificates, or API credentials in test files.
7
+ * Use .env file for your actual values (already gitignored).
8
+ */
3
9
 
4
10
  describe('MyInvoisClientWithRealData', () => {
5
11
  it('should verify TIN with real data', async () => {
@@ -19,11 +25,14 @@ describe('MyInvoisClientWithRealData', () => {
19
25
  process.env.CLIENT_ID!,
20
26
  process.env.CLIENT_SECRET!,
21
27
  'sandbox',
28
+ process.env.CERTIFICATE!,
29
+ process.env.PRIVATE_KEY!,
22
30
  )
23
- // @ts-ignore
31
+ // @ts-ignore - refreshToken is a private method
24
32
  await client.refreshToken()
25
33
  const result = await client.verifyTin(
26
34
  process.env.TIN_VALUE!,
35
+ 'NRIC',
27
36
  process.env.NRIC_VALUE!,
28
37
  )
29
38
  expect(result).toBe(true)
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { MyInvoisClient } from '../src/utils/MyInvoisClient'
2
+ import { MyInvoisClient } from '../src'
3
3
 
4
4
  // Mock global fetch
5
5
  const mockFetch = vi.fn()
@@ -12,7 +12,15 @@ describe('MyInvoisClient', () => {
12
12
 
13
13
  beforeEach(() => {
14
14
  vi.clearAllMocks()
15
- client = new MyInvoisClient('test-id', 'test-secret', 'sandbox', false)
15
+ client = new MyInvoisClient(
16
+ 'test-id',
17
+ 'test-secret',
18
+ 'sandbox',
19
+ process.env.TEST_CERTIFICATE!,
20
+ process.env.TEST_PRIVATE_KEY!,
21
+ undefined,
22
+ true,
23
+ )
16
24
  })
17
25
 
18
26
  describe('constructor', () => {
@@ -21,6 +29,9 @@ describe('MyInvoisClient', () => {
21
29
  'test-id',
22
30
  'test-secret',
23
31
  'sandbox',
32
+ process.env.TEST_CERTIFICATE!,
33
+ process.env.TEST_PRIVATE_KEY!,
34
+ undefined,
24
35
  true,
25
36
  )
26
37
  expect((sandboxClient as any).baseUrl).toBe(
@@ -33,6 +44,9 @@ describe('MyInvoisClient', () => {
33
44
  'test-id',
34
45
  'test-secret',
35
46
  'production',
47
+ process.env.TEST_CERTIFICATE!,
48
+ process.env.TEST_PRIVATE_KEY!,
49
+ undefined,
36
50
  true,
37
51
  )
38
52
  expect((prodClient as any).baseUrl).toBe(
@@ -53,7 +67,7 @@ describe('MyInvoisClient', () => {
53
67
  json: () => Promise.resolve(mockToken),
54
68
  } as Response)
55
69
 
56
- await client.verifyTin('123', '456')
70
+ await client.verifyTin('123', 'NRIC', '456')
57
71
 
58
72
  expect(mockFetch).toHaveBeenCalledWith(
59
73
  'https://preprod-api.myinvois.hasil.gov.my/connect/token',
@@ -77,7 +91,7 @@ describe('MyInvoisClient', () => {
77
91
  json: () => Promise.resolve(undefined),
78
92
  } as Response)
79
93
 
80
- await client.verifyTin('123', '456')
94
+ await client.verifyTin('123', 'NRIC', '456')
81
95
 
82
96
  // Check first call (token request)
83
97
  expect(mockFetch).toHaveBeenNthCalledWith(
@@ -122,12 +136,12 @@ describe('MyInvoisClient', () => {
122
136
  } as Response)
123
137
 
124
138
  // First call to get token
125
- await client.verifyTin('123', '456')
139
+ await client.verifyTin('123', 'NRIC', '456')
126
140
 
127
141
  vi.setSystemTime(new Date(Date.now() + 1000))
128
142
 
129
143
  // Second call should reuse token
130
- await client.verifyTin('123', '456')
144
+ await client.verifyTin('123', 'NRIC', '456')
131
145
 
132
146
  // Token endpoint should only be called once
133
147
  expect(mockFetch).toHaveBeenCalledWith(
@@ -154,9 +168,9 @@ describe('MyInvoisClient', () => {
154
168
  json: () => Promise.resolve(undefined),
155
169
  } as Response)
156
170
 
157
- await client.verifyTin('123', '456')
171
+ await client.verifyTin('123', 'NRIC', '456')
158
172
  vi.setSystemTime(new Date(Date.now() + 1000 * 8000))
159
- await client.verifyTin('123', '456')
173
+ await client.verifyTin('123', 'NRIC', '456')
160
174
 
161
175
  expect(mockFetch).toHaveBeenCalledWith(
162
176
  `https://preprod-api.myinvois.hasil.gov.my/api/v1.0/taxpayer/validate/123?idType=NRIC&idValue=456`,
@@ -182,7 +196,7 @@ describe('MyInvoisClient', () => {
182
196
  } as Response)
183
197
  .mockRejectedValueOnce(new Error('Invalid TIN'))
184
198
 
185
- const result = await client.verifyTin('123', '456')
199
+ const result = await client.verifyTin('123', 'NRIC', '456')
186
200
 
187
201
  expect(result).toBe(false)
188
202
  })
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { encodeToBase64 } from '../src/utils/base64' // Adjust path if necessary
3
+
4
+ describe('encodeToBase64', () => {
5
+ it('should correctly encode a simple JSON string', () => {
6
+ const jsonString = '{"key": "value"}'
7
+ const expectedBase64 = 'eyJrZXkiOiAidmFsdWUifQ==' // Buffer.from(jsonString).toString('base64')
8
+ expect(encodeToBase64(jsonString)).toBe(expectedBase64)
9
+ })
10
+
11
+ it('should correctly encode a simple XML string', () => {
12
+ const xmlString = '<root><element>value</element></root>'
13
+ const expectedBase64 =
14
+ 'PHJvb3Q+PGVsZW1lbnQ+dmFsdWU8L2VsZW1lbnQ+PC9yb290Pg==' // Buffer.from(xmlString).toString('base64')
15
+ expect(encodeToBase64(xmlString)).toBe(expectedBase64)
16
+ })
17
+
18
+ it('should correctly encode an empty string', () => {
19
+ const emptyString = ''
20
+ const expectedBase64 = '' // Buffer.from(emptyString).toString('base64')
21
+ expect(encodeToBase64(emptyString)).toBe(expectedBase64)
22
+ })
23
+
24
+ it('should correctly encode a string with special characters', () => {
25
+ const specialString = '!@#$%^&*()_+=-`~[]{}\\|;\'",./<>?'
26
+ const expectedBase64 = 'IUAjJCVeJiooKV8rPS1gfltde31cfDsnIiwuLzw+Pw==' // Corrected value
27
+ expect(encodeToBase64(specialString)).toBe(expectedBase64)
28
+ })
29
+
30
+ it('should correctly encode a string with Unicode characters', () => {
31
+ const unicodeString = '你好世界🌍'
32
+ const expectedBase64 = '5L2g5aW95LiW55WM8J+MjQ==' // Corrected value
33
+ expect(encodeToBase64(unicodeString)).toBe(expectedBase64)
34
+ })
35
+
36
+ it('should correctly encode a longer string', () => {
37
+ const longString =
38
+ 'This is a longer string that needs to be encoded to Base64 to ensure it handles more than just a few characters correctly.'
39
+ const expectedBase64 =
40
+ 'VGhpcyBpcyBhIGxvbmdlciBzdHJpbmcgdGhhdCBuZWVkcyB0byBiZSBlbmNvZGVkIHRvIEJhc2U2NCB0byBlbnN1cmUgaXQgaGFuZGxlcyBtb3JlIHRoYW4ganVzdCBhIGZldyBjaGFyYWN0ZXJzIGNvcnJlY3RseS4=' // Buffer.from(longString).toString('base64')
41
+ expect(encodeToBase64(longString)).toBe(expectedBase64)
42
+ })
43
+ })