@atbas/m-mcp 0.1.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 (366) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -0
  3. package/dist/builder/transaction-builder.d.ts +53 -0
  4. package/dist/builder/transaction-builder.d.ts.map +1 -0
  5. package/dist/builder/transaction-builder.js +159 -0
  6. package/dist/builder/transaction-builder.js.map +1 -0
  7. package/dist/catalogue/default-catalogue.d.ts +18 -0
  8. package/dist/catalogue/default-catalogue.d.ts.map +1 -0
  9. package/dist/catalogue/default-catalogue.js +21 -0
  10. package/dist/catalogue/default-catalogue.js.map +1 -0
  11. package/dist/catalogue/endpoints.d.ts +22 -0
  12. package/dist/catalogue/endpoints.d.ts.map +1 -0
  13. package/dist/catalogue/endpoints.js +24 -0
  14. package/dist/catalogue/endpoints.js.map +1 -0
  15. package/dist/catalogue/flows.d.ts +32 -0
  16. package/dist/catalogue/flows.d.ts.map +1 -0
  17. package/dist/catalogue/flows.js +64 -0
  18. package/dist/catalogue/flows.js.map +1 -0
  19. package/dist/catalogue/hash-options.d.ts +112 -0
  20. package/dist/catalogue/hash-options.d.ts.map +1 -0
  21. package/dist/catalogue/hash-options.js +151 -0
  22. package/dist/catalogue/hash-options.js.map +1 -0
  23. package/dist/catalogue/types.d.ts +279 -0
  24. package/dist/catalogue/types.d.ts.map +1 -0
  25. package/dist/catalogue/types.js +360 -0
  26. package/dist/catalogue/types.js.map +1 -0
  27. package/dist/codegen/generate.d.ts +14 -0
  28. package/dist/codegen/generate.d.ts.map +1 -0
  29. package/dist/codegen/generate.js +490 -0
  30. package/dist/codegen/generate.js.map +1 -0
  31. package/dist/codegen/languages/node.d.ts +3 -0
  32. package/dist/codegen/languages/node.d.ts.map +1 -0
  33. package/dist/codegen/languages/node.js +32 -0
  34. package/dist/codegen/languages/node.js.map +1 -0
  35. package/dist/codegen/languages/php.d.ts +3 -0
  36. package/dist/codegen/languages/php.d.ts.map +1 -0
  37. package/dist/codegen/languages/php.js +27 -0
  38. package/dist/codegen/languages/php.js.map +1 -0
  39. package/dist/codegen/languages/python.d.ts +3 -0
  40. package/dist/codegen/languages/python.d.ts.map +1 -0
  41. package/dist/codegen/languages/python.js +27 -0
  42. package/dist/codegen/languages/python.js.map +1 -0
  43. package/dist/codegen/renderer.d.ts +24 -0
  44. package/dist/codegen/renderer.d.ts.map +1 -0
  45. package/dist/codegen/renderer.js +59 -0
  46. package/dist/codegen/renderer.js.map +1 -0
  47. package/dist/codegen/types.d.ts +50 -0
  48. package/dist/codegen/types.d.ts.map +1 -0
  49. package/dist/codegen/types.js +5 -0
  50. package/dist/codegen/types.js.map +1 -0
  51. package/dist/config/credentials.d.ts +64 -0
  52. package/dist/config/credentials.d.ts.map +1 -0
  53. package/dist/config/credentials.js +216 -0
  54. package/dist/config/credentials.js.map +1 -0
  55. package/dist/config/environment.d.ts +23 -0
  56. package/dist/config/environment.d.ts.map +1 -0
  57. package/dist/config/environment.js +54 -0
  58. package/dist/config/environment.js.map +1 -0
  59. package/dist/core/dependencies.d.ts +43 -0
  60. package/dist/core/dependencies.d.ts.map +1 -0
  61. package/dist/core/dependencies.js +2 -0
  62. package/dist/core/dependencies.js.map +1 -0
  63. package/dist/core/registry.d.ts +33 -0
  64. package/dist/core/registry.d.ts.map +1 -0
  65. package/dist/core/registry.js +13 -0
  66. package/dist/core/registry.js.map +1 -0
  67. package/dist/core/server.d.ts +61 -0
  68. package/dist/core/server.d.ts.map +1 -0
  69. package/dist/core/server.js +90 -0
  70. package/dist/core/server.js.map +1 -0
  71. package/dist/dependencies.d.ts +28 -0
  72. package/dist/dependencies.d.ts.map +1 -0
  73. package/dist/dependencies.js +2 -0
  74. package/dist/dependencies.js.map +1 -0
  75. package/dist/diagnostics/diagnose.d.ts +43 -0
  76. package/dist/diagnostics/diagnose.d.ts.map +1 -0
  77. package/dist/diagnostics/diagnose.js +121 -0
  78. package/dist/diagnostics/diagnose.js.map +1 -0
  79. package/dist/domain/amount.d.ts +157 -0
  80. package/dist/domain/amount.d.ts.map +1 -0
  81. package/dist/domain/amount.js +275 -0
  82. package/dist/domain/amount.js.map +1 -0
  83. package/dist/domain/datetime.d.ts +95 -0
  84. package/dist/domain/datetime.d.ts.map +1 -0
  85. package/dist/domain/datetime.js +211 -0
  86. package/dist/domain/datetime.js.map +1 -0
  87. package/dist/domain/response-codes.d.ts +116 -0
  88. package/dist/domain/response-codes.d.ts.map +1 -0
  89. package/dist/domain/response-codes.js +380 -0
  90. package/dist/domain/response-codes.js.map +1 -0
  91. package/dist/domain/secure-hash.d.ts +78 -0
  92. package/dist/domain/secure-hash.d.ts.map +1 -0
  93. package/dist/domain/secure-hash.js +69 -0
  94. package/dist/domain/secure-hash.js.map +1 -0
  95. package/dist/domain/signature/jazzcash.d.ts +56 -0
  96. package/dist/domain/signature/jazzcash.d.ts.map +1 -0
  97. package/dist/domain/signature/jazzcash.js +214 -0
  98. package/dist/domain/signature/jazzcash.js.map +1 -0
  99. package/dist/domain/signature/registry.d.ts +69 -0
  100. package/dist/domain/signature/registry.d.ts.map +1 -0
  101. package/dist/domain/signature/registry.js +92 -0
  102. package/dist/domain/signature/registry.js.map +1 -0
  103. package/dist/domain/signature/scheme.d.ts +349 -0
  104. package/dist/domain/signature/scheme.d.ts.map +1 -0
  105. package/dist/domain/signature/scheme.js +398 -0
  106. package/dist/domain/signature/scheme.js.map +1 -0
  107. package/dist/domain/txn-ref.d.ts +76 -0
  108. package/dist/domain/txn-ref.d.ts.map +1 -0
  109. package/dist/domain/txn-ref.js +126 -0
  110. package/dist/domain/txn-ref.js.map +1 -0
  111. package/dist/domain/types.d.ts +45 -0
  112. package/dist/domain/types.d.ts.map +1 -0
  113. package/dist/domain/types.js +70 -0
  114. package/dist/domain/types.js.map +1 -0
  115. package/dist/errors.d.ts +84 -0
  116. package/dist/errors.d.ts.map +1 -0
  117. package/dist/errors.js +87 -0
  118. package/dist/errors.js.map +1 -0
  119. package/dist/gateway/checkout-form.d.ts +16 -0
  120. package/dist/gateway/checkout-form.d.ts.map +1 -0
  121. package/dist/gateway/checkout-form.js +103 -0
  122. package/dist/gateway/checkout-form.js.map +1 -0
  123. package/dist/gateway/checkout-output.d.ts +70 -0
  124. package/dist/gateway/checkout-output.d.ts.map +1 -0
  125. package/dist/gateway/checkout-output.js +199 -0
  126. package/dist/gateway/checkout-output.js.map +1 -0
  127. package/dist/gateway/guards.d.ts +37 -0
  128. package/dist/gateway/guards.d.ts.map +1 -0
  129. package/dist/gateway/guards.js +44 -0
  130. package/dist/gateway/guards.js.map +1 -0
  131. package/dist/gateway/http-client.d.ts +42 -0
  132. package/dist/gateway/http-client.d.ts.map +1 -0
  133. package/dist/gateway/http-client.js +153 -0
  134. package/dist/gateway/http-client.js.map +1 -0
  135. package/dist/gateway/rest-client.d.ts +42 -0
  136. package/dist/gateway/rest-client.d.ts.map +1 -0
  137. package/dist/gateway/rest-client.js +241 -0
  138. package/dist/gateway/rest-client.js.map +1 -0
  139. package/dist/gateway/transport.d.ts +31 -0
  140. package/dist/gateway/transport.d.ts.map +1 -0
  141. package/dist/gateway/transport.js +73 -0
  142. package/dist/gateway/transport.js.map +1 -0
  143. package/dist/index.d.ts +3 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +75 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/install/cli.d.ts +38 -0
  148. package/dist/install/cli.d.ts.map +1 -0
  149. package/dist/install/cli.js +274 -0
  150. package/dist/install/cli.js.map +1 -0
  151. package/dist/install/harnesses.d.ts +74 -0
  152. package/dist/install/harnesses.d.ts.map +1 -0
  153. package/dist/install/harnesses.js +263 -0
  154. package/dist/install/harnesses.js.map +1 -0
  155. package/dist/knowledge/corpus.d.ts +15 -0
  156. package/dist/knowledge/corpus.d.ts.map +1 -0
  157. package/dist/knowledge/corpus.js +129 -0
  158. package/dist/knowledge/corpus.js.map +1 -0
  159. package/dist/knowledge/search.d.ts +29 -0
  160. package/dist/knowledge/search.d.ts.map +1 -0
  161. package/dist/knowledge/search.js +181 -0
  162. package/dist/knowledge/search.js.map +1 -0
  163. package/dist/knowledge/source.d.ts +60 -0
  164. package/dist/knowledge/source.d.ts.map +1 -0
  165. package/dist/knowledge/source.js +91 -0
  166. package/dist/knowledge/source.js.map +1 -0
  167. package/dist/knowledge/types.d.ts +40 -0
  168. package/dist/knowledge/types.d.ts.map +1 -0
  169. package/dist/knowledge/types.js +19 -0
  170. package/dist/knowledge/types.js.map +1 -0
  171. package/dist/paths.d.ts +26 -0
  172. package/dist/paths.d.ts.map +1 -0
  173. package/dist/paths.js +106 -0
  174. package/dist/paths.js.map +1 -0
  175. package/dist/prompts/integration-walkthrough.d.ts +11 -0
  176. package/dist/prompts/integration-walkthrough.d.ts.map +1 -0
  177. package/dist/prompts/integration-walkthrough.js +52 -0
  178. package/dist/prompts/integration-walkthrough.js.map +1 -0
  179. package/dist/prompts/register.d.ts +4 -0
  180. package/dist/prompts/register.d.ts.map +1 -0
  181. package/dist/prompts/register.js +4 -0
  182. package/dist/prompts/register.js.map +1 -0
  183. package/dist/registry.d.ts +14 -0
  184. package/dist/registry.d.ts.map +1 -0
  185. package/dist/registry.js +2 -0
  186. package/dist/registry.js.map +1 -0
  187. package/dist/resources/knowledge-resources.d.ts +5 -0
  188. package/dist/resources/knowledge-resources.d.ts.map +1 -0
  189. package/dist/resources/knowledge-resources.js +121 -0
  190. package/dist/resources/knowledge-resources.js.map +1 -0
  191. package/dist/resources/register.d.ts +4 -0
  192. package/dist/resources/register.d.ts.map +1 -0
  193. package/dist/resources/register.js +8 -0
  194. package/dist/resources/register.js.map +1 -0
  195. package/dist/server.d.ts +40 -0
  196. package/dist/server.d.ts.map +1 -0
  197. package/dist/server.js +56 -0
  198. package/dist/server.js.map +1 -0
  199. package/dist/telemetry/logger.d.ts +31 -0
  200. package/dist/telemetry/logger.d.ts.map +1 -0
  201. package/dist/telemetry/logger.js +67 -0
  202. package/dist/telemetry/logger.js.map +1 -0
  203. package/dist/telemetry/redaction.d.ts +88 -0
  204. package/dist/telemetry/redaction.d.ts.map +1 -0
  205. package/dist/telemetry/redaction.js +238 -0
  206. package/dist/telemetry/redaction.js.map +1 -0
  207. package/dist/tools/build-transaction-parameters.d.ts +10 -0
  208. package/dist/tools/build-transaction-parameters.d.ts.map +1 -0
  209. package/dist/tools/build-transaction-parameters.js +154 -0
  210. package/dist/tools/build-transaction-parameters.js.map +1 -0
  211. package/dist/tools/call-context.d.ts +47 -0
  212. package/dist/tools/call-context.d.ts.map +1 -0
  213. package/dist/tools/call-context.js +15 -0
  214. package/dist/tools/call-context.js.map +1 -0
  215. package/dist/tools/codegen-tools.d.ts +4 -0
  216. package/dist/tools/codegen-tools.d.ts.map +1 -0
  217. package/dist/tools/codegen-tools.js +131 -0
  218. package/dist/tools/codegen-tools.js.map +1 -0
  219. package/dist/tools/credential-tools.d.ts +32 -0
  220. package/dist/tools/credential-tools.d.ts.map +1 -0
  221. package/dist/tools/credential-tools.js +65 -0
  222. package/dist/tools/credential-tools.js.map +1 -0
  223. package/dist/tools/descriptor.d.ts +52 -0
  224. package/dist/tools/descriptor.d.ts.map +1 -0
  225. package/dist/tools/descriptor.js +2 -0
  226. package/dist/tools/descriptor.js.map +1 -0
  227. package/dist/tools/diagnostic-tools.d.ts +3 -0
  228. package/dist/tools/diagnostic-tools.d.ts.map +1 -0
  229. package/dist/tools/diagnostic-tools.js +128 -0
  230. package/dist/tools/diagnostic-tools.js.map +1 -0
  231. package/dist/tools/gateway-tools.d.ts +6 -0
  232. package/dist/tools/gateway-tools.d.ts.map +1 -0
  233. package/dist/tools/gateway-tools.js +381 -0
  234. package/dist/tools/gateway-tools.js.map +1 -0
  235. package/dist/tools/hash-tools.d.ts +5 -0
  236. package/dist/tools/hash-tools.d.ts.map +1 -0
  237. package/dist/tools/hash-tools.js +238 -0
  238. package/dist/tools/hash-tools.js.map +1 -0
  239. package/dist/tools/knowledge-tools.d.ts +7 -0
  240. package/dist/tools/knowledge-tools.d.ts.map +1 -0
  241. package/dist/tools/knowledge-tools.js +247 -0
  242. package/dist/tools/knowledge-tools.js.map +1 -0
  243. package/dist/tools/onboarding-checklist.d.ts +3 -0
  244. package/dist/tools/onboarding-checklist.d.ts.map +1 -0
  245. package/dist/tools/onboarding-checklist.js +141 -0
  246. package/dist/tools/onboarding-checklist.js.map +1 -0
  247. package/dist/tools/register.d.ts +60 -0
  248. package/dist/tools/register.d.ts.map +1 -0
  249. package/dist/tools/register.js +145 -0
  250. package/dist/tools/register.js.map +1 -0
  251. package/dist/tools/tool-kit.d.ts +88 -0
  252. package/dist/tools/tool-kit.d.ts.map +1 -0
  253. package/dist/tools/tool-kit.js +297 -0
  254. package/dist/tools/tool-kit.js.map +1 -0
  255. package/dist/validation/payload-validator.d.ts +29 -0
  256. package/dist/validation/payload-validator.d.ts.map +1 -0
  257. package/dist/validation/payload-validator.js +79 -0
  258. package/dist/validation/payload-validator.js.map +1 -0
  259. package/dist/validation/rules/amount.d.ts +3 -0
  260. package/dist/validation/rules/amount.d.ts.map +1 -0
  261. package/dist/validation/rules/amount.js +77 -0
  262. package/dist/validation/rules/amount.js.map +1 -0
  263. package/dist/validation/rules/bill-reference.d.ts +10 -0
  264. package/dist/validation/rules/bill-reference.d.ts.map +1 -0
  265. package/dist/validation/rules/bill-reference.js +34 -0
  266. package/dist/validation/rules/bill-reference.js.map +1 -0
  267. package/dist/validation/rules/cnic.d.ts +3 -0
  268. package/dist/validation/rules/cnic.d.ts.map +1 -0
  269. package/dist/validation/rules/cnic.js +42 -0
  270. package/dist/validation/rules/cnic.js.map +1 -0
  271. package/dist/validation/rules/constants.d.ts +9 -0
  272. package/dist/validation/rules/constants.d.ts.map +1 -0
  273. package/dist/validation/rules/constants.js +28 -0
  274. package/dist/validation/rules/constants.js.map +1 -0
  275. package/dist/validation/rules/constraints.d.ts +3 -0
  276. package/dist/validation/rules/constraints.d.ts.map +1 -0
  277. package/dist/validation/rules/constraints.js +113 -0
  278. package/dist/validation/rules/constraints.js.map +1 -0
  279. package/dist/validation/rules/datetime.d.ts +3 -0
  280. package/dist/validation/rules/datetime.d.ts.map +1 -0
  281. package/dist/validation/rules/datetime.js +49 -0
  282. package/dist/validation/rules/datetime.js.map +1 -0
  283. package/dist/validation/rules/empty-string.d.ts +13 -0
  284. package/dist/validation/rules/empty-string.d.ts.map +1 -0
  285. package/dist/validation/rules/empty-string.js +32 -0
  286. package/dist/validation/rules/empty-string.js.map +1 -0
  287. package/dist/validation/rules/expiry.d.ts +3 -0
  288. package/dist/validation/rules/expiry.d.ts.map +1 -0
  289. package/dist/validation/rules/expiry.js +52 -0
  290. package/dist/validation/rules/expiry.js.map +1 -0
  291. package/dist/validation/rules/msisdn.d.ts +3 -0
  292. package/dist/validation/rules/msisdn.d.ts.map +1 -0
  293. package/dist/validation/rules/msisdn.js +45 -0
  294. package/dist/validation/rules/msisdn.js.map +1 -0
  295. package/dist/validation/rules/presence.d.ts +3 -0
  296. package/dist/validation/rules/presence.d.ts.map +1 -0
  297. package/dist/validation/rules/presence.js +90 -0
  298. package/dist/validation/rules/presence.js.map +1 -0
  299. package/dist/validation/rules/return-url.d.ts +14 -0
  300. package/dist/validation/rules/return-url.d.ts.map +1 -0
  301. package/dist/validation/rules/return-url.js +58 -0
  302. package/dist/validation/rules/return-url.js.map +1 -0
  303. package/dist/validation/rules/tool-argument.d.ts +16 -0
  304. package/dist/validation/rules/tool-argument.d.ts.map +1 -0
  305. package/dist/validation/rules/tool-argument.js +38 -0
  306. package/dist/validation/rules/tool-argument.js.map +1 -0
  307. package/dist/validation/rules/txn-ref.d.ts +3 -0
  308. package/dist/validation/rules/txn-ref.d.ts.map +1 -0
  309. package/dist/validation/rules/txn-ref.js +47 -0
  310. package/dist/validation/rules/txn-ref.js.map +1 -0
  311. package/dist/validation/rules/unknown-fields.d.ts +10 -0
  312. package/dist/validation/rules/unknown-fields.d.ts.map +1 -0
  313. package/dist/validation/rules/unknown-fields.js +29 -0
  314. package/dist/validation/rules/unknown-fields.js.map +1 -0
  315. package/dist/validation/types.d.ts +36 -0
  316. package/dist/validation/types.d.ts.map +1 -0
  317. package/dist/validation/types.js +12 -0
  318. package/dist/validation/types.js.map +1 -0
  319. package/dist/version.d.ts +23 -0
  320. package/dist/version.d.ts.map +1 -0
  321. package/dist/version.js +23 -0
  322. package/dist/version.js.map +1 -0
  323. package/knowledge/diagnostics/symptoms.json +321 -0
  324. package/knowledge/docs/bnpl-guide.md +77 -0
  325. package/knowledge/docs/card-page-redirection.md +67 -0
  326. package/knowledge/docs/card-refund.md +91 -0
  327. package/knowledge/docs/hmac-sha256-calculation.md +72 -0
  328. package/knowledge/docs/index.json +189 -0
  329. package/knowledge/docs/ipn-implementation.md +89 -0
  330. package/knowledge/docs/mwallet-recurring.md +117 -0
  331. package/knowledge/docs/mwallet-refund.md +65 -0
  332. package/knowledge/docs/mwallet-rest-v1-1.md +126 -0
  333. package/knowledge/docs/mwallet-rest-v2-0-cnic.md +113 -0
  334. package/knowledge/docs/portal-guide.md +45 -0
  335. package/knowledge/docs/shopify-guide.md +74 -0
  336. package/knowledge/docs/status-inquiry-2025-26.md +32 -0
  337. package/knowledge/docs/status-inquiry.md +111 -0
  338. package/knowledge/docs/wordpress-guide.md +49 -0
  339. package/knowledge/flow.schema.json +390 -0
  340. package/knowledge/flows/bnpl-page-redirection-v1.1.json +211 -0
  341. package/knowledge/flows/card-page-redirection-v1.1.json +211 -0
  342. package/knowledge/flows/mwallet-rest-v1.1.json +187 -0
  343. package/knowledge/flows/mwallet-rest-v2.0-cnic.json +197 -0
  344. package/knowledge/flows/mwallet-token-pay-v4.0.json +130 -0
  345. package/knowledge/flows/refund-bnpl.json +105 -0
  346. package/knowledge/flows/refund-card-v1.1.json +112 -0
  347. package/knowledge/flows/refund-card-v2.0.json +121 -0
  348. package/knowledge/flows/refund-mwallet-v1.1.json +106 -0
  349. package/knowledge/flows/status-inquiry-v1.1.json +102 -0
  350. package/knowledge/flows/status-inquiry-v2.0.json +103 -0
  351. package/knowledge/flows/token-delete-v1.json +85 -0
  352. package/knowledge/flows/token-inquiry-v1.json +85 -0
  353. package/knowledge/flows/wallet-linking-portal.json +103 -0
  354. package/knowledge/templates/node/checkout-redirect.tmpl +166 -0
  355. package/knowledge/templates/node/ipn-handler.tmpl +108 -0
  356. package/knowledge/templates/node/rest-client.tmpl +197 -0
  357. package/knowledge/templates/node/wallet-linking.tmpl +147 -0
  358. package/knowledge/templates/php/checkout-redirect.tmpl +149 -0
  359. package/knowledge/templates/php/ipn-handler.tmpl +105 -0
  360. package/knowledge/templates/php/rest-client.tmpl +191 -0
  361. package/knowledge/templates/php/wallet-linking.tmpl +137 -0
  362. package/knowledge/templates/python/checkout-redirect.tmpl +148 -0
  363. package/knowledge/templates/python/ipn-handler.tmpl +110 -0
  364. package/knowledge/templates/python/rest-client.tmpl +179 -0
  365. package/knowledge/templates/python/wallet-linking.tmpl +133 -0
  366. package/package.json +84 -0
@@ -0,0 +1,275 @@
1
+ import { JazzCashConfigError, JazzCashValidationError } from '../errors.js';
2
+ /** Accepted exponents, kept in step with {@link DECIMAL_PLACES} and {@link MinorUnitExponent}. */
3
+ const MINOR_UNIT_EXPONENTS = [0, 1, 2, 3, 4];
4
+ /**
5
+ * The decimal-places phrase for each exponent.
6
+ *
7
+ * Spelled out per exponent so the message reads as a rule ("at most two decimal
8
+ * places") rather than as a number a merchant has to interpret, and so the
9
+ * singular case is not written "at most 1 decimal places".
10
+ */
11
+ const DECIMAL_PLACES = [
12
+ 'no decimal places',
13
+ 'one decimal place',
14
+ 'two decimal places',
15
+ 'three decimal places',
16
+ 'four decimal places',
17
+ ];
18
+ /** Accepts any number of decimals; the exponent decides how many are too many. */
19
+ const MAJOR_AMOUNT_PATTERN = /^(\d+)(?:\.(\d+))?$/;
20
+ const MINOR_AMOUNT_PATTERN = /^\d+$/;
21
+ /** One hundred major units, in minor units: the worked example in error messages. */
22
+ const EXAMPLE_MAJOR_UNITS = 100n;
23
+ function reject(message, details, docRefs) {
24
+ throw new JazzCashValidationError(message, { details, docRefs });
25
+ }
26
+ /**
27
+ * Validate a currency declaration and derive its scale.
28
+ *
29
+ * The checks run once per definition and are cheap, but a mistyped exponent is
30
+ * not a cosmetic fault: it multiplies or divides every amount in that currency
31
+ * by a power of ten, and no downstream check can detect the difference between a
32
+ * deliberate JPY 10,000 and a KWD 10,000 that should have been 10.000. A
33
+ * currency assembled from a configuration file — the obvious next step once a
34
+ * second provider ships — must fail here rather than at the gateway.
35
+ */
36
+ export function defineCurrency(spec) {
37
+ if (!/^[A-Z]{3}$/.test(spec.code)) {
38
+ throw new JazzCashConfigError(`"${spec.code}" is not an ISO 4217 alphabetic currency code, which is three capital letters such as "PKR".`, { details: { code: spec.code } });
39
+ }
40
+ if (!MINOR_UNIT_EXPONENTS.includes(spec.exponent)) {
41
+ throw new JazzCashConfigError(`Minor-unit exponent ${String(spec.exponent)} for ${spec.code} is not one of the ISO 4217 exponents 0 to 4.`, { details: { code: spec.code, exponent: spec.exponent } });
42
+ }
43
+ if (spec.maxMajorUnits <= 0n) {
44
+ throw new JazzCashConfigError(`The ${spec.code} transaction ceiling must be greater than zero, or no amount at all is payable.`, { details: { code: spec.code, maxMajorUnits: spec.maxMajorUnits.toString() } });
45
+ }
46
+ const scale = 10n ** BigInt(spec.exponent);
47
+ return {
48
+ ...spec,
49
+ docRefs: spec.docRefs ?? [],
50
+ scale,
51
+ maxMinorUnits: spec.maxMajorUnits * scale,
52
+ };
53
+ }
54
+ /** The only currency JazzCash processes. No conversion is performed. */
55
+ export const PKR = defineCurrency({
56
+ code: 'PKR',
57
+ exponent: 2,
58
+ encoding: 'minor-units',
59
+ // No guide states a gateway ceiling. PKR 100,000,000.00 is a local sanity
60
+ // guard against a unit-conversion bug reaching the gateway as a real debit,
61
+ // not a JazzCash rule.
62
+ maxMajorUnits: 100000000n,
63
+ docRefs: [{ docId: 'mwallet-rest-v2-0-cnic', sectionId: 'currency-and-amount-handling' }],
64
+ });
65
+ /** The ISO 4217 code of the only currency the JazzCash gateway processes. */
66
+ export const SUPPORTED_CURRENCY = PKR.code;
67
+ /**
68
+ * Upper bound on a single PKR transaction, in minor units (PKR 100,000,000.00).
69
+ *
70
+ * Derived from {@link PKR} rather than written out, so the ceiling cannot drift
71
+ * away from the currency it belongs to. Retained as a named export because
72
+ * callers and tests reason about the PKR ceiling directly.
73
+ */
74
+ export const MAX_MINOR_UNITS = PKR.maxMinorUnits;
75
+ /**
76
+ * Parse a major-unit figure into exact minor units.
77
+ *
78
+ * Returns `undefined` rather than throwing so that both the human-facing and the
79
+ * wire-facing caller can word the rejection for their own audience. Anything
80
+ * with more decimal places than the currency has is refused, because rounding a
81
+ * merchant's money silently is worse than refusing it.
82
+ */
83
+ function parseMajorUnits(text, currency) {
84
+ const match = MAJOR_AMOUNT_PATTERN.exec(text);
85
+ if (match === null) {
86
+ return undefined;
87
+ }
88
+ /* v8 ignore next -- unreachable: group 1 is mandatory in MAJOR_AMOUNT_PATTERN */
89
+ const whole = match[1] ?? '0';
90
+ // Group 2 is genuinely optional: "100" has no fractional part.
91
+ const fraction = match[2] ?? '';
92
+ if (fraction.length > currency.exponent) {
93
+ return undefined;
94
+ }
95
+ const padded = fraction.padEnd(currency.exponent, '0');
96
+ return BigInt(whole) * currency.scale + (padded === '' ? 0n : BigInt(padded));
97
+ }
98
+ /** Describe the shape a major-unit amount must take in this currency. */
99
+ function majorUnitsAdvice(currency) {
100
+ // A currency with no minor unit has no "decimal places" rule to state, and
101
+ // offering "100.50" as an example there would invite the very value that is
102
+ // being refused.
103
+ if (currency.exponent === 0) {
104
+ return `a positive whole number, because ${currency.code} has no minor unit. Pass a value such as "100"${minorUnitsAdvice(currency)}.`;
105
+ }
106
+ const example = `100.${'5'.padEnd(currency.exponent, '0')}`;
107
+ return `a positive decimal with at most ${DECIMAL_PLACES[currency.exponent]}. Pass a value such as "100" or "${example}"${minorUnitsAdvice(currency)}.`;
108
+ }
109
+ /** Only a minor-unit gateway accepts minor units, so only it may be offered them. */
110
+ function minorUnitsAdvice(currency) {
111
+ return currency.encoding === 'minor-units' ? ', or pass minor units directly' : '';
112
+ }
113
+ /** Bounds every amount must satisfy, whichever form it arrived in. */
114
+ function assertWithinBounds(minor, display, currency) {
115
+ if (minor === 0n) {
116
+ reject('Amount must be greater than zero.', { amount: display }, currency.docRefs);
117
+ }
118
+ if (minor > currency.maxMinorUnits) {
119
+ reject(`Amount ${display} exceeds the configured ceiling of ${fromMinorUnits(currency.maxMinorUnits.toString(), currency)} ${currency.code}. Raise maxMajorUnits on the ${currency.code} currency definition deliberately if this is a genuine transaction.`, { amount: display }, currency.docRefs);
120
+ }
121
+ }
122
+ /**
123
+ * Reject a wire value padded with whitespace rather than absorbing it.
124
+ *
125
+ * The payload transmits the value and the secure hash is computed over it
126
+ * verbatim, so approving `" 10000 "` approves a hash input the gateway never
127
+ * expects.
128
+ */
129
+ function assertNoSurroundingWhitespace(value, currency) {
130
+ if (value !== value.trim()) {
131
+ reject(`pp_Amount "${value}" carries leading or trailing whitespace. The value is transmitted and hashed exactly as written, so the padding corrupts pp_SecureHash even when the digits are right. Remove the surrounding whitespace before building the payload.`, { amount: value }, currency.docRefs);
132
+ }
133
+ }
134
+ /**
135
+ * Convert a human-facing amount to the minor-unit string the gateway expects.
136
+ *
137
+ * Accepts `"100"`, `"100.5"`, `"100.50"` and the numeric equivalents in a
138
+ * two-decimal currency. Rejects anything with more decimal places than the
139
+ * currency has, because rounding a merchant's money silently is worse than
140
+ * refusing it.
141
+ */
142
+ export function toMinorUnits(amount, currency = PKR) {
143
+ if (typeof amount === 'number') {
144
+ if (!Number.isFinite(amount)) {
145
+ reject('Amount must be a finite number.', { amount: String(amount) }, currency.docRefs);
146
+ }
147
+ // Round-tripping through a string keeps the decimal-places rule enforceable
148
+ // and surfaces float artefacts (0.1 + 0.2) as a validation error rather than
149
+ // a silently wrong debit.
150
+ return toMinorUnits(String(amount), currency);
151
+ }
152
+ const trimmed = amount.trim();
153
+ if (trimmed === '') {
154
+ reject('Amount is required.', { amount }, currency.docRefs);
155
+ }
156
+ const minor = parseMajorUnits(trimmed, currency);
157
+ if (minor === undefined) {
158
+ reject(`Amount "${trimmed}" is not ${majorUnitsAdvice(currency)}`, { amount: trimmed }, currency.docRefs);
159
+ }
160
+ assertWithinBounds(minor, trimmed, currency);
161
+ return minor.toString();
162
+ }
163
+ /**
164
+ * Validate a minor-unit string, throwing with an actionable message if it is wrong.
165
+ *
166
+ * Unlike {@link toMinorUnits}, which converts a human-facing figure, this checks
167
+ * a value that is already in wire form, so surrounding whitespace is rejected
168
+ * rather than absorbed.
169
+ */
170
+ export function assertMinorUnits(value, currency = PKR) {
171
+ assertNoSurroundingWhitespace(value, currency);
172
+ if (!MINOR_AMOUNT_PATTERN.test(value)) {
173
+ const example = (EXAMPLE_MAJOR_UNITS * currency.scale).toString();
174
+ const hint = value.includes('.')
175
+ ? ` It contains a decimal point: pp_Amount is in minor units, so ${formatAmount(example, currency)} is sent as "${example}".`
176
+ : '';
177
+ reject(`pp_Amount "${value}" must be a whole number of minor units.${hint}`, { amount: value }, currency.docRefs);
178
+ }
179
+ const minor = BigInt(value);
180
+ if (minor === 0n) {
181
+ reject('pp_Amount must be greater than zero.', { amount: value }, currency.docRefs);
182
+ }
183
+ if (minor > currency.maxMinorUnits) {
184
+ reject(`pp_Amount ${value} exceeds the configured ceiling of ${currency.maxMinorUnits.toString()} minor units.`, { amount: value }, currency.docRefs);
185
+ }
186
+ }
187
+ /** Convert minor units back to a major-unit string, e.g. `"10000"` to `"100.00"`. */
188
+ export function fromMinorUnits(minorUnits, currency = PKR) {
189
+ const trimmed = minorUnits.trim();
190
+ if (!MINOR_AMOUNT_PATTERN.test(trimmed)) {
191
+ reject(`Cannot interpret "${minorUnits}" as minor units: it must be a whole number.`, { minorUnits }, currency.docRefs);
192
+ }
193
+ const minor = BigInt(trimmed);
194
+ // A currency with no minor unit has no fractional part at all: JPY 100 is
195
+ // written "100", and a trailing "100." would be rejected by every consumer.
196
+ if (currency.exponent === 0) {
197
+ return minor.toString();
198
+ }
199
+ const whole = minor / currency.scale;
200
+ const fraction = (minor % currency.scale).toString().padStart(currency.exponent, '0');
201
+ return `${whole.toString()}.${fraction}`;
202
+ }
203
+ /** Format minor units for display to a merchant, e.g. `"PKR 100.00"`. */
204
+ export function formatAmount(minorUnits, currency = PKR) {
205
+ return `${currency.code} ${fromMinorUnits(minorUnits, currency)}`;
206
+ }
207
+ /**
208
+ * Format minor units as PKR.
209
+ *
210
+ * Retained as a named function because it reads better at a JazzCash-only call
211
+ * site than {@link formatAmount} with a defaulted currency, and because removing
212
+ * it would churn callers for no behavioural gain.
213
+ */
214
+ export function formatPkr(minorUnits) {
215
+ return formatAmount(minorUnits, PKR);
216
+ }
217
+ /**
218
+ * Encode a human-facing amount as the provider's gateway expects to receive it.
219
+ *
220
+ * The counterpart of {@link toMinorUnits} for a provider whose wire form is not
221
+ * minor units. Both go through the same exact integer parse, so a `major-units`
222
+ * gateway cannot receive a figure that a `minor-units` gateway would have
223
+ * refused.
224
+ */
225
+ export function toWireAmount(amount, currency = PKR) {
226
+ const minor = toMinorUnits(amount, currency);
227
+ return currency.encoding === 'minor-units' ? minor : fromMinorUnits(minor, currency);
228
+ }
229
+ /**
230
+ * Validate a value already in wire form against the currency's encoding.
231
+ *
232
+ * A decimal point is a defect in a minor-unit payload and mandatory in a
233
+ * major-unit one, so the same string cannot be judged without knowing which
234
+ * gateway it is bound for.
235
+ */
236
+ export function assertWireAmount(value, currency = PKR) {
237
+ if (currency.encoding === 'minor-units') {
238
+ assertMinorUnits(value, currency);
239
+ return;
240
+ }
241
+ assertNoSurroundingWhitespace(value, currency);
242
+ const minor = parseMajorUnits(value, currency);
243
+ if (minor === undefined) {
244
+ reject(`Amount "${value}" is not ${majorUnitsAdvice(currency)}`, { amount: value }, currency.docRefs);
245
+ }
246
+ assertWithinBounds(minor, value, currency);
247
+ }
248
+ /**
249
+ * Recover canonical minor units from a value in wire form.
250
+ *
251
+ * The inverse of {@link toWireAmount}. Response handling compares the amount the
252
+ * gateway echoed with the amount that was sent, and comparing `"100.00"` with
253
+ * `"10000"` as strings reports a mismatch between two identical sums.
254
+ */
255
+ export function minorUnitsFromWire(value, currency = PKR) {
256
+ assertWireAmount(value, currency);
257
+ return currency.encoding === 'minor-units' ? value : toMinorUnits(value, currency);
258
+ }
259
+ /**
260
+ * Heuristic: does this look like a major-unit amount that was never scaled?
261
+ *
262
+ * Used by validation to turn the single most common integration mistake into a
263
+ * specific warning instead of an opaque gateway rejection. A decimal point is
264
+ * conclusive; a suspiciously small whole number is only a warning, because a
265
+ * PKR 1.00 test transaction (`"100"`) is both legitimate and common.
266
+ */
267
+ export function looksLikeMajorUnits(value, currency = PKR) {
268
+ // Where major units are the wire form, a decimal point is correct. Flagging it
269
+ // would train a merchant to dismiss the warning that does matter.
270
+ if (currency.encoding === 'major-units') {
271
+ return false;
272
+ }
273
+ return value.includes('.');
274
+ }
275
+ //# sourceMappingURL=amount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount.js","sourceRoot":"","sources":["../../src/domain/amount.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Wall-clock timestamp handling.
3
+ *
4
+ * Every guide states that `pp_TxnDateTime` and `pp_TxnExpiryDateTime` are
5
+ * `YYYYMMDDHHMMSS` in Pakistan Standard Time, and the PHP samples open with
6
+ * `date_default_timezone_set("Asia/karachi")`. Pakistan Standard Time is
7
+ * therefore the default of every function here, and no existing caller has to
8
+ * name it.
9
+ *
10
+ * The zone is a parameter rather than a module constant. It used to be an
11
+ * `Intl.DateTimeFormat` built at import time — a process-wide singleton fixed
12
+ * before any flow was known, so a provider settling in another zone could not be
13
+ * expressed at all, only worked around by changing the host's clock.
14
+ *
15
+ * Formatting goes through `Intl.DateTimeFormat` with an explicit time zone, so
16
+ * the output is correct no matter what the host clock is set to. A merchant
17
+ * server in UTC and one in New York must produce the same stamp for the same
18
+ * instant, and a wrong stamp fails gateway validation with an unhelpful error.
19
+ *
20
+ * Pakistan has observed no daylight saving since 2009 and Turkey none since
21
+ * 2016, so both zones in use have a fixed offset — which is exactly why the
22
+ * arithmetic below must not assume one. A stamp names a wall clock, and in a
23
+ * zone that observes daylight saving the gap between two wall clocks is not the
24
+ * time that elapsed between them: on a spring-forward morning 01:59 and 03:10
25
+ * are eleven minutes apart, not seventy-one. Every comparison therefore resolves
26
+ * a stamp to the instant it names in its zone before subtracting, so that the
27
+ * Status Inquiry wait and the expiry window stay true durations.
28
+ */
29
+ export declare const PAKISTAN_TIME_ZONE = "Asia/Karachi";
30
+ export declare const TXN_DATE_TIME_FORMAT = "YYYYMMDDHHMMSS";
31
+ export interface TxnDateTimeParts {
32
+ readonly year: number;
33
+ readonly month: number;
34
+ readonly day: number;
35
+ readonly hour: number;
36
+ readonly minute: number;
37
+ readonly second: number;
38
+ }
39
+ /** Format an instant as `YYYYMMDDHHMMSS`, in Pakistan Standard Time unless told otherwise. */
40
+ export declare function formatTxnDateTime(date: Date, timeZone?: string): string;
41
+ /** Current transaction stamp in the provider's zone. The clock is injectable for testing. */
42
+ export declare function nowTxnDateTime(now?: Date, timeZone?: string): string;
43
+ /**
44
+ * Add whole days to an instant.
45
+ *
46
+ * Elapsed days, not wall-clock days: the result is exactly 24 hours per day
47
+ * later, so an expiry window is the length the merchant asked for even when the
48
+ * zone gains or loses an hour in between. The wall clock the stamp then shows
49
+ * moves by that hour, which is the correct rendering of that instant.
50
+ */
51
+ export declare function addDays(date: Date, days: number): Date;
52
+ /**
53
+ * Expiry stamp for a transaction, one day after it, as every guide specifies.
54
+ */
55
+ export declare function txnExpiryDateTime(date?: Date, days?: number, timeZone?: string): string;
56
+ /** True when a string is a structurally and calendrically valid stamp. */
57
+ export declare function isValidTxnDateTime(value: string): boolean;
58
+ /** Parse a stamp into its components, or throw with the expected format named. */
59
+ export declare function parseTxnDateTime(value: string): TxnDateTimeParts;
60
+ /**
61
+ * The instant a stamp names in a zone.
62
+ *
63
+ * Resolved in two passes. The first offset is looked up from the wall clock read
64
+ * as though it were UTC, which lands on the correct side of a daylight-saving
65
+ * transition for every stamp except those within an offset's distance of it; the
66
+ * second lookup, from the instant that first offset implies, corrects those.
67
+ * A single pass would put a stamp taken minutes after a spring-forward an hour
68
+ * out, and the error is invisible in the digits.
69
+ *
70
+ * A wall clock is not always one instant. A stamp inside the hour a
71
+ * spring-forward skips names no instant at all, and one inside the hour an
72
+ * autumn fall-back repeats names two. Both resolve deterministically rather than
73
+ * meaningfully: a repeated wall clock resolves to its first occurrence, and a
74
+ * skipped one to the hour before the reading its own offset implies. A stamp
75
+ * that needs either rule is one the system that emitted it got wrong, and both
76
+ * resolutions are pinned by tests so neither can drift unnoticed.
77
+ */
78
+ export declare function txnDateTimeToInstant(value: string, timeZone?: string): Date;
79
+ /** Whole and fractional days from `from` to `to`. Negative when `to` precedes `from`. */
80
+ export declare function daysBetweenTxnDateTimes(from: string, to: string, timeZone?: string): number;
81
+ /** True when `expiry` is strictly after `txnDateTime`. */
82
+ export declare function isExpiryAfterTransaction(txnDateTime: string, expiry: string, timeZone?: string): boolean;
83
+ /**
84
+ * Minutes elapsed between a transaction stamp and an instant.
85
+ *
86
+ * Used to enforce the Status Inquiry guidance that the API should not be called
87
+ * until at least ten minutes after the transaction was initiated. The stamp is
88
+ * resolved to its instant rather than the instant reduced to a stamp, so that a
89
+ * daylight-saving transition between the two cannot report seventy-one minutes
90
+ * where eleven have passed and wave through a call the gateway will refuse.
91
+ */
92
+ export declare function minutesSinceTxnDateTime(txnDateTime: string, now?: Date, timeZone?: string): number;
93
+ /** Documented minimum wait before calling Status Inquiry. */
94
+ export declare const STATUS_INQUIRY_MINIMUM_WAIT_MINUTES = 10;
95
+ //# sourceMappingURL=datetime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datetime.d.ts","sourceRoot":"","sources":["../../src/domain/datetime.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAkDrD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAuCD,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAE,MAA2B,GAAG,MAAM,CAI3F;AAED,6FAA6F;AAC7F,wBAAgB,cAAc,CAAC,GAAG,GAAE,IAAiB,EAAE,QAAQ,GAAE,MAA2B,GAAG,MAAM,CAEpG;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,IAAiB,EAAE,IAAI,SAAI,EAAE,QAAQ,GAAE,MAA2B,GAAG,MAAM,CAElH;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAuBzD;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAqBhE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA2B,GAAG,IAAI,CAK/F;AAED,yFAAyF;AACzF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAE,MAA2B,GAAG,MAAM,CAG/G;AAED,0DAA0D;AAC1D,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,MAA2B,GACpC,OAAO,CAET;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,IAAiB,EACtB,QAAQ,GAAE,MAA2B,GACpC,MAAM,CAOR;AAED,6DAA6D;AAC7D,eAAO,MAAM,mCAAmC,KAAK,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { JazzCashConfigError, JazzCashValidationError } from '../errors.js';
2
+ /**
3
+ * Wall-clock timestamp handling.
4
+ *
5
+ * Every guide states that `pp_TxnDateTime` and `pp_TxnExpiryDateTime` are
6
+ * `YYYYMMDDHHMMSS` in Pakistan Standard Time, and the PHP samples open with
7
+ * `date_default_timezone_set("Asia/karachi")`. Pakistan Standard Time is
8
+ * therefore the default of every function here, and no existing caller has to
9
+ * name it.
10
+ *
11
+ * The zone is a parameter rather than a module constant. It used to be an
12
+ * `Intl.DateTimeFormat` built at import time — a process-wide singleton fixed
13
+ * before any flow was known, so a provider settling in another zone could not be
14
+ * expressed at all, only worked around by changing the host's clock.
15
+ *
16
+ * Formatting goes through `Intl.DateTimeFormat` with an explicit time zone, so
17
+ * the output is correct no matter what the host clock is set to. A merchant
18
+ * server in UTC and one in New York must produce the same stamp for the same
19
+ * instant, and a wrong stamp fails gateway validation with an unhelpful error.
20
+ *
21
+ * Pakistan has observed no daylight saving since 2009 and Turkey none since
22
+ * 2016, so both zones in use have a fixed offset — which is exactly why the
23
+ * arithmetic below must not assume one. A stamp names a wall clock, and in a
24
+ * zone that observes daylight saving the gap between two wall clocks is not the
25
+ * time that elapsed between them: on a spring-forward morning 01:59 and 03:10
26
+ * are eleven minutes apart, not seventy-one. Every comparison therefore resolves
27
+ * a stamp to the instant it names in its zone before subtracting, so that the
28
+ * Status Inquiry wait and the expiry window stay true durations.
29
+ */
30
+ export const PAKISTAN_TIME_ZONE = 'Asia/Karachi';
31
+ export const TXN_DATE_TIME_FORMAT = 'YYYYMMDDHHMMSS';
32
+ const TXN_DATE_TIME_PATTERN = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/;
33
+ const MILLISECONDS_PER_DAY = 86_400_000;
34
+ const MILLISECONDS_PER_MINUTE = 60_000;
35
+ const MILLISECONDS_PER_SECOND = 1_000;
36
+ /**
37
+ * Formatters, memoised by zone.
38
+ *
39
+ * A memo of a pure function, not mutable state: constructing an
40
+ * `Intl.DateTimeFormat` costs far more than formatting with one, and a single
41
+ * payload formats two stamps, so rebuilding per call would be paid on every
42
+ * transaction. Only zones the runtime accepted are inserted and the IANA
43
+ * database is finite, so the map cannot grow without bound.
44
+ */
45
+ const formatters = new Map();
46
+ function formatterFor(timeZone) {
47
+ const memoised = formatters.get(timeZone);
48
+ if (memoised !== undefined) {
49
+ return memoised;
50
+ }
51
+ let formatter;
52
+ try {
53
+ formatter = new Intl.DateTimeFormat('en-GB', {
54
+ timeZone,
55
+ year: 'numeric',
56
+ month: '2-digit',
57
+ day: '2-digit',
58
+ hour: '2-digit',
59
+ minute: '2-digit',
60
+ second: '2-digit',
61
+ hourCycle: 'h23',
62
+ });
63
+ }
64
+ catch (cause) {
65
+ // A mistyped zone otherwise surfaces as a bare RangeError from inside a
66
+ // payload build, naming neither the setting at fault nor the provider.
67
+ throw new JazzCashConfigError(`"${timeZone}" is not an IANA time zone this runtime recognises. Use an identifier such as "${PAKISTAN_TIME_ZONE}" or "Europe/Istanbul".`, { cause, details: { timeZone } });
68
+ }
69
+ formatters.set(timeZone, formatter);
70
+ return formatter;
71
+ }
72
+ function partsOf(date, timeZone) {
73
+ const result = {};
74
+ for (const part of formatterFor(timeZone).formatToParts(date)) {
75
+ if (part.type !== 'literal') {
76
+ result[part.type] = part.value;
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+ /** Reject an instant that is not one, rather than emitting `NaN` into a payload. */
82
+ function assertRealInstant(date) {
83
+ if (Number.isNaN(date.getTime())) {
84
+ throw new JazzCashValidationError('Cannot format an invalid Date as a JazzCash timestamp.');
85
+ }
86
+ }
87
+ /** The wall clock an instant shows in a zone, expressed as though it were UTC. */
88
+ function wallClockOf(instant, timeZone) {
89
+ const parts = partsOf(new Date(instant), timeZone);
90
+ return Date.UTC(Number(parts['year']), Number(parts['month']) - 1, Number(parts['day']), Number(parts['hour']), Number(parts['minute']), Number(parts['second']));
91
+ }
92
+ /** A zone's offset from UTC at an instant, in milliseconds. */
93
+ function offsetAt(instant, timeZone) {
94
+ // The formatter resolves to the second, so the instant is compared at the same
95
+ // resolution; a millisecond component would otherwise be read as offset.
96
+ return wallClockOf(instant, timeZone) - Math.floor(instant / MILLISECONDS_PER_SECOND) * MILLISECONDS_PER_SECOND;
97
+ }
98
+ /** Format an instant as `YYYYMMDDHHMMSS`, in Pakistan Standard Time unless told otherwise. */
99
+ export function formatTxnDateTime(date, timeZone = PAKISTAN_TIME_ZONE) {
100
+ assertRealInstant(date);
101
+ const parts = partsOf(date, timeZone);
102
+ return [parts['year'], parts['month'], parts['day'], parts['hour'], parts['minute'], parts['second']].join('');
103
+ }
104
+ /** Current transaction stamp in the provider's zone. The clock is injectable for testing. */
105
+ export function nowTxnDateTime(now = new Date(), timeZone = PAKISTAN_TIME_ZONE) {
106
+ return formatTxnDateTime(now, timeZone);
107
+ }
108
+ /**
109
+ * Add whole days to an instant.
110
+ *
111
+ * Elapsed days, not wall-clock days: the result is exactly 24 hours per day
112
+ * later, so an expiry window is the length the merchant asked for even when the
113
+ * zone gains or loses an hour in between. The wall clock the stamp then shows
114
+ * moves by that hour, which is the correct rendering of that instant.
115
+ */
116
+ export function addDays(date, days) {
117
+ return new Date(date.getTime() + days * MILLISECONDS_PER_DAY);
118
+ }
119
+ /**
120
+ * Expiry stamp for a transaction, one day after it, as every guide specifies.
121
+ */
122
+ export function txnExpiryDateTime(date = new Date(), days = 1, timeZone = PAKISTAN_TIME_ZONE) {
123
+ return formatTxnDateTime(addDays(date, days), timeZone);
124
+ }
125
+ /** True when a string is a structurally and calendrically valid stamp. */
126
+ export function isValidTxnDateTime(value) {
127
+ const match = TXN_DATE_TIME_PATTERN.exec(value);
128
+ if (match === null) {
129
+ return false;
130
+ }
131
+ const [, year, month, day, hour, minute, second] = match.map(Number);
132
+ if (month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59) {
133
+ return false;
134
+ }
135
+ // Reject 31 April and 29 February in a common year, which pass a range check
136
+ // but are not real dates and are rejected by the gateway.
137
+ const asUtc = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
138
+ return asUtc.getUTCFullYear() === year && asUtc.getUTCMonth() === month - 1 && asUtc.getUTCDate() === day;
139
+ }
140
+ /** Parse a stamp into its components, or throw with the expected format named. */
141
+ export function parseTxnDateTime(value) {
142
+ if (!isValidTxnDateTime(value)) {
143
+ throw new JazzCashValidationError(`"${value}" is not a valid ${TXN_DATE_TIME_FORMAT} timestamp in Pakistan Standard Time. Example: 20260114153733.`, { details: { value }, docRefs: [{ docId: 'mwallet-rest-v2-0-cnic', sectionId: 'date-and-time-parameters' }] });
144
+ }
145
+ const match = TXN_DATE_TIME_PATTERN.exec(value);
146
+ /* c8 ignore next 3 -- unreachable: isValidTxnDateTime already matched this pattern */
147
+ if (match === null) {
148
+ throw new JazzCashValidationError(`"${value}" is not a valid ${TXN_DATE_TIME_FORMAT} timestamp.`);
149
+ }
150
+ return {
151
+ year: Number(match[1]),
152
+ month: Number(match[2]),
153
+ day: Number(match[3]),
154
+ hour: Number(match[4]),
155
+ minute: Number(match[5]),
156
+ second: Number(match[6]),
157
+ };
158
+ }
159
+ /**
160
+ * The instant a stamp names in a zone.
161
+ *
162
+ * Resolved in two passes. The first offset is looked up from the wall clock read
163
+ * as though it were UTC, which lands on the correct side of a daylight-saving
164
+ * transition for every stamp except those within an offset's distance of it; the
165
+ * second lookup, from the instant that first offset implies, corrects those.
166
+ * A single pass would put a stamp taken minutes after a spring-forward an hour
167
+ * out, and the error is invisible in the digits.
168
+ *
169
+ * A wall clock is not always one instant. A stamp inside the hour a
170
+ * spring-forward skips names no instant at all, and one inside the hour an
171
+ * autumn fall-back repeats names two. Both resolve deterministically rather than
172
+ * meaningfully: a repeated wall clock resolves to its first occurrence, and a
173
+ * skipped one to the hour before the reading its own offset implies. A stamp
174
+ * that needs either rule is one the system that emitted it got wrong, and both
175
+ * resolutions are pinned by tests so neither can drift unnoticed.
176
+ */
177
+ export function txnDateTimeToInstant(value, timeZone = PAKISTAN_TIME_ZONE) {
178
+ const parts = parseTxnDateTime(value);
179
+ const wallClock = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
180
+ const firstPass = offsetAt(wallClock, timeZone);
181
+ return new Date(wallClock - offsetAt(wallClock - firstPass, timeZone));
182
+ }
183
+ /** Whole and fractional days from `from` to `to`. Negative when `to` precedes `from`. */
184
+ export function daysBetweenTxnDateTimes(from, to, timeZone = PAKISTAN_TIME_ZONE) {
185
+ const elapsed = txnDateTimeToInstant(to, timeZone).getTime() - txnDateTimeToInstant(from, timeZone).getTime();
186
+ return elapsed / MILLISECONDS_PER_DAY;
187
+ }
188
+ /** True when `expiry` is strictly after `txnDateTime`. */
189
+ export function isExpiryAfterTransaction(txnDateTime, expiry, timeZone = PAKISTAN_TIME_ZONE) {
190
+ return txnDateTimeToInstant(expiry, timeZone).getTime() > txnDateTimeToInstant(txnDateTime, timeZone).getTime();
191
+ }
192
+ /**
193
+ * Minutes elapsed between a transaction stamp and an instant.
194
+ *
195
+ * Used to enforce the Status Inquiry guidance that the API should not be called
196
+ * until at least ten minutes after the transaction was initiated. The stamp is
197
+ * resolved to its instant rather than the instant reduced to a stamp, so that a
198
+ * daylight-saving transition between the two cannot report seventy-one minutes
199
+ * where eleven have passed and wave through a call the gateway will refuse.
200
+ */
201
+ export function minutesSinceTxnDateTime(txnDateTime, now = new Date(), timeZone = PAKISTAN_TIME_ZONE) {
202
+ assertRealInstant(now);
203
+ // Truncated to the second because a stamp carries no sub-second component:
204
+ // reporting 10.008 minutes where the gateway's own clock reads 10 would make
205
+ // this guard disagree with the rule it exists to enforce.
206
+ const nowSeconds = Math.floor(now.getTime() / MILLISECONDS_PER_SECOND) * MILLISECONDS_PER_SECOND;
207
+ return (nowSeconds - txnDateTimeToInstant(txnDateTime, timeZone).getTime()) / MILLISECONDS_PER_MINUTE;
208
+ }
209
+ /** Documented minimum wait before calling Status Inquiry. */
210
+ export const STATUS_INQUIRY_MINIMUM_WAIT_MINUTES = 10;
211
+ //# sourceMappingURL=datetime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datetime.js","sourceRoot":"","sources":["../../src/domain/datetime.ts"],"names":[],"mappings":"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"}