@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.
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/dist/builder/transaction-builder.d.ts +53 -0
- package/dist/builder/transaction-builder.d.ts.map +1 -0
- package/dist/builder/transaction-builder.js +159 -0
- package/dist/builder/transaction-builder.js.map +1 -0
- package/dist/catalogue/default-catalogue.d.ts +18 -0
- package/dist/catalogue/default-catalogue.d.ts.map +1 -0
- package/dist/catalogue/default-catalogue.js +21 -0
- package/dist/catalogue/default-catalogue.js.map +1 -0
- package/dist/catalogue/endpoints.d.ts +22 -0
- package/dist/catalogue/endpoints.d.ts.map +1 -0
- package/dist/catalogue/endpoints.js +24 -0
- package/dist/catalogue/endpoints.js.map +1 -0
- package/dist/catalogue/flows.d.ts +32 -0
- package/dist/catalogue/flows.d.ts.map +1 -0
- package/dist/catalogue/flows.js +64 -0
- package/dist/catalogue/flows.js.map +1 -0
- package/dist/catalogue/hash-options.d.ts +112 -0
- package/dist/catalogue/hash-options.d.ts.map +1 -0
- package/dist/catalogue/hash-options.js +151 -0
- package/dist/catalogue/hash-options.js.map +1 -0
- package/dist/catalogue/types.d.ts +279 -0
- package/dist/catalogue/types.d.ts.map +1 -0
- package/dist/catalogue/types.js +360 -0
- package/dist/catalogue/types.js.map +1 -0
- package/dist/codegen/generate.d.ts +14 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/generate.js +490 -0
- package/dist/codegen/generate.js.map +1 -0
- package/dist/codegen/languages/node.d.ts +3 -0
- package/dist/codegen/languages/node.d.ts.map +1 -0
- package/dist/codegen/languages/node.js +32 -0
- package/dist/codegen/languages/node.js.map +1 -0
- package/dist/codegen/languages/php.d.ts +3 -0
- package/dist/codegen/languages/php.d.ts.map +1 -0
- package/dist/codegen/languages/php.js +27 -0
- package/dist/codegen/languages/php.js.map +1 -0
- package/dist/codegen/languages/python.d.ts +3 -0
- package/dist/codegen/languages/python.d.ts.map +1 -0
- package/dist/codegen/languages/python.js +27 -0
- package/dist/codegen/languages/python.js.map +1 -0
- package/dist/codegen/renderer.d.ts +24 -0
- package/dist/codegen/renderer.d.ts.map +1 -0
- package/dist/codegen/renderer.js +59 -0
- package/dist/codegen/renderer.js.map +1 -0
- package/dist/codegen/types.d.ts +50 -0
- package/dist/codegen/types.d.ts.map +1 -0
- package/dist/codegen/types.js +5 -0
- package/dist/codegen/types.js.map +1 -0
- package/dist/config/credentials.d.ts +64 -0
- package/dist/config/credentials.d.ts.map +1 -0
- package/dist/config/credentials.js +216 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/environment.d.ts +23 -0
- package/dist/config/environment.d.ts.map +1 -0
- package/dist/config/environment.js +54 -0
- package/dist/config/environment.js.map +1 -0
- package/dist/core/dependencies.d.ts +43 -0
- package/dist/core/dependencies.d.ts.map +1 -0
- package/dist/core/dependencies.js +2 -0
- package/dist/core/dependencies.js.map +1 -0
- package/dist/core/registry.d.ts +33 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +13 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/server.d.ts +61 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/core/server.js +90 -0
- package/dist/core/server.js.map +1 -0
- package/dist/dependencies.d.ts +28 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/dependencies.js +2 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/diagnostics/diagnose.d.ts +43 -0
- package/dist/diagnostics/diagnose.d.ts.map +1 -0
- package/dist/diagnostics/diagnose.js +121 -0
- package/dist/diagnostics/diagnose.js.map +1 -0
- package/dist/domain/amount.d.ts +157 -0
- package/dist/domain/amount.d.ts.map +1 -0
- package/dist/domain/amount.js +275 -0
- package/dist/domain/amount.js.map +1 -0
- package/dist/domain/datetime.d.ts +95 -0
- package/dist/domain/datetime.d.ts.map +1 -0
- package/dist/domain/datetime.js +211 -0
- package/dist/domain/datetime.js.map +1 -0
- package/dist/domain/response-codes.d.ts +116 -0
- package/dist/domain/response-codes.d.ts.map +1 -0
- package/dist/domain/response-codes.js +380 -0
- package/dist/domain/response-codes.js.map +1 -0
- package/dist/domain/secure-hash.d.ts +78 -0
- package/dist/domain/secure-hash.d.ts.map +1 -0
- package/dist/domain/secure-hash.js +69 -0
- package/dist/domain/secure-hash.js.map +1 -0
- package/dist/domain/signature/jazzcash.d.ts +56 -0
- package/dist/domain/signature/jazzcash.d.ts.map +1 -0
- package/dist/domain/signature/jazzcash.js +214 -0
- package/dist/domain/signature/jazzcash.js.map +1 -0
- package/dist/domain/signature/registry.d.ts +69 -0
- package/dist/domain/signature/registry.d.ts.map +1 -0
- package/dist/domain/signature/registry.js +92 -0
- package/dist/domain/signature/registry.js.map +1 -0
- package/dist/domain/signature/scheme.d.ts +349 -0
- package/dist/domain/signature/scheme.d.ts.map +1 -0
- package/dist/domain/signature/scheme.js +398 -0
- package/dist/domain/signature/scheme.js.map +1 -0
- package/dist/domain/txn-ref.d.ts +76 -0
- package/dist/domain/txn-ref.d.ts.map +1 -0
- package/dist/domain/txn-ref.js +126 -0
- package/dist/domain/txn-ref.js.map +1 -0
- package/dist/domain/types.d.ts +45 -0
- package/dist/domain/types.d.ts.map +1 -0
- package/dist/domain/types.js +70 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/errors.d.ts +84 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +87 -0
- package/dist/errors.js.map +1 -0
- package/dist/gateway/checkout-form.d.ts +16 -0
- package/dist/gateway/checkout-form.d.ts.map +1 -0
- package/dist/gateway/checkout-form.js +103 -0
- package/dist/gateway/checkout-form.js.map +1 -0
- package/dist/gateway/checkout-output.d.ts +70 -0
- package/dist/gateway/checkout-output.d.ts.map +1 -0
- package/dist/gateway/checkout-output.js +199 -0
- package/dist/gateway/checkout-output.js.map +1 -0
- package/dist/gateway/guards.d.ts +37 -0
- package/dist/gateway/guards.d.ts.map +1 -0
- package/dist/gateway/guards.js +44 -0
- package/dist/gateway/guards.js.map +1 -0
- package/dist/gateway/http-client.d.ts +42 -0
- package/dist/gateway/http-client.d.ts.map +1 -0
- package/dist/gateway/http-client.js +153 -0
- package/dist/gateway/http-client.js.map +1 -0
- package/dist/gateway/rest-client.d.ts +42 -0
- package/dist/gateway/rest-client.d.ts.map +1 -0
- package/dist/gateway/rest-client.js +241 -0
- package/dist/gateway/rest-client.js.map +1 -0
- package/dist/gateway/transport.d.ts +31 -0
- package/dist/gateway/transport.d.ts.map +1 -0
- package/dist/gateway/transport.js +73 -0
- package/dist/gateway/transport.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/install/cli.d.ts +38 -0
- package/dist/install/cli.d.ts.map +1 -0
- package/dist/install/cli.js +274 -0
- package/dist/install/cli.js.map +1 -0
- package/dist/install/harnesses.d.ts +74 -0
- package/dist/install/harnesses.d.ts.map +1 -0
- package/dist/install/harnesses.js +263 -0
- package/dist/install/harnesses.js.map +1 -0
- package/dist/knowledge/corpus.d.ts +15 -0
- package/dist/knowledge/corpus.d.ts.map +1 -0
- package/dist/knowledge/corpus.js +129 -0
- package/dist/knowledge/corpus.js.map +1 -0
- package/dist/knowledge/search.d.ts +29 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +181 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/source.d.ts +60 -0
- package/dist/knowledge/source.d.ts.map +1 -0
- package/dist/knowledge/source.js +91 -0
- package/dist/knowledge/source.js.map +1 -0
- package/dist/knowledge/types.d.ts +40 -0
- package/dist/knowledge/types.d.ts.map +1 -0
- package/dist/knowledge/types.js +19 -0
- package/dist/knowledge/types.js.map +1 -0
- package/dist/paths.d.ts +26 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +106 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompts/integration-walkthrough.d.ts +11 -0
- package/dist/prompts/integration-walkthrough.d.ts.map +1 -0
- package/dist/prompts/integration-walkthrough.js +52 -0
- package/dist/prompts/integration-walkthrough.js.map +1 -0
- package/dist/prompts/register.d.ts +4 -0
- package/dist/prompts/register.d.ts.map +1 -0
- package/dist/prompts/register.js +4 -0
- package/dist/prompts/register.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -0
- package/dist/resources/knowledge-resources.d.ts +5 -0
- package/dist/resources/knowledge-resources.d.ts.map +1 -0
- package/dist/resources/knowledge-resources.js +121 -0
- package/dist/resources/knowledge-resources.js.map +1 -0
- package/dist/resources/register.d.ts +4 -0
- package/dist/resources/register.d.ts.map +1 -0
- package/dist/resources/register.js +8 -0
- package/dist/resources/register.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry/logger.d.ts +31 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/telemetry/logger.js +67 -0
- package/dist/telemetry/logger.js.map +1 -0
- package/dist/telemetry/redaction.d.ts +88 -0
- package/dist/telemetry/redaction.d.ts.map +1 -0
- package/dist/telemetry/redaction.js +238 -0
- package/dist/telemetry/redaction.js.map +1 -0
- package/dist/tools/build-transaction-parameters.d.ts +10 -0
- package/dist/tools/build-transaction-parameters.d.ts.map +1 -0
- package/dist/tools/build-transaction-parameters.js +154 -0
- package/dist/tools/build-transaction-parameters.js.map +1 -0
- package/dist/tools/call-context.d.ts +47 -0
- package/dist/tools/call-context.d.ts.map +1 -0
- package/dist/tools/call-context.js +15 -0
- package/dist/tools/call-context.js.map +1 -0
- package/dist/tools/codegen-tools.d.ts +4 -0
- package/dist/tools/codegen-tools.d.ts.map +1 -0
- package/dist/tools/codegen-tools.js +131 -0
- package/dist/tools/codegen-tools.js.map +1 -0
- package/dist/tools/credential-tools.d.ts +32 -0
- package/dist/tools/credential-tools.d.ts.map +1 -0
- package/dist/tools/credential-tools.js +65 -0
- package/dist/tools/credential-tools.js.map +1 -0
- package/dist/tools/descriptor.d.ts +52 -0
- package/dist/tools/descriptor.d.ts.map +1 -0
- package/dist/tools/descriptor.js +2 -0
- package/dist/tools/descriptor.js.map +1 -0
- package/dist/tools/diagnostic-tools.d.ts +3 -0
- package/dist/tools/diagnostic-tools.d.ts.map +1 -0
- package/dist/tools/diagnostic-tools.js +128 -0
- package/dist/tools/diagnostic-tools.js.map +1 -0
- package/dist/tools/gateway-tools.d.ts +6 -0
- package/dist/tools/gateway-tools.d.ts.map +1 -0
- package/dist/tools/gateway-tools.js +381 -0
- package/dist/tools/gateway-tools.js.map +1 -0
- package/dist/tools/hash-tools.d.ts +5 -0
- package/dist/tools/hash-tools.d.ts.map +1 -0
- package/dist/tools/hash-tools.js +238 -0
- package/dist/tools/hash-tools.js.map +1 -0
- package/dist/tools/knowledge-tools.d.ts +7 -0
- package/dist/tools/knowledge-tools.d.ts.map +1 -0
- package/dist/tools/knowledge-tools.js +247 -0
- package/dist/tools/knowledge-tools.js.map +1 -0
- package/dist/tools/onboarding-checklist.d.ts +3 -0
- package/dist/tools/onboarding-checklist.d.ts.map +1 -0
- package/dist/tools/onboarding-checklist.js +141 -0
- package/dist/tools/onboarding-checklist.js.map +1 -0
- package/dist/tools/register.d.ts +60 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +145 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/tool-kit.d.ts +88 -0
- package/dist/tools/tool-kit.d.ts.map +1 -0
- package/dist/tools/tool-kit.js +297 -0
- package/dist/tools/tool-kit.js.map +1 -0
- package/dist/validation/payload-validator.d.ts +29 -0
- package/dist/validation/payload-validator.d.ts.map +1 -0
- package/dist/validation/payload-validator.js +79 -0
- package/dist/validation/payload-validator.js.map +1 -0
- package/dist/validation/rules/amount.d.ts +3 -0
- package/dist/validation/rules/amount.d.ts.map +1 -0
- package/dist/validation/rules/amount.js +77 -0
- package/dist/validation/rules/amount.js.map +1 -0
- package/dist/validation/rules/bill-reference.d.ts +10 -0
- package/dist/validation/rules/bill-reference.d.ts.map +1 -0
- package/dist/validation/rules/bill-reference.js +34 -0
- package/dist/validation/rules/bill-reference.js.map +1 -0
- package/dist/validation/rules/cnic.d.ts +3 -0
- package/dist/validation/rules/cnic.d.ts.map +1 -0
- package/dist/validation/rules/cnic.js +42 -0
- package/dist/validation/rules/cnic.js.map +1 -0
- package/dist/validation/rules/constants.d.ts +9 -0
- package/dist/validation/rules/constants.d.ts.map +1 -0
- package/dist/validation/rules/constants.js +28 -0
- package/dist/validation/rules/constants.js.map +1 -0
- package/dist/validation/rules/constraints.d.ts +3 -0
- package/dist/validation/rules/constraints.d.ts.map +1 -0
- package/dist/validation/rules/constraints.js +113 -0
- package/dist/validation/rules/constraints.js.map +1 -0
- package/dist/validation/rules/datetime.d.ts +3 -0
- package/dist/validation/rules/datetime.d.ts.map +1 -0
- package/dist/validation/rules/datetime.js +49 -0
- package/dist/validation/rules/datetime.js.map +1 -0
- package/dist/validation/rules/empty-string.d.ts +13 -0
- package/dist/validation/rules/empty-string.d.ts.map +1 -0
- package/dist/validation/rules/empty-string.js +32 -0
- package/dist/validation/rules/empty-string.js.map +1 -0
- package/dist/validation/rules/expiry.d.ts +3 -0
- package/dist/validation/rules/expiry.d.ts.map +1 -0
- package/dist/validation/rules/expiry.js +52 -0
- package/dist/validation/rules/expiry.js.map +1 -0
- package/dist/validation/rules/msisdn.d.ts +3 -0
- package/dist/validation/rules/msisdn.d.ts.map +1 -0
- package/dist/validation/rules/msisdn.js +45 -0
- package/dist/validation/rules/msisdn.js.map +1 -0
- package/dist/validation/rules/presence.d.ts +3 -0
- package/dist/validation/rules/presence.d.ts.map +1 -0
- package/dist/validation/rules/presence.js +90 -0
- package/dist/validation/rules/presence.js.map +1 -0
- package/dist/validation/rules/return-url.d.ts +14 -0
- package/dist/validation/rules/return-url.d.ts.map +1 -0
- package/dist/validation/rules/return-url.js +58 -0
- package/dist/validation/rules/return-url.js.map +1 -0
- package/dist/validation/rules/tool-argument.d.ts +16 -0
- package/dist/validation/rules/tool-argument.d.ts.map +1 -0
- package/dist/validation/rules/tool-argument.js +38 -0
- package/dist/validation/rules/tool-argument.js.map +1 -0
- package/dist/validation/rules/txn-ref.d.ts +3 -0
- package/dist/validation/rules/txn-ref.d.ts.map +1 -0
- package/dist/validation/rules/txn-ref.js +47 -0
- package/dist/validation/rules/txn-ref.js.map +1 -0
- package/dist/validation/rules/unknown-fields.d.ts +10 -0
- package/dist/validation/rules/unknown-fields.d.ts.map +1 -0
- package/dist/validation/rules/unknown-fields.js +29 -0
- package/dist/validation/rules/unknown-fields.js.map +1 -0
- package/dist/validation/types.d.ts +36 -0
- package/dist/validation/types.d.ts.map +1 -0
- package/dist/validation/types.js +12 -0
- package/dist/validation/types.js.map +1 -0
- package/dist/version.d.ts +23 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/knowledge/diagnostics/symptoms.json +321 -0
- package/knowledge/docs/bnpl-guide.md +77 -0
- package/knowledge/docs/card-page-redirection.md +67 -0
- package/knowledge/docs/card-refund.md +91 -0
- package/knowledge/docs/hmac-sha256-calculation.md +72 -0
- package/knowledge/docs/index.json +189 -0
- package/knowledge/docs/ipn-implementation.md +89 -0
- package/knowledge/docs/mwallet-recurring.md +117 -0
- package/knowledge/docs/mwallet-refund.md +65 -0
- package/knowledge/docs/mwallet-rest-v1-1.md +126 -0
- package/knowledge/docs/mwallet-rest-v2-0-cnic.md +113 -0
- package/knowledge/docs/portal-guide.md +45 -0
- package/knowledge/docs/shopify-guide.md +74 -0
- package/knowledge/docs/status-inquiry-2025-26.md +32 -0
- package/knowledge/docs/status-inquiry.md +111 -0
- package/knowledge/docs/wordpress-guide.md +49 -0
- package/knowledge/flow.schema.json +390 -0
- package/knowledge/flows/bnpl-page-redirection-v1.1.json +211 -0
- package/knowledge/flows/card-page-redirection-v1.1.json +211 -0
- package/knowledge/flows/mwallet-rest-v1.1.json +187 -0
- package/knowledge/flows/mwallet-rest-v2.0-cnic.json +197 -0
- package/knowledge/flows/mwallet-token-pay-v4.0.json +130 -0
- package/knowledge/flows/refund-bnpl.json +105 -0
- package/knowledge/flows/refund-card-v1.1.json +112 -0
- package/knowledge/flows/refund-card-v2.0.json +121 -0
- package/knowledge/flows/refund-mwallet-v1.1.json +106 -0
- package/knowledge/flows/status-inquiry-v1.1.json +102 -0
- package/knowledge/flows/status-inquiry-v2.0.json +103 -0
- package/knowledge/flows/token-delete-v1.json +85 -0
- package/knowledge/flows/token-inquiry-v1.json +85 -0
- package/knowledge/flows/wallet-linking-portal.json +103 -0
- package/knowledge/templates/node/checkout-redirect.tmpl +166 -0
- package/knowledge/templates/node/ipn-handler.tmpl +108 -0
- package/knowledge/templates/node/rest-client.tmpl +197 -0
- package/knowledge/templates/node/wallet-linking.tmpl +147 -0
- package/knowledge/templates/php/checkout-redirect.tmpl +149 -0
- package/knowledge/templates/php/ipn-handler.tmpl +105 -0
- package/knowledge/templates/php/rest-client.tmpl +191 -0
- package/knowledge/templates/php/wallet-linking.tmpl +137 -0
- package/knowledge/templates/python/checkout-redirect.tmpl +148 -0
- package/knowledge/templates/python/ipn-handler.tmpl +110 -0
- package/knowledge/templates/python/rest-client.tmpl +179 -0
- package/knowledge/templates/python/wallet-linking.tmpl +133 -0
- package/package.json +84 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* JazzCash — {{FLOW_TITLE}}
|
|
4
|
+
*
|
|
5
|
+
* Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
|
|
6
|
+
* Sources: {{DOC_SOURCES}}
|
|
7
|
+
*
|
|
8
|
+
* SAFETY NOTES
|
|
9
|
+
* - Sandbox and production use the SAME URL. The environment is decided entirely
|
|
10
|
+
* by the credentials below. Going live means swapping credentials.
|
|
11
|
+
* - The Integrity Salt is the HMAC key. It must never reach the browser.
|
|
12
|
+
* - pp_ReturnURL must be byte-identical to the URL registered in the portal.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare(strict_types=1);
|
|
16
|
+
|
|
17
|
+
date_default_timezone_set('Asia/Karachi');
|
|
18
|
+
|
|
19
|
+
// --- Credentials -------------------------------------------------------------
|
|
20
|
+
// Read from the environment. Never hard-code these and never commit them.
|
|
21
|
+
$merchantId = getenv('JAZZCASH_MERCHANT_ID') ?: '';
|
|
22
|
+
$password = getenv('JAZZCASH_PASSWORD') ?: '';
|
|
23
|
+
$integritySalt = getenv('JAZZCASH_INTEGRITY_SALT') ?: '';
|
|
24
|
+
$returnUrl = getenv('JAZZCASH_RETURN_URL') ?: '';
|
|
25
|
+
|
|
26
|
+
if ($merchantId === '' || $password === '' || $integritySalt === '' || $returnUrl === '') {
|
|
27
|
+
http_response_code(500);
|
|
28
|
+
exit('JazzCash credentials are not configured.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$postUrl = '{{ENDPOINT}}';
|
|
32
|
+
|
|
33
|
+
// pp_TxnRefNo is annotated [20AN] in the guides: at most 20 letters and digits.
|
|
34
|
+
const JAZZCASH_MAX_REFERENCE_LENGTH = 20;
|
|
35
|
+
const JAZZCASH_REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reference that stays unique inside a single millisecond.
|
|
39
|
+
*
|
|
40
|
+
* The clock alone is not enough: two orders placed in the same millisecond
|
|
41
|
+
* produce the same reference, and the gateway rejects the second as a duplicate.
|
|
42
|
+
* The random suffix fills whatever the prefix and the timestamp leave of the
|
|
43
|
+
* 20-character budget. random_int() is the cryptographic generator; rand() and
|
|
44
|
+
* mt_rand() are not.
|
|
45
|
+
*/
|
|
46
|
+
function jazzcash_reference(string $prefix): string
|
|
47
|
+
{
|
|
48
|
+
$stamp = date('YmdHis');
|
|
49
|
+
$room = JAZZCASH_MAX_REFERENCE_LENGTH - strlen($prefix) - strlen($stamp);
|
|
50
|
+
|
|
51
|
+
if ($room < 1) {
|
|
52
|
+
throw new RuntimeException(
|
|
53
|
+
'Reference prefix "' . $prefix . '" leaves no room inside the '
|
|
54
|
+
. JAZZCASH_MAX_REFERENCE_LENGTH . '-character limit.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$alphabet = JAZZCASH_REFERENCE_ALPHABET;
|
|
59
|
+
$suffix = '';
|
|
60
|
+
for ($index = 0; $index < $room; $index++) {
|
|
61
|
+
$suffix .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return $prefix . $stamp . $suffix;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Order data --------------------------------------------------------------
|
|
68
|
+
// Replace these with your real order. $amountMajor is in rupees.
|
|
69
|
+
$amountMajor = '1.00';
|
|
70
|
+
$billReference = 'billref001';
|
|
71
|
+
$description = 'Test transaction description';
|
|
72
|
+
|
|
73
|
+
// pp_Amount is in MINOR units: the gateway reads the last two digits as decimals.
|
|
74
|
+
// bcmul keeps this exact; never use floating point for money.
|
|
75
|
+
$amountMinor = bcmul($amountMajor, '100', 0);
|
|
76
|
+
|
|
77
|
+
// pp_TxnRefNo must be unique for every transaction. Replace TRN with your own
|
|
78
|
+
// three-letter merchant prefix, as the guides recommend.
|
|
79
|
+
$txnRefNo = jazzcash_reference('TRN');
|
|
80
|
+
|
|
81
|
+
$params = [
|
|
82
|
+
{{PARAMS}}
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Overlay the runtime values.
|
|
86
|
+
$params['pp_MerchantID'] = $merchantId;
|
|
87
|
+
$params['pp_Password'] = $password;
|
|
88
|
+
$params['pp_ReturnURL'] = $returnUrl;
|
|
89
|
+
$params['pp_TxnRefNo'] = $txnRefNo;
|
|
90
|
+
$params['pp_Amount'] = $amountMinor;
|
|
91
|
+
$params['pp_BillReference'] = $billReference;
|
|
92
|
+
$params['pp_Description'] = $description;
|
|
93
|
+
$params['pp_TxnDateTime'] = date('YmdHis');
|
|
94
|
+
$params['pp_TxnExpiryDateTime'] = date('YmdHis', strtotime('+1 day'));
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build pp_SecureHash.
|
|
98
|
+
*
|
|
99
|
+
* Sort by parameter NAME ascending ASCII, join the non-empty VALUES with "&",
|
|
100
|
+
* prepend the Integrity Salt, then HMAC-SHA256 with the salt as the key.
|
|
101
|
+
*
|
|
102
|
+
* Two deliberate differences from the sample in the official guide, both bug fixes:
|
|
103
|
+
* 1. SORT_STRING, so ordering is a plain string comparison as the spec requires.
|
|
104
|
+
* Plain ksort() uses SORT_REGULAR, which is not guaranteed to be one.
|
|
105
|
+
* 2. Strict emptiness test. The guide uses `$value != ""`, which is FALSE for
|
|
106
|
+
* integer 0 in PHP and silently drops that field from the hash.
|
|
107
|
+
*/
|
|
108
|
+
function jazzcash_secure_hash(array $params, string $integritySalt): string
|
|
109
|
+
{
|
|
110
|
+
unset($params['pp_SecureHash']);
|
|
111
|
+
ksort($params, SORT_STRING);
|
|
112
|
+
|
|
113
|
+
$values = [];
|
|
114
|
+
foreach ($params as $value) {
|
|
115
|
+
if ($value !== null && (string) $value !== '') {
|
|
116
|
+
$values[] = (string) $value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
$message = $integritySalt . ($values === [] ? '' : '&' . implode('&', $values));
|
|
121
|
+
|
|
122
|
+
return hash_hmac('sha256', $message, $integritySalt);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
$params['pp_SecureHash'] = jazzcash_secure_hash($params, $integritySalt);
|
|
126
|
+
?>
|
|
127
|
+
<!DOCTYPE html>
|
|
128
|
+
<html lang="en">
|
|
129
|
+
<head>
|
|
130
|
+
<meta charset="UTF-8">
|
|
131
|
+
<title>Checkout | JazzCash</title>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<h1>{{FLOW_TITLE}}</h1>
|
|
135
|
+
<p>Transaction reference: <strong><?= htmlspecialchars($txnRefNo, ENT_QUOTES, 'UTF-8') ?></strong></p>
|
|
136
|
+
<p>Amount: <strong>PKR <?= htmlspecialchars($amountMajor, ENT_QUOTES, 'UTF-8') ?></strong></p>
|
|
137
|
+
|
|
138
|
+
<form method="post" action="<?= htmlspecialchars($postUrl, ENT_QUOTES, 'UTF-8') ?>">
|
|
139
|
+
<?php foreach ($params as $name => $value): ?>
|
|
140
|
+
<input type="hidden"
|
|
141
|
+
name="<?= htmlspecialchars((string) $name, ENT_QUOTES, 'UTF-8') ?>"
|
|
142
|
+
value="<?= htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8') ?>">
|
|
143
|
+
<?php endforeach; ?>
|
|
144
|
+
<button type="submit">Proceed to checkout</button>
|
|
145
|
+
</form>
|
|
146
|
+
|
|
147
|
+
<p><small>You will be redirected to the JazzCash secure payment page.</small></p>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* JazzCash IPN (Instant Payment Notification) listener
|
|
4
|
+
*
|
|
5
|
+
* Generated by the JazzCash MCP server ({{CORPUS_VERSION}} guides).
|
|
6
|
+
* Sources: {{DOC_SOURCES}}
|
|
7
|
+
*
|
|
8
|
+
* Register this URL under Integration > Credentials in the merchant portal.
|
|
9
|
+
* Framework: {{FRAMEWORK}}
|
|
10
|
+
*
|
|
11
|
+
* CONTRACT
|
|
12
|
+
* - Respond within 60 seconds. JazzCash retries twice on failure or timeout.
|
|
13
|
+
* - Therefore this handler MUST be idempotent: the same notification can
|
|
14
|
+
* legitimately arrive three times.
|
|
15
|
+
* - This URL is public. Anyone can post to it. VERIFY THE HASH FIRST.
|
|
16
|
+
* - In an inbound IPN the payment outcome is in pp_ResponseCode: 121 succeeded,
|
|
17
|
+
* 199/999 failed. (In a Status Inquiry response it is pp_PaymentResponseCode.)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
declare(strict_types=1);
|
|
21
|
+
|
|
22
|
+
date_default_timezone_set('Asia/Karachi');
|
|
23
|
+
|
|
24
|
+
header('Content-Type: application/json');
|
|
25
|
+
|
|
26
|
+
$integritySalt = getenv('JAZZCASH_INTEGRITY_SALT') ?: '';
|
|
27
|
+
if ($integritySalt === '') {
|
|
28
|
+
http_response_code(500);
|
|
29
|
+
exit(json_encode(['pp_ResponseCode' => '500', 'pp_ResponseMessage' => 'Not configured']));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function jazzcash_secure_hash(array $params, string $integritySalt): string
|
|
33
|
+
{
|
|
34
|
+
unset($params['pp_SecureHash'], $params['SecureHash']);
|
|
35
|
+
ksort($params, SORT_STRING);
|
|
36
|
+
|
|
37
|
+
$values = [];
|
|
38
|
+
foreach ($params as $value) {
|
|
39
|
+
// The gateway sends JSON null in IPN payloads even though merchants are
|
|
40
|
+
// told never to send null. Treat null as empty when hashing.
|
|
41
|
+
if ($value !== null && (string) $value !== '') {
|
|
42
|
+
$values[] = (string) $value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$message = $integritySalt . ($values === [] ? '' : '&' . implode('&', $values));
|
|
47
|
+
|
|
48
|
+
return hash_hmac('sha256', $message, $integritySalt);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function jazzcash_ack(string $message): string
|
|
52
|
+
{
|
|
53
|
+
$body = ['pp_ResponseCode' => '000', 'pp_ResponseMessage' => $message];
|
|
54
|
+
// Acknowledge with our own hash over the acknowledgement fields.
|
|
55
|
+
$body['pp_SecureHash'] = jazzcash_secure_hash($body, getenv('JAZZCASH_INTEGRITY_SALT') ?: '');
|
|
56
|
+
|
|
57
|
+
return json_encode($body, JSON_UNESCAPED_SLASHES);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$raw = file_get_contents('php://input') ?: '';
|
|
61
|
+
$notification = json_decode($raw, true);
|
|
62
|
+
|
|
63
|
+
if (!is_array($notification)) {
|
|
64
|
+
http_response_code(400);
|
|
65
|
+
exit(json_encode(['pp_ResponseCode' => '400', 'pp_ResponseMessage' => 'Malformed body']));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 1. Verify the hash BEFORE anything else.
|
|
69
|
+
$supplied = (string) ($notification['pp_SecureHash'] ?? '');
|
|
70
|
+
$recomputed = jazzcash_secure_hash($notification, $integritySalt);
|
|
71
|
+
|
|
72
|
+
if ($supplied === '' || !hash_equals(strtolower($recomputed), strtolower(trim($supplied)))) {
|
|
73
|
+
error_log('JazzCash IPN rejected: secure hash did not verify.');
|
|
74
|
+
http_response_code(400);
|
|
75
|
+
exit(json_encode(['pp_ResponseCode' => '400', 'pp_ResponseMessage' => 'Invalid secure hash']));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$txnRefNo = (string) ($notification['pp_TxnRefNo'] ?? '');
|
|
79
|
+
$responseCode = (string) ($notification['pp_ResponseCode'] ?? '');
|
|
80
|
+
|
|
81
|
+
// 2. Look up the order. Replace with your own persistence layer.
|
|
82
|
+
// $order = find_order_by_reference($txnRefNo);
|
|
83
|
+
// if ($order === null) { ... acknowledge and investigate ... }
|
|
84
|
+
|
|
85
|
+
// 3. Idempotency: a retry must not fulfil the order twice.
|
|
86
|
+
// if ($order->isPaid()) { echo jazzcash_ack('IPN received successfully'); exit; }
|
|
87
|
+
|
|
88
|
+
// 4. Confirm the amount matches what you recorded, so a replayed or altered
|
|
89
|
+
// notification cannot mark a large order paid from a small transaction.
|
|
90
|
+
// if ((string) $order->amountMinorUnits !== (string) ($notification['pp_Amount'] ?? '')) { ... }
|
|
91
|
+
|
|
92
|
+
// 5. Interpret the outcome.
|
|
93
|
+
if ($responseCode === '121') {
|
|
94
|
+
// Paid. Mark the order paid, then do slow work asynchronously.
|
|
95
|
+
// mark_order_paid($txnRefNo, $notification);
|
|
96
|
+
} elseif ($responseCode === '199' || $responseCode === '999') {
|
|
97
|
+
// Failed. Do not release goods.
|
|
98
|
+
// mark_order_failed($txnRefNo, $notification);
|
|
99
|
+
} else {
|
|
100
|
+
// Undocumented code: unresolved, NOT failed. Reconcile with Status Inquiry.
|
|
101
|
+
error_log('JazzCash IPN carried undocumented code ' . $responseCode . ' for ' . $txnRefNo);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 6. Acknowledge well inside the 60-second window.
|
|
105
|
+
echo jazzcash_ack('IPN received successfully');
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* JazzCash — {{FLOW_TITLE}}
|
|
4
|
+
*
|
|
5
|
+
* Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
|
|
6
|
+
* Sources: {{DOC_SOURCES}}
|
|
7
|
+
*
|
|
8
|
+
* SAFETY NOTES
|
|
9
|
+
* - Sandbox and production use the SAME URL ({{ENDPOINT}}). The environment is
|
|
10
|
+
* decided entirely by the credentials. Confirm which are loaded before running.
|
|
11
|
+
* - Moves money: {{MOVES_MONEY}}.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare(strict_types=1);
|
|
15
|
+
|
|
16
|
+
date_default_timezone_set('Asia/Karachi');
|
|
17
|
+
|
|
18
|
+
const JAZZCASH_ENDPOINT = '{{ENDPOINT}}';
|
|
19
|
+
const JAZZCASH_TIMEOUT_SECS = 30;
|
|
20
|
+
|
|
21
|
+
// pp_TxnRefNo and pp_RequestID are annotated [20AN] in the guides: at most 20
|
|
22
|
+
// letters and digits.
|
|
23
|
+
const JAZZCASH_MAX_REFERENCE_LENGTH = 20;
|
|
24
|
+
const JAZZCASH_REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reference that stays unique inside a single millisecond.
|
|
28
|
+
*
|
|
29
|
+
* The clock alone is not enough: two orders placed in the same millisecond
|
|
30
|
+
* produce the same reference, and the gateway rejects the second as a duplicate.
|
|
31
|
+
* The random suffix fills whatever the prefix and the timestamp leave of the
|
|
32
|
+
* 20-character budget. random_int() is the cryptographic generator; rand() and
|
|
33
|
+
* mt_rand() are not.
|
|
34
|
+
*/
|
|
35
|
+
function jazzcash_reference(string $prefix): string
|
|
36
|
+
{
|
|
37
|
+
$stamp = date('YmdHis');
|
|
38
|
+
$room = JAZZCASH_MAX_REFERENCE_LENGTH - strlen($prefix) - strlen($stamp);
|
|
39
|
+
|
|
40
|
+
if ($room < 1) {
|
|
41
|
+
throw new RuntimeException(
|
|
42
|
+
'Reference prefix "' . $prefix . '" leaves no room inside the '
|
|
43
|
+
. JAZZCASH_MAX_REFERENCE_LENGTH . '-character limit.'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$alphabet = JAZZCASH_REFERENCE_ALPHABET;
|
|
48
|
+
$suffix = '';
|
|
49
|
+
for ($index = 0; $index < $room; $index++) {
|
|
50
|
+
$suffix .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return $prefix . $stamp . $suffix;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert rupees to the minor units pp_Amount carries.
|
|
58
|
+
*
|
|
59
|
+
* bcmul keeps this exact. Never use floating point for money: 100.50 * 100 is
|
|
60
|
+
* 10049.999... in binary floating point, and the gateway would take PKR 100.49.
|
|
61
|
+
*/
|
|
62
|
+
function jazzcash_to_minor_units(string $majorAmount): string
|
|
63
|
+
{
|
|
64
|
+
if (preg_match('/^\d+(\.\d{1,2})?$/', trim($majorAmount)) !== 1) {
|
|
65
|
+
throw new RuntimeException(
|
|
66
|
+
'Amount "' . $majorAmount . '" must be a positive value with at most two decimal places.'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return bcmul(trim($majorAmount), '100', 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build pp_SecureHash: sort by name, join non-empty values with "&",
|
|
75
|
+
* prepend the Integrity Salt, HMAC-SHA256 keyed by the salt.
|
|
76
|
+
*/
|
|
77
|
+
function jazzcash_secure_hash(array $params, string $integritySalt): string
|
|
78
|
+
{
|
|
79
|
+
unset($params['pp_SecureHash'], $params['SecureHash']);
|
|
80
|
+
ksort($params, SORT_STRING);
|
|
81
|
+
|
|
82
|
+
$values = [];
|
|
83
|
+
foreach ($params as $value) {
|
|
84
|
+
if ($value !== null && (string) $value !== '') {
|
|
85
|
+
$values[] = (string) $value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$message = $integritySalt . ($values === [] ? '' : '&' . implode('&', $values));
|
|
90
|
+
|
|
91
|
+
return hash_hmac('sha256', $message, $integritySalt);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Verify a hash in constant time and case-insensitively.
|
|
96
|
+
*
|
|
97
|
+
* JazzCash returns uppercase hex while hash_hmac produces lowercase; both denote
|
|
98
|
+
* the same digest.
|
|
99
|
+
*/
|
|
100
|
+
function jazzcash_hash_matches(string $expected, string $actual): bool
|
|
101
|
+
{
|
|
102
|
+
return hash_equals(strtolower(trim($expected)), strtolower(trim($actual)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Send one request and return the decoded response.
|
|
107
|
+
*
|
|
108
|
+
* @throws RuntimeException on transport failure or an unparseable body.
|
|
109
|
+
*/
|
|
110
|
+
function jazzcash_post(array $params): array
|
|
111
|
+
{
|
|
112
|
+
$body = json_encode($params, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
|
113
|
+
|
|
114
|
+
$handle = curl_init(JAZZCASH_ENDPOINT);
|
|
115
|
+
curl_setopt_array($handle, [
|
|
116
|
+
CURLOPT_POST => true,
|
|
117
|
+
CURLOPT_POSTFIELDS => $body,
|
|
118
|
+
CURLOPT_RETURNTRANSFER => true,
|
|
119
|
+
CURLOPT_TIMEOUT => JAZZCASH_TIMEOUT_SECS,
|
|
120
|
+
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
$raw = curl_exec($handle);
|
|
124
|
+
$error = curl_error($handle);
|
|
125
|
+
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
|
|
126
|
+
curl_close($handle);
|
|
127
|
+
|
|
128
|
+
if ($raw === false) {
|
|
129
|
+
// Never assume a transport failure means the payment did not happen.
|
|
130
|
+
// Resolve it with the Status Inquiry API before retrying.
|
|
131
|
+
throw new RuntimeException('JazzCash request failed: ' . $error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$decoded = json_decode((string) $raw, true);
|
|
135
|
+
if (!is_array($decoded)) {
|
|
136
|
+
throw new RuntimeException('JazzCash returned a non-JSON body with HTTP ' . $status);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return $decoded;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Credentials -------------------------------------------------------------
|
|
143
|
+
$merchantId = getenv('JAZZCASH_MERCHANT_ID') ?: '';
|
|
144
|
+
$password = getenv('JAZZCASH_PASSWORD') ?: '';
|
|
145
|
+
$integritySalt = getenv('JAZZCASH_INTEGRITY_SALT') ?: '';
|
|
146
|
+
|
|
147
|
+
if ($merchantId === '' || $password === '' || $integritySalt === '') {
|
|
148
|
+
throw new RuntimeException('JazzCash credentials are not configured.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Request -----------------------------------------------------------------
|
|
152
|
+
// Every value marked TODO must be supplied by your application. pp_Amount is in
|
|
153
|
+
// MINOR units: pass your rupee total through jazzcash_to_minor_units('100.00')
|
|
154
|
+
// rather than multiplying a float, so the conversion is exact.
|
|
155
|
+
$params = [
|
|
156
|
+
{{PARAMS}}
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
$params['pp_MerchantID'] = $merchantId;
|
|
160
|
+
$params['pp_Password'] = $password;
|
|
161
|
+
{{RUNTIME_OVERRIDES}}
|
|
162
|
+
|
|
163
|
+
$params['pp_SecureHash'] = jazzcash_secure_hash($params, $integritySalt);
|
|
164
|
+
|
|
165
|
+
$response = jazzcash_post($params);
|
|
166
|
+
|
|
167
|
+
// --- Response ----------------------------------------------------------------
|
|
168
|
+
$responseCode = $response['{{SUCCESS_FIELD}}'] ?? '';
|
|
169
|
+
|
|
170
|
+
if ($responseCode === '{{SUCCESS_VALUE}}') {
|
|
171
|
+
{{SETTLEMENT_CHECK}} // The API call succeeded. For Status Inquiry this says nothing about the
|
|
172
|
+
// payment: read pp_PaymentResponseCode, where 121 means completed.
|
|
173
|
+
{{SUCCESS_REPORT}}
|
|
174
|
+
} else {
|
|
175
|
+
// Do NOT treat an unrecognised code as a definite failure. Resolve it with
|
|
176
|
+
// the Status Inquiry API before refunding or re-charging.
|
|
177
|
+
echo "Unresolved or failed. Code: " . $responseCode . PHP_EOL;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Verify the response hash where one is returned. Note this flow returns it in
|
|
181
|
+
// {{RESPONSE_HASH_FIELD}}; card and BNPL refunds use the unprefixed spelling.
|
|
182
|
+
$responseHash = $response['{{RESPONSE_HASH_FIELD}}'] ?? '';
|
|
183
|
+
if ($responseHash !== '') {
|
|
184
|
+
$recomputed = jazzcash_secure_hash($response, $integritySalt);
|
|
185
|
+
if (!jazzcash_hash_matches($recomputed, $responseHash)) {
|
|
186
|
+
// JazzCash does not document which response fields feed this hash, so a
|
|
187
|
+
// mismatch here means UNVERIFIED, not proven tampering. Log it and
|
|
188
|
+
// reconcile through Status Inquiry rather than rejecting the payment.
|
|
189
|
+
error_log('JazzCash response hash could not be verified for ' . ($response['pp_TxnRefNo'] ?? 'unknown'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* JazzCash — MWallet linking form
|
|
4
|
+
*
|
|
5
|
+
* Generated by the JazzCash MCP server from {{FLOW_ID}} ({{CORPUS_VERSION}} guides).
|
|
6
|
+
* Sources: {{DOC_SOURCES}}
|
|
7
|
+
*
|
|
8
|
+
* - Sandbox and production use the SAME URL. The environment is decided by the
|
|
9
|
+
* credentials alone: going live is a credential swap, not a URL change.
|
|
10
|
+
*
|
|
11
|
+
* TWO CORRECTIONS TO THE OFFICIAL SAMPLE, both deliberate:
|
|
12
|
+
*
|
|
13
|
+
* 1. The sample in the guide computes the hash in browser JavaScript with
|
|
14
|
+
* `const INTEGRITY_SALT = "..."` in the page source. The Integrity Salt is
|
|
15
|
+
* the HMAC key: publishing it lets anyone forge a valid pp_SecureHash for
|
|
16
|
+
* your merchant account. This file computes the hash server-side and renders
|
|
17
|
+
* only the finished hash.
|
|
18
|
+
*
|
|
19
|
+
* 2. The sample posts to https://pgw.jazzcash.com.pk/..., contradicting the
|
|
20
|
+
* endpoint table and screenshot in the same document. This file uses the
|
|
21
|
+
* endpoint from the table: {{ENDPOINT}}
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
declare(strict_types=1);
|
|
25
|
+
|
|
26
|
+
date_default_timezone_set('Asia/Karachi');
|
|
27
|
+
|
|
28
|
+
$merchantId = getenv('JAZZCASH_MERCHANT_ID') ?: '';
|
|
29
|
+
$password = getenv('JAZZCASH_PASSWORD') ?: '';
|
|
30
|
+
$integritySalt = getenv('JAZZCASH_INTEGRITY_SALT') ?: '';
|
|
31
|
+
$returnUrl = getenv('JAZZCASH_RETURN_URL') ?: '';
|
|
32
|
+
|
|
33
|
+
if ($merchantId === '' || $password === '' || $integritySalt === '' || $returnUrl === '') {
|
|
34
|
+
http_response_code(500);
|
|
35
|
+
exit('JazzCash credentials are not configured.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$postUrl = '{{ENDPOINT}}';
|
|
39
|
+
|
|
40
|
+
// The wallet number to link. Supply this from your own session or form.
|
|
41
|
+
$msisdn = $_POST['msisdn'] ?? '';
|
|
42
|
+
if (!preg_match('/^03[0-9]{9}$/', (string) $msisdn)) {
|
|
43
|
+
http_response_code(400);
|
|
44
|
+
exit('A Pakistani mobile wallet number in the form 03XXXXXXXXX is required.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// pp_RequestID is annotated [20AN] in the guides: at most 20 letters and digits.
|
|
48
|
+
const JAZZCASH_MAX_REFERENCE_LENGTH = 20;
|
|
49
|
+
const JAZZCASH_REFERENCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reference that stays unique inside a single millisecond.
|
|
53
|
+
*
|
|
54
|
+
* The clock alone is not enough: two linking attempts started in the same
|
|
55
|
+
* millisecond produce the same pp_RequestID, and the gateway rejects the second
|
|
56
|
+
* as a duplicate. random_int() is the cryptographic generator; rand() and
|
|
57
|
+
* mt_rand() are not.
|
|
58
|
+
*/
|
|
59
|
+
function jazzcash_reference(string $prefix): string
|
|
60
|
+
{
|
|
61
|
+
$stamp = date('YmdHis');
|
|
62
|
+
$room = JAZZCASH_MAX_REFERENCE_LENGTH - strlen($prefix) - strlen($stamp);
|
|
63
|
+
|
|
64
|
+
if ($room < 1) {
|
|
65
|
+
throw new RuntimeException(
|
|
66
|
+
'Reference prefix "' . $prefix . '" leaves no room inside the '
|
|
67
|
+
. JAZZCASH_MAX_REFERENCE_LENGTH . '-character limit.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$alphabet = JAZZCASH_REFERENCE_ALPHABET;
|
|
72
|
+
$suffix = '';
|
|
73
|
+
for ($index = 0; $index < $room; $index++) {
|
|
74
|
+
$suffix .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return $prefix . $stamp . $suffix;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// pp_RequestID must be unique for every linking attempt.
|
|
81
|
+
$requestId = jazzcash_reference('R');
|
|
82
|
+
|
|
83
|
+
$params = [
|
|
84
|
+
{{PARAMS}}
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
$params['pp_MerchantID'] = $merchantId;
|
|
88
|
+
$params['pp_Password'] = $password;
|
|
89
|
+
$params['pp_MSISDN'] = (string) $msisdn;
|
|
90
|
+
$params['pp_RequestID'] = $requestId;
|
|
91
|
+
$params['pp_ReturnURL'] = $returnUrl;
|
|
92
|
+
|
|
93
|
+
function jazzcash_secure_hash(array $params, string $integritySalt): string
|
|
94
|
+
{
|
|
95
|
+
unset($params['pp_SecureHash']);
|
|
96
|
+
ksort($params, SORT_STRING);
|
|
97
|
+
|
|
98
|
+
$values = [];
|
|
99
|
+
foreach ($params as $value) {
|
|
100
|
+
if ($value !== null && (string) $value !== '') {
|
|
101
|
+
$values[] = (string) $value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
$message = $integritySalt . ($values === [] ? '' : '&' . implode('&', $values));
|
|
106
|
+
|
|
107
|
+
return hash_hmac('sha256', $message, $integritySalt);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$params['pp_SecureHash'] = jazzcash_secure_hash($params, $integritySalt);
|
|
111
|
+
?>
|
|
112
|
+
<!DOCTYPE html>
|
|
113
|
+
<html lang="en">
|
|
114
|
+
<head>
|
|
115
|
+
<meta charset="UTF-8">
|
|
116
|
+
<title>Link your JazzCash wallet</title>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<h1>Link your JazzCash wallet</h1>
|
|
120
|
+
<p>You will be asked for your MPIN on the JazzCash page.</p>
|
|
121
|
+
|
|
122
|
+
<form id="link-wallet" method="post" action="<?= htmlspecialchars($postUrl, ENT_QUOTES, 'UTF-8') ?>">
|
|
123
|
+
<?php foreach ($params as $name => $value): ?>
|
|
124
|
+
<input type="hidden"
|
|
125
|
+
name="<?= htmlspecialchars((string) $name, ENT_QUOTES, 'UTF-8') ?>"
|
|
126
|
+
value="<?= htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8') ?>">
|
|
127
|
+
<?php endforeach; ?>
|
|
128
|
+
<button type="submit">Continue</button>
|
|
129
|
+
</form>
|
|
130
|
+
|
|
131
|
+
<?php
|
|
132
|
+
// On return, JazzCash posts a payment token to your Return URL. Store it
|
|
133
|
+
// against the customer and charge it later with the token payment API.
|
|
134
|
+
// Call the Token Inquiry API before charging: the customer may have unlinked.
|
|
135
|
+
?>
|
|
136
|
+
</body>
|
|
137
|
+
</html>
|