@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,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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"}
|