@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,166 @@
1
+ /**
2
+ * JazzCash — {{FLOW_TITLE}}
3
+ *
4
+ * Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
5
+ * Sources: {{DOC_SOURCES}}
6
+ *
7
+ * Renders a signed, auto-submitting form that redirects the customer to the
8
+ * JazzCash hosted payment page.
9
+ *
10
+ * SAFETY NOTES
11
+ * - Sandbox and production use the SAME URL. The environment is decided by the
12
+ * credentials alone.
13
+ * - The Integrity Salt is the HMAC key and must never reach the browser. This
14
+ * module runs server-side and emits only the finished hash.
15
+ * - pp_ReturnURL must be byte-identical to the URL registered in the portal.
16
+ *
17
+ * Requires Node.js 20 or later.
18
+ */
19
+
20
+ import { createHmac, randomInt } from 'node:crypto';
21
+
22
+ export const JAZZCASH_POST_URL = '{{ENDPOINT}}';
23
+
24
+ const PAKISTAN_TIME_ZONE = 'Asia/Karachi';
25
+
26
+ // pp_TxnRefNo is annotated [20AN] in the guides: at most 20 letters and digits.
27
+ const MAX_REFERENCE_LENGTH = 20;
28
+ const REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
29
+
30
+ const pktFormatter = new Intl.DateTimeFormat('en-GB', {
31
+ timeZone: PAKISTAN_TIME_ZONE,
32
+ year: 'numeric',
33
+ month: '2-digit',
34
+ day: '2-digit',
35
+ hour: '2-digit',
36
+ minute: '2-digit',
37
+ second: '2-digit',
38
+ hourCycle: 'h23',
39
+ });
40
+
41
+ function txnDateTime(date = new Date()) {
42
+ const parts = Object.fromEntries(
43
+ pktFormatter.formatToParts(date).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]),
44
+ );
45
+ return `${parts.year}${parts.month}${parts.day}${parts.hour}${parts.minute}${parts.second}`;
46
+ }
47
+
48
+ /**
49
+ * Reference that stays unique inside a single millisecond.
50
+ *
51
+ * The clock alone is not enough: two orders placed in the same millisecond
52
+ * produce the same reference, and the gateway rejects the second as a duplicate.
53
+ * The random suffix fills whatever the prefix and the timestamp leave of the
54
+ * 20-character budget, drawn from crypto.randomInt rather than Math.random.
55
+ */
56
+ function generateReference(prefix, date = new Date()) {
57
+ const stamp = txnDateTime(date);
58
+ const room = MAX_REFERENCE_LENGTH - prefix.length - stamp.length;
59
+
60
+ if (room < 1) {
61
+ throw new Error(`Reference prefix "${prefix}" leaves no room inside the ${MAX_REFERENCE_LENGTH}-character limit.`);
62
+ }
63
+
64
+ let suffix = '';
65
+ for (let index = 0; index < room; index += 1) {
66
+ suffix += REFERENCE_ALPHABET[randomInt(REFERENCE_ALPHABET.length)];
67
+ }
68
+
69
+ return `${prefix}${stamp}${suffix}`;
70
+ }
71
+
72
+ function toMinorUnits(majorAmount) {
73
+ const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(String(majorAmount).trim());
74
+ if (match === null) {
75
+ throw new Error(`Amount "${majorAmount}" must be a positive value with at most two decimal places.`);
76
+ }
77
+ return (BigInt(match[1]) * 100n + BigInt((match[2] ?? '').padEnd(2, '0'))).toString();
78
+ }
79
+
80
+ function secureHash(parameters, integritySalt) {
81
+ const values = Object.keys(parameters)
82
+ .filter((name) => name !== 'pp_SecureHash')
83
+ .sort()
84
+ .map((name) => (parameters[name] === null || parameters[name] === undefined ? '' : String(parameters[name])))
85
+ .filter((value) => value !== '');
86
+
87
+ const message = values.length === 0 ? integritySalt : `${integritySalt}&${values.join('&')}`;
88
+
89
+ return createHmac('sha256', integritySalt).update(message, 'utf8').digest('hex');
90
+ }
91
+
92
+ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
93
+
94
+ function escapeHtml(value) {
95
+ return String(value).replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
96
+ }
97
+
98
+ /**
99
+ * Build the signed parameter set for one order.
100
+ *
101
+ * `order` carries `amountMajor` (rupees, as a string), `billReference`
102
+ * (alphanumeric only) and `description`.
103
+ */
104
+ export function buildCheckoutParameters(order) {
105
+ const merchantId = process.env.JAZZCASH_MERCHANT_ID ?? '';
106
+ const password = process.env.JAZZCASH_PASSWORD ?? '';
107
+ const integritySalt = process.env.JAZZCASH_INTEGRITY_SALT ?? '';
108
+ const returnUrl = process.env.JAZZCASH_RETURN_URL ?? '';
109
+
110
+ if (merchantId === '' || password === '' || integritySalt === '' || returnUrl === '') {
111
+ throw new Error('JazzCash credentials are not configured.');
112
+ }
113
+
114
+ const now = new Date();
115
+ const expiry = new Date(now.getTime() + 86_400_000);
116
+
117
+ const parameters = {
118
+ {{PARAMS}}
119
+ pp_MerchantID: merchantId,
120
+ pp_Password: password,
121
+ pp_ReturnURL: returnUrl,
122
+ // Unique per transaction. Replace TRN with your own three-letter merchant
123
+ // prefix, as the guides recommend.
124
+ pp_TxnRefNo: generateReference('TRN', now),
125
+ pp_Amount: toMinorUnits(order.amountMajor),
126
+ pp_BillReference: order.billReference,
127
+ pp_Description: order.description,
128
+ pp_TxnDateTime: txnDateTime(now),
129
+ pp_TxnExpiryDateTime: txnDateTime(expiry),
130
+ };
131
+
132
+ parameters.pp_SecureHash = secureHash(parameters, integritySalt);
133
+
134
+ return parameters;
135
+ }
136
+
137
+ /** Render an auto-submitting HTML form. Every value is HTML-escaped. */
138
+ export function renderCheckoutForm(parameters) {
139
+ const inputs = Object.entries(parameters)
140
+ .map(([name, value]) => ` <input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`)
141
+ .join('\n');
142
+
143
+ return `<!DOCTYPE html>
144
+ <html lang="en">
145
+ <head><meta charset="UTF-8"><title>Redirecting to JazzCash…</title></head>
146
+ <body>
147
+ <p>Redirecting you to the JazzCash secure payment page…</p>
148
+ <form id="jazzcash" method="post" action="${escapeHtml(JAZZCASH_POST_URL)}">
149
+ ${inputs}
150
+ <noscript><button type="submit">Continue</button></noscript>
151
+ </form>
152
+ <script>document.getElementById('jazzcash').submit();</script>
153
+ </body>
154
+ </html>`;
155
+ }
156
+
157
+ // Example wiring with Express:
158
+ //
159
+ // app.post('/checkout', (request, response) => {
160
+ // const parameters = buildCheckoutParameters({
161
+ // amountMajor: '1.00',
162
+ // billReference: 'billref001',
163
+ // description: 'Order 1024',
164
+ // });
165
+ // response.type('html').send(renderCheckoutForm(parameters));
166
+ // });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * JazzCash IPN (Instant Payment Notification) listener — {{FRAMEWORK}}
3
+ *
4
+ * Generated by the JazzCash MCP server ({{CORPUS_VERSION}} guides).
5
+ * Sources: {{DOC_SOURCES}}
6
+ *
7
+ * Register this URL under Integration > Credentials in the merchant portal.
8
+ *
9
+ * CONTRACT
10
+ * - Respond within 60 seconds. JazzCash retries twice on failure or timeout.
11
+ * - Therefore this handler MUST be idempotent: the same notification can
12
+ * legitimately arrive three times.
13
+ * - This URL is public. Anyone can post to it. VERIFY THE HASH FIRST.
14
+ * - In an inbound IPN the payment outcome is in pp_ResponseCode: 121 succeeded,
15
+ * 199/999 failed. (In a Status Inquiry response it is pp_PaymentResponseCode.)
16
+ */
17
+
18
+ import { createHmac, timingSafeEqual } from 'node:crypto';
19
+ import express from 'express';
20
+
21
+ const app = express();
22
+ app.use(express.json());
23
+
24
+ const INTEGRITY_SALT = process.env.JAZZCASH_INTEGRITY_SALT ?? '';
25
+ if (INTEGRITY_SALT === '') {
26
+ throw new Error('JAZZCASH_INTEGRITY_SALT is not configured.');
27
+ }
28
+
29
+ function secureHash(parameters, integritySalt) {
30
+ const values = Object.keys(parameters)
31
+ .filter((name) => name !== 'pp_SecureHash' && name !== 'SecureHash')
32
+ .sort()
33
+ // The gateway sends JSON null in IPN payloads even though merchants are told
34
+ // never to send null. Treat null as empty when hashing.
35
+ .map((name) => (parameters[name] === null || parameters[name] === undefined ? '' : String(parameters[name])))
36
+ .filter((value) => value !== '');
37
+
38
+ const message = values.length === 0 ? integritySalt : `${integritySalt}&${values.join('&')}`;
39
+
40
+ return createHmac('sha256', integritySalt).update(message, 'utf8').digest('hex');
41
+ }
42
+
43
+ function hashMatches(left, right) {
44
+ const a = Buffer.from(String(left).trim().toLowerCase(), 'utf8');
45
+ const b = Buffer.from(String(right).trim().toLowerCase(), 'utf8');
46
+ return a.length === b.length && timingSafeEqual(a, b);
47
+ }
48
+
49
+ function acknowledgement(message) {
50
+ const body = { pp_ResponseCode: '000', pp_ResponseMessage: message };
51
+ body.pp_SecureHash = secureHash(body, INTEGRITY_SALT);
52
+ return body;
53
+ }
54
+
55
+ app.post('{{IPN_PATH}}', (request, response) => {
56
+ const notification = request.body;
57
+
58
+ if (typeof notification !== 'object' || notification === null) {
59
+ response.status(400).json({ pp_ResponseCode: '400', pp_ResponseMessage: 'Malformed body' });
60
+ return;
61
+ }
62
+
63
+ // 1. Verify the hash BEFORE anything else.
64
+ const supplied = notification.pp_SecureHash;
65
+ if (typeof supplied !== 'string' || !hashMatches(secureHash(notification, INTEGRITY_SALT), supplied)) {
66
+ console.error('JazzCash IPN rejected: secure hash did not verify.');
67
+ response.status(400).json({ pp_ResponseCode: '400', pp_ResponseMessage: 'Invalid secure hash' });
68
+ return;
69
+ }
70
+
71
+ const txnRefNo = String(notification.pp_TxnRefNo ?? '');
72
+ const responseCode = String(notification.pp_ResponseCode ?? '');
73
+
74
+ // 2. Acknowledge FIRST, then do the slow work. The 60-second budget is for the
75
+ // HTTP response, not for your order pipeline.
76
+ response.json(acknowledgement('IPN received successfully'));
77
+
78
+ // 3. Process asynchronously and idempotently.
79
+ queueMicrotask(() => {
80
+ void handleNotification(txnRefNo, responseCode, notification).catch((error) => {
81
+ console.error('JazzCash IPN processing failed for', txnRefNo, error);
82
+ });
83
+ });
84
+ });
85
+
86
+ async function handleNotification(txnRefNo, responseCode, notification) {
87
+ // Replace with your own persistence layer.
88
+ // const order = await findOrderByReference(txnRefNo);
89
+ // if (order === null) return; // acknowledge and investigate
90
+ // if (order.isPaid) return; // retry: already handled
91
+ // if (order.amountMinorUnits !== String(notification.pp_Amount)) return; // amount mismatch
92
+
93
+ if (responseCode === '121') {
94
+ // Paid. Mark the order paid.
95
+ // await markOrderPaid(txnRefNo, notification);
96
+ } else if (responseCode === '199' || responseCode === '999') {
97
+ // Failed. Do not release goods.
98
+ // await markOrderFailed(txnRefNo, notification);
99
+ } else {
100
+ // Undocumented code: unresolved, NOT failed. Reconcile with Status Inquiry.
101
+ console.error('JazzCash IPN carried undocumented code', responseCode, 'for', txnRefNo);
102
+ }
103
+ }
104
+
105
+ const port = Number(process.env.PORT ?? 3000);
106
+ app.listen(port, () => {
107
+ console.error(`JazzCash IPN listener on port ${port} at {{IPN_PATH}}`);
108
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * JazzCash — {{FLOW_TITLE}}
3
+ *
4
+ * Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
5
+ * Sources: {{DOC_SOURCES}}
6
+ *
7
+ * SAFETY NOTES
8
+ * - Sandbox and production use the SAME URL ({{ENDPOINT}}). The environment is
9
+ * decided entirely by the credentials. Confirm which are loaded before running.
10
+ * - Moves money: {{MOVES_MONEY}}.
11
+ *
12
+ * Requires Node.js 20 or later (global fetch, AbortSignal.timeout).
13
+ */
14
+
15
+ import { createHmac, randomInt, timingSafeEqual } from 'node:crypto';
16
+
17
+ export const JAZZCASH_ENDPOINT = '{{ENDPOINT}}';
18
+
19
+ const TIMEOUT_MS = 30_000;
20
+ const PAKISTAN_TIME_ZONE = 'Asia/Karachi';
21
+
22
+ // pp_TxnRefNo and pp_RequestID are annotated [20AN] in the guides: at most 20
23
+ // letters and digits.
24
+ const MAX_REFERENCE_LENGTH = 20;
25
+ const REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
26
+
27
+ const pktFormatter = new Intl.DateTimeFormat('en-GB', {
28
+ timeZone: PAKISTAN_TIME_ZONE,
29
+ year: 'numeric',
30
+ month: '2-digit',
31
+ day: '2-digit',
32
+ hour: '2-digit',
33
+ minute: '2-digit',
34
+ second: '2-digit',
35
+ hourCycle: 'h23',
36
+ });
37
+
38
+ /**
39
+ * Format an instant as YYYYMMDDHHMMSS in Pakistan Standard Time.
40
+ *
41
+ * Uses an explicit time zone so the stamp is correct regardless of the host
42
+ * clock. A server in UTC and one in New York must produce the same value.
43
+ */
44
+ export function txnDateTime(date = new Date()) {
45
+ const parts = Object.fromEntries(
46
+ pktFormatter.formatToParts(date).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]),
47
+ );
48
+ return `${parts.year}${parts.month}${parts.day}${parts.hour}${parts.minute}${parts.second}`;
49
+ }
50
+
51
+ /** Expiry stamp, one day after the transaction, as the guides require. */
52
+ export function txnExpiryDateTime(date = new Date()) {
53
+ return txnDateTime(new Date(date.getTime() + 86_400_000));
54
+ }
55
+
56
+ /** Convert rupees to the minor units the gateway expects. Integer maths only. */
57
+ export function toMinorUnits(majorAmount) {
58
+ const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(String(majorAmount).trim());
59
+ if (match === null) {
60
+ throw new Error(`Amount "${majorAmount}" must be a positive value with at most two decimal places.`);
61
+ }
62
+ return (BigInt(match[1]) * 100n + BigInt((match[2] ?? '').padEnd(2, '0'))).toString();
63
+ }
64
+
65
+ /**
66
+ * Reference that stays unique inside a single millisecond.
67
+ *
68
+ * The clock alone is not enough: two orders placed in the same millisecond
69
+ * produce the same reference, and the gateway rejects the second as a duplicate.
70
+ * The random suffix fills whatever the prefix and the timestamp leave of the
71
+ * 20-character budget, drawn from crypto.randomInt rather than Math.random.
72
+ */
73
+ export function generateReference(prefix = 'T', date = new Date()) {
74
+ const stamp = txnDateTime(date);
75
+ const room = MAX_REFERENCE_LENGTH - prefix.length - stamp.length;
76
+
77
+ if (room < 1) {
78
+ throw new Error(`Reference prefix "${prefix}" leaves no room inside the ${MAX_REFERENCE_LENGTH}-character limit.`);
79
+ }
80
+
81
+ let suffix = '';
82
+ for (let index = 0; index < room; index += 1) {
83
+ suffix += REFERENCE_ALPHABET[randomInt(REFERENCE_ALPHABET.length)];
84
+ }
85
+
86
+ return `${prefix}${stamp}${suffix}`;
87
+ }
88
+
89
+ /**
90
+ * Build pp_SecureHash.
91
+ *
92
+ * Sort by parameter NAME ascending, join the non-empty VALUES with "&", prepend
93
+ * the Integrity Salt, then HMAC-SHA256 with the salt as the key.
94
+ *
95
+ * Sorting uses the default comparator, which is code-unit order. Do NOT use
96
+ * localeCompare: it is locale-sensitive and would produce a hash that works on
97
+ * one host and fails on another.
98
+ */
99
+ export function secureHash(parameters, integritySalt) {
100
+ const values = Object.keys(parameters)
101
+ .filter((name) => name !== 'pp_SecureHash' && name !== 'SecureHash')
102
+ .sort()
103
+ .map((name) => (parameters[name] === null || parameters[name] === undefined ? '' : String(parameters[name])))
104
+ .filter((value) => value !== '');
105
+
106
+ const message = values.length === 0 ? integritySalt : `${integritySalt}&${values.join('&')}`;
107
+
108
+ return createHmac('sha256', integritySalt).update(message, 'utf8').digest('hex');
109
+ }
110
+
111
+ /**
112
+ * Compare hashes in constant time, case-insensitively.
113
+ *
114
+ * JazzCash returns uppercase hex; this code produces lowercase. Same digest.
115
+ */
116
+ export function hashMatches(left, right) {
117
+ const a = Buffer.from(String(left).trim().toLowerCase(), 'utf8');
118
+ const b = Buffer.from(String(right).trim().toLowerCase(), 'utf8');
119
+ return a.length === b.length && timingSafeEqual(a, b);
120
+ }
121
+
122
+ function credentials() {
123
+ const merchantId = process.env.JAZZCASH_MERCHANT_ID ?? '';
124
+ const password = process.env.JAZZCASH_PASSWORD ?? '';
125
+ const integritySalt = process.env.JAZZCASH_INTEGRITY_SALT ?? '';
126
+
127
+ if (merchantId === '' || password === '' || integritySalt === '') {
128
+ throw new Error('JazzCash credentials are not configured.');
129
+ }
130
+
131
+ return { merchantId, password, integritySalt };
132
+ }
133
+
134
+ /** Send one request. Never logs the payload: it carries the password. */
135
+ async function post(parameters) {
136
+ const response = await fetch(JAZZCASH_ENDPOINT, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
139
+ body: JSON.stringify(parameters),
140
+ signal: AbortSignal.timeout(TIMEOUT_MS),
141
+ });
142
+
143
+ const text = await response.text();
144
+
145
+ try {
146
+ return JSON.parse(text);
147
+ } catch {
148
+ throw new Error(`JazzCash returned a non-JSON body with HTTP ${response.status}.`);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Execute {{FLOW_ID}}.
154
+ *
155
+ * Every value marked TODO must be supplied by your application, either by
156
+ * editing the literal below or by passing it in `overrides`. pp_Amount is in
157
+ * MINOR units: pass `toMinorUnits('100.00')` rather than a rupee float, so the
158
+ * conversion is exact.
159
+ */
160
+ export async function run(overrides = {}) {
161
+ const { merchantId, password, integritySalt } = credentials();
162
+
163
+ const parameters = {
164
+ {{PARAMS}}
165
+ ...overrides,
166
+ pp_MerchantID: merchantId,
167
+ pp_Password: password,
168
+ {{RUNTIME_OVERRIDES}}
169
+ };
170
+
171
+ // Hash LAST, from exactly the object about to be transmitted.
172
+ parameters.pp_SecureHash = secureHash(parameters, integritySalt);
173
+
174
+ const result = await post(parameters);
175
+
176
+ if (result['{{SUCCESS_FIELD}}'] === '{{SUCCESS_VALUE}}') {
177
+ {{SETTLEMENT_CHECK}} // For Status Inquiry this only means the enquiry worked. Read
178
+ // pp_PaymentResponseCode for the payment outcome: 121 means completed.
179
+ {{SUCCESS_REPORT}}
180
+ } else {
181
+ // An unrecognised code is UNRESOLVED, not failed. Resolve it with the
182
+ // Status Inquiry API before refunding or re-charging.
183
+ console.error('JazzCash call unresolved. Code:', result['{{SUCCESS_FIELD}}']);
184
+ }
185
+
186
+ const responseHash = result['{{RESPONSE_HASH_FIELD}}'];
187
+ if (typeof responseHash === 'string' && responseHash !== '') {
188
+ if (!hashMatches(secureHash(result, integritySalt), responseHash)) {
189
+ // JazzCash does not document which response fields feed this hash, so a
190
+ // mismatch means UNVERIFIED, not proven tampering. Reconcile through
191
+ // Status Inquiry rather than rejecting the payment.
192
+ console.error('JazzCash response hash could not be verified for', result.pp_TxnRefNo);
193
+ }
194
+ }
195
+
196
+ return result;
197
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * JazzCash — MWallet linking form
3
+ *
4
+ * Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
5
+ * Sources: {{DOC_SOURCES}}
6
+ *
7
+ * - Sandbox and production use the SAME URL. The environment is decided by the
8
+ * credentials alone: going live is a credential swap, not a URL change.
9
+ *
10
+ * TWO CORRECTIONS TO THE OFFICIAL SAMPLE, both deliberate:
11
+ *
12
+ * 1. The sample in the guide computes the hash in browser JavaScript with the
13
+ * Integrity Salt embedded in the page. The salt is the HMAC key: publishing
14
+ * it lets anyone forge a valid pp_SecureHash for your merchant account. This
15
+ * module runs server-side and emits only the finished hash.
16
+ *
17
+ * 2. The sample posts to https://pgw.jazzcash.com.pk/..., contradicting the
18
+ * endpoint table and screenshot in the same document. This module uses the
19
+ * endpoint from the table: {{ENDPOINT}}
20
+ *
21
+ * Requires Node.js 20 or later.
22
+ */
23
+
24
+ import { createHmac, randomInt } from 'node:crypto';
25
+
26
+ export const JAZZCASH_LINKING_URL = '{{ENDPOINT}}';
27
+
28
+ const MSISDN_PATTERN = /^03\d{9}$/;
29
+
30
+ // pp_RequestID is annotated [20AN] in the guides: at most 20 letters and digits.
31
+ const MAX_REFERENCE_LENGTH = 20;
32
+ const REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
33
+
34
+ const pktFormatter = new Intl.DateTimeFormat('en-GB', {
35
+ timeZone: 'Asia/Karachi',
36
+ year: 'numeric',
37
+ month: '2-digit',
38
+ day: '2-digit',
39
+ hour: '2-digit',
40
+ minute: '2-digit',
41
+ second: '2-digit',
42
+ hourCycle: 'h23',
43
+ });
44
+
45
+ function txnDateTime(date = new Date()) {
46
+ const parts = Object.fromEntries(
47
+ pktFormatter.formatToParts(date).filter((part) => part.type !== 'literal').map((part) => [part.type, part.value]),
48
+ );
49
+ return `${parts.year}${parts.month}${parts.day}${parts.hour}${parts.minute}${parts.second}`;
50
+ }
51
+
52
+ /**
53
+ * Reference that stays unique inside a single millisecond.
54
+ *
55
+ * The clock alone is not enough: two linking attempts started in the same
56
+ * millisecond produce the same pp_RequestID, and the gateway rejects the second
57
+ * as a duplicate. Drawn from crypto.randomInt rather than Math.random.
58
+ */
59
+ function generateReference(prefix, date = new Date()) {
60
+ const stamp = txnDateTime(date);
61
+ const room = MAX_REFERENCE_LENGTH - prefix.length - stamp.length;
62
+
63
+ if (room < 1) {
64
+ throw new Error(`Reference prefix "${prefix}" leaves no room inside the ${MAX_REFERENCE_LENGTH}-character limit.`);
65
+ }
66
+
67
+ let suffix = '';
68
+ for (let index = 0; index < room; index += 1) {
69
+ suffix += REFERENCE_ALPHABET[randomInt(REFERENCE_ALPHABET.length)];
70
+ }
71
+
72
+ return `${prefix}${stamp}${suffix}`;
73
+ }
74
+
75
+ function secureHash(parameters, integritySalt) {
76
+ const values = Object.keys(parameters)
77
+ .filter((name) => name !== 'pp_SecureHash')
78
+ .sort()
79
+ .map((name) => (parameters[name] === null || parameters[name] === undefined ? '' : String(parameters[name])))
80
+ .filter((value) => value !== '');
81
+
82
+ const message = values.length === 0 ? integritySalt : `${integritySalt}&${values.join('&')}`;
83
+
84
+ return createHmac('sha256', integritySalt).update(message, 'utf8').digest('hex');
85
+ }
86
+
87
+ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
88
+
89
+ function escapeHtml(value) {
90
+ return String(value).replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
91
+ }
92
+
93
+ /** Build the signed linking parameters for one customer wallet number. */
94
+ export function buildLinkingParameters(msisdn) {
95
+ if (!MSISDN_PATTERN.test(String(msisdn))) {
96
+ throw new Error('A Pakistani mobile wallet number in the form 03XXXXXXXXX is required.');
97
+ }
98
+
99
+ const merchantId = process.env.JAZZCASH_MERCHANT_ID ?? '';
100
+ const password = process.env.JAZZCASH_PASSWORD ?? '';
101
+ const integritySalt = process.env.JAZZCASH_INTEGRITY_SALT ?? '';
102
+ const returnUrl = process.env.JAZZCASH_RETURN_URL ?? '';
103
+
104
+ if (merchantId === '' || password === '' || integritySalt === '' || returnUrl === '') {
105
+ throw new Error('JazzCash credentials are not configured.');
106
+ }
107
+
108
+ const now = new Date();
109
+
110
+ const parameters = {
111
+ {{PARAMS}}
112
+ pp_MerchantID: merchantId,
113
+ pp_Password: password,
114
+ pp_MSISDN: String(msisdn),
115
+ // Must be unique for every linking attempt.
116
+ pp_RequestID: generateReference('R', now),
117
+ pp_ReturnURL: returnUrl,
118
+ };
119
+
120
+ parameters.pp_SecureHash = secureHash(parameters, integritySalt);
121
+
122
+ return parameters;
123
+ }
124
+
125
+ /** Render an auto-submitting linking form. */
126
+ export function renderLinkingForm(parameters) {
127
+ const inputs = Object.entries(parameters)
128
+ .map(([name, value]) => ` <input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`)
129
+ .join('\n');
130
+
131
+ return `<!DOCTYPE html>
132
+ <html lang="en">
133
+ <head><meta charset="UTF-8"><title>Link your JazzCash wallet</title></head>
134
+ <body>
135
+ <p>You will be asked for your MPIN on the JazzCash page.</p>
136
+ <form id="link-wallet" method="post" action="${escapeHtml(JAZZCASH_LINKING_URL)}">
137
+ ${inputs}
138
+ <noscript><button type="submit">Continue</button></noscript>
139
+ </form>
140
+ <script>document.getElementById('link-wallet').submit();</script>
141
+ </body>
142
+ </html>`;
143
+ }
144
+
145
+ // On return, JazzCash posts a payment token to your Return URL. Store it against
146
+ // the customer and charge it later with the token payment API. Call the Token
147
+ // Inquiry API before charging: the customer may have unlinked their wallet.