@laranex/myanmar-payments 4.0.0-dev.0 → 4.0.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +19 -4
  2. package/README.md +3 -3
  3. package/dist/cjs/aya-pay/aya-pay.d.ts +13 -9
  4. package/dist/cjs/aya-pay/aya-pay.js +16 -12
  5. package/dist/cjs/core/amount.d.ts +4 -3
  6. package/dist/cjs/core/amount.js +13 -4
  7. package/dist/cjs/core/callback.d.ts +14 -8
  8. package/dist/cjs/core/callback.js +19 -11
  9. package/dist/cjs/core/crypto.d.ts +6 -1
  10. package/dist/cjs/core/crypto.js +17 -3
  11. package/dist/cjs/core/env.d.ts +14 -5
  12. package/dist/cjs/core/env.js +35 -11
  13. package/dist/cjs/core/errors.d.ts +9 -3
  14. package/dist/cjs/core/errors.js +12 -4
  15. package/dist/cjs/core/http.d.ts +10 -11
  16. package/dist/cjs/core/http.js +7 -9
  17. package/dist/cjs/core/index.d.ts +2 -2
  18. package/dist/cjs/core/index.js +3 -2
  19. package/dist/cjs/core/json.d.ts +2 -2
  20. package/dist/cjs/core/json.js +3 -3
  21. package/dist/cjs/core/status.d.ts +3 -3
  22. package/dist/cjs/core/status.js +2 -2
  23. package/dist/cjs/core/values.d.ts +2 -0
  24. package/dist/cjs/core/values.js +5 -0
  25. package/dist/cjs/cyber-source/cyber-source.d.ts +12 -14
  26. package/dist/cjs/cyber-source/cyber-source.js +39 -26
  27. package/dist/cjs/kbz-pay/kbz-pay.d.ts +27 -11
  28. package/dist/cjs/kbz-pay/kbz-pay.js +43 -20
  29. package/dist/cjs/kbz-pay/signer.d.ts +4 -1
  30. package/dist/cjs/kbz-pay/signer.js +7 -2
  31. package/dist/cjs/wave-money/wave-money.d.ts +26 -13
  32. package/dist/cjs/wave-money/wave-money.js +29 -29
  33. package/dist/cjs/yoma-mmqr/yoma-mmqr.d.ts +17 -13
  34. package/dist/cjs/yoma-mmqr/yoma-mmqr.js +21 -19
  35. package/dist/esm/aya-pay/aya-pay.d.ts +13 -9
  36. package/dist/esm/aya-pay/aya-pay.js +18 -14
  37. package/dist/esm/core/amount.d.ts +4 -3
  38. package/dist/esm/core/amount.js +13 -4
  39. package/dist/esm/core/callback.d.ts +14 -8
  40. package/dist/esm/core/callback.js +19 -11
  41. package/dist/esm/core/crypto.d.ts +6 -1
  42. package/dist/esm/core/crypto.js +17 -3
  43. package/dist/esm/core/env.d.ts +14 -5
  44. package/dist/esm/core/env.js +32 -9
  45. package/dist/esm/core/errors.d.ts +9 -3
  46. package/dist/esm/core/errors.js +12 -4
  47. package/dist/esm/core/http.d.ts +10 -11
  48. package/dist/esm/core/http.js +6 -8
  49. package/dist/esm/core/index.d.ts +2 -2
  50. package/dist/esm/core/index.js +2 -1
  51. package/dist/esm/core/json.d.ts +2 -2
  52. package/dist/esm/core/json.js +3 -3
  53. package/dist/esm/core/status.d.ts +3 -3
  54. package/dist/esm/core/status.js +2 -2
  55. package/dist/esm/core/values.d.ts +2 -0
  56. package/dist/esm/core/values.js +4 -0
  57. package/dist/esm/cyber-source/cyber-source.d.ts +12 -14
  58. package/dist/esm/cyber-source/cyber-source.js +41 -28
  59. package/dist/esm/kbz-pay/kbz-pay.d.ts +27 -11
  60. package/dist/esm/kbz-pay/kbz-pay.js +45 -22
  61. package/dist/esm/kbz-pay/signer.d.ts +4 -1
  62. package/dist/esm/kbz-pay/signer.js +8 -3
  63. package/dist/esm/wave-money/wave-money.d.ts +26 -13
  64. package/dist/esm/wave-money/wave-money.js +31 -31
  65. package/dist/esm/yoma-mmqr/yoma-mmqr.d.ts +17 -13
  66. package/dist/esm/yoma-mmqr/yoma-mmqr.js +23 -21
  67. package/package.json +2 -2
  68. package/skills/node-myanmar-payments/SKILL.md +26 -22
@@ -1,8 +1,8 @@
1
1
  export { Amount, type AmountInput } from './amount.js';
2
2
  export { MemoryTokenCache, type TokenCache } from './cache.js';
3
3
  export { Acknowledgement, CallbackRequest, PaymentCallback, PaymentStatusResult, type AcknowledgementInit, type BodyInput, type CallbackRequestInit, type HeadersInput, type NodeRequestLike, type PaymentCallbackInit, type PaymentStatusResultInit, type QueryInput, type ServerResponseLike, type WebRequestLike, } from './callback.js';
4
- export type { EnvSource } from './env.js';
4
+ export { DEFAULT_TIMEOUT_SECONDS, type EnvSource } from './env.js';
5
5
  export { ApiError, ConfigurationError, InvalidPaymentDataError, PaymentError, SignatureVerificationError, } from './errors.js';
6
- export { DEFAULT_TIMEOUT_MS, FetchHttpClient, type FetchFunction, type FetchHttpClientOptions, type GatewayOptions, type HttpClient, type HttpRequest, type HttpResponse, type RequestOptions, } from './http.js';
6
+ export { FetchHttpClient, type FetchFunction, type FetchHttpClientOptions, type GatewayOptions, type HttpClient, type HttpRequest, type HttpResponse, type RequestOptions, } from './http.js';
7
7
  export { AppPayment, FormPayment, QrPayment, RedirectPayment, type FormField, type PaymentResult, } from './results.js';
8
8
  export { PaymentFlow, PaymentStatus, resolveStatus } from './status.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveStatus = exports.PaymentStatus = exports.PaymentFlow = exports.RedirectPayment = exports.QrPayment = exports.FormPayment = exports.AppPayment = exports.FetchHttpClient = exports.DEFAULT_TIMEOUT_MS = exports.SignatureVerificationError = exports.PaymentError = exports.InvalidPaymentDataError = exports.ConfigurationError = exports.ApiError = exports.PaymentStatusResult = exports.PaymentCallback = exports.CallbackRequest = exports.Acknowledgement = exports.MemoryTokenCache = exports.Amount = void 0;
3
+ exports.resolveStatus = exports.PaymentStatus = exports.PaymentFlow = exports.RedirectPayment = exports.QrPayment = exports.FormPayment = exports.AppPayment = exports.FetchHttpClient = exports.SignatureVerificationError = exports.PaymentError = exports.InvalidPaymentDataError = exports.ConfigurationError = exports.ApiError = exports.DEFAULT_TIMEOUT_SECONDS = exports.PaymentStatusResult = exports.PaymentCallback = exports.CallbackRequest = exports.Acknowledgement = exports.MemoryTokenCache = exports.Amount = void 0;
4
4
  var amount_js_1 = require("./amount.js");
5
5
  Object.defineProperty(exports, "Amount", { enumerable: true, get: function () { return amount_js_1.Amount; } });
6
6
  var cache_js_1 = require("./cache.js");
@@ -10,6 +10,8 @@ Object.defineProperty(exports, "Acknowledgement", { enumerable: true, get: funct
10
10
  Object.defineProperty(exports, "CallbackRequest", { enumerable: true, get: function () { return callback_js_1.CallbackRequest; } });
11
11
  Object.defineProperty(exports, "PaymentCallback", { enumerable: true, get: function () { return callback_js_1.PaymentCallback; } });
12
12
  Object.defineProperty(exports, "PaymentStatusResult", { enumerable: true, get: function () { return callback_js_1.PaymentStatusResult; } });
13
+ var env_js_1 = require("./env.js");
14
+ Object.defineProperty(exports, "DEFAULT_TIMEOUT_SECONDS", { enumerable: true, get: function () { return env_js_1.DEFAULT_TIMEOUT_SECONDS; } });
13
15
  var errors_js_1 = require("./errors.js");
14
16
  Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return errors_js_1.ApiError; } });
15
17
  Object.defineProperty(exports, "ConfigurationError", { enumerable: true, get: function () { return errors_js_1.ConfigurationError; } });
@@ -17,7 +19,6 @@ Object.defineProperty(exports, "InvalidPaymentDataError", { enumerable: true, ge
17
19
  Object.defineProperty(exports, "PaymentError", { enumerable: true, get: function () { return errors_js_1.PaymentError; } });
18
20
  Object.defineProperty(exports, "SignatureVerificationError", { enumerable: true, get: function () { return errors_js_1.SignatureVerificationError; } });
19
21
  var http_js_1 = require("./http.js");
20
- Object.defineProperty(exports, "DEFAULT_TIMEOUT_MS", { enumerable: true, get: function () { return http_js_1.DEFAULT_TIMEOUT_MS; } });
21
22
  Object.defineProperty(exports, "FetchHttpClient", { enumerable: true, get: function () { return http_js_1.FetchHttpClient; } });
22
23
  var results_js_1 = require("./results.js");
23
24
  Object.defineProperty(exports, "AppPayment", { enumerable: true, get: function () { return results_js_1.AppPayment; } });
@@ -32,8 +32,8 @@ export declare function parseJsonObject(text: string): LosslessObject | undefine
32
32
  /** @internal */
33
33
  export declare function isLosslessObject(value: unknown): value is LosslessObject;
34
34
  /**
35
- * Converts a lossless value to plain JavaScript values (numbers become `number`), for the `raw`
36
- * fields of results.
35
+ * Converts a lossless value to plain JavaScript values for the `raw` fields of results. Numbers
36
+ * become their exact text as a string (`1000.50` stays `"1000.50"`), so no precision is lost.
37
37
  *
38
38
  * @internal
39
39
  */
@@ -56,14 +56,14 @@ function isLosslessObject(value) {
56
56
  !(value instanceof JsonNumber));
57
57
  }
58
58
  /**
59
- * Converts a lossless value to plain JavaScript values (numbers become `number`), for the `raw`
60
- * fields of results.
59
+ * Converts a lossless value to plain JavaScript values for the `raw` fields of results. Numbers
60
+ * become their exact text as a string (`1000.50` stays `"1000.50"`), so no precision is lost.
61
61
  *
62
62
  * @internal
63
63
  */
64
64
  function toPlain(value) {
65
65
  if (value instanceof JsonNumber) {
66
- return Number(value.text);
66
+ return value.text;
67
67
  }
68
68
  if (Array.isArray(value)) {
69
69
  return value.map(toPlain);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A gateway-independent payment status. Every gateway's own status values are mapped onto these.
3
3
  */
4
- export type PaymentStatus = 'successful' | 'pending' | 'failed' | 'cancelled' | 'expired' | 'unknown';
4
+ export type PaymentStatus = 'successful' | 'pending' | 'failed' | 'canceled' | 'expired' | 'unknown';
5
5
  /**
6
6
  * The payment statuses, plus helpers.
7
7
  *
@@ -18,13 +18,13 @@ export declare const PaymentStatus: Readonly<{
18
18
  /** The payment was attempted and failed or was rejected. */
19
19
  Failed: "failed";
20
20
  /** The payment or order was canceled or closed before completing. */
21
- Cancelled: "cancelled";
21
+ Canceled: "canceled";
22
22
  /** The payment window ran out before the customer paid. */
23
23
  Expired: "expired";
24
24
  /** The gateway sent a status this package does not recognize. Inspect `gatewayStatus`. */
25
25
  Unknown: "unknown";
26
26
  /** Every status, in the order above. */
27
- values: readonly ["successful", "pending", "failed", "cancelled", "expired", "unknown"];
27
+ values: readonly ["successful", "pending", "failed", "canceled", "expired", "unknown"];
28
28
  /** Whether the status will not change any more: `false` for `pending` and `unknown`. */
29
29
  isFinal(status: PaymentStatus): boolean;
30
30
  }>;
@@ -18,7 +18,7 @@ exports.PaymentStatus = Object.freeze({
18
18
  /** The payment was attempted and failed or was rejected. */
19
19
  Failed: 'failed',
20
20
  /** The payment or order was canceled or closed before completing. */
21
- Cancelled: 'cancelled',
21
+ Canceled: 'canceled',
22
22
  /** The payment window ran out before the customer paid. */
23
23
  Expired: 'expired',
24
24
  /** The gateway sent a status this package does not recognize. Inspect `gatewayStatus`. */
@@ -28,7 +28,7 @@ exports.PaymentStatus = Object.freeze({
28
28
  'successful',
29
29
  'pending',
30
30
  'failed',
31
- 'cancelled',
31
+ 'canceled',
32
32
  'expired',
33
33
  'unknown',
34
34
  ]),
@@ -14,3 +14,5 @@ export declare function optional(source: Record<string, unknown>, key: string):
14
14
  export declare function trimmed(source: Record<string, unknown>, key: string): string;
15
15
  /** The value at `key` when it is an object (not an array). @internal */
16
16
  export declare function object(source: Record<string, unknown>, key: string): LosslessObject | undefined;
17
+ /** Whether `value` is an object or array, which a gateway never signs. @internal */
18
+ export declare function isNested(value: unknown): boolean;
@@ -5,6 +5,7 @@ exports.get = get;
5
5
  exports.optional = optional;
6
6
  exports.trimmed = trimmed;
7
7
  exports.object = object;
8
+ exports.isNested = isNested;
8
9
  const json_js_1 = require("./json.js");
9
10
  /**
10
11
  * Returns a scalar as the text a gateway signs: strings as is, numbers exactly as sent, booleans as
@@ -43,3 +44,7 @@ function object(source, key) {
43
44
  const value = source[key];
44
45
  return (0, json_js_1.isLosslessObject)(value) ? value : undefined;
45
46
  }
47
+ /** Whether `value` is an object or array, which a gateway never signs. @internal */
48
+ function isNested(value) {
49
+ return typeof value === 'object' && value !== null && !(value instanceof json_js_1.JsonNumber);
50
+ }
@@ -10,27 +10,24 @@ export interface CyberSourceConfigOptions {
10
10
  accessKey: string;
11
11
  /** The profile's secret key, which signs the fields. */
12
12
  secretKey: string;
13
- /** Use the test environment (default `true`). */
14
- sandbox?: boolean | undefined;
15
- /** Overrides the Secure Acceptance base URL. */
13
+ /** Overrides the Secure Acceptance base URL, e.g. to point at the test environment. */
16
14
  baseUrl?: string | undefined;
17
15
  }
18
16
  /**
19
- * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`.
17
+ * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`. The
18
+ * URL defaults to production.
20
19
  */
21
20
  export declare class CyberSourceConfig {
22
- static readonly SANDBOX_URL = "https://testsecureacceptance.cybersource.com";
23
21
  static readonly PRODUCTION_URL = "https://secureacceptance.cybersource.com";
24
22
  readonly profileId: string;
25
23
  readonly accessKey: string;
26
24
  readonly secretKey: string;
27
- readonly sandbox: boolean;
28
25
  /** The base URL in use. */
29
26
  readonly baseUrl: string;
30
27
  constructor(options: CyberSourceConfigOptions);
31
28
  /**
32
29
  * Reads `CYBER_SOURCE_PROFILE_ID`, `CYBER_SOURCE_ACCESS_KEY`, `CYBER_SOURCE_SECRET_KEY`,
33
- * `CYBER_SOURCE_SANDBOX` and `CYBER_SOURCE_BASE_URL`.
30
+ * and `CYBER_SOURCE_BASE_URL`.
34
31
  */
35
32
  static fromEnv(env?: EnvSource): CyberSourceConfig;
36
33
  }
@@ -61,12 +58,12 @@ export interface CyberSourcePaymentData {
61
58
  returnUrl?: string | undefined;
62
59
  /** The page shown on cancel (`override_custom_cancel_page`), at most 255 characters. */
63
60
  cancelUrl?: string | undefined;
64
- /** An ISO 4217 code (default `MMK`). */
65
- currency?: string | undefined;
66
- /** What to do with the card (default `sale`). */
67
- transactionType?: CyberSourceTransactionType | undefined;
68
- /** The hosted page language, e.g. `en-us` (the default). */
69
- locale?: string | undefined;
61
+ /** An ISO 4217 code, e.g. `MMK`. */
62
+ currency: string;
63
+ /** What to do with the card, e.g. `sale`. */
64
+ transactionType: CyberSourceTransactionType;
65
+ /** The hosted page language, e.g. `en-us`. */
66
+ locale: string;
70
67
  }
71
68
  /**
72
69
  * CyberSource Secure Acceptance hosted checkout for card payments: signs the form and verifies the
@@ -87,7 +84,8 @@ export declare class CyberSource {
87
84
  initiate(data: CyberSourcePaymentData): FormPayment;
88
85
  /**
89
86
  * Verifies CyberSource's result post. The same check works for the browser post to your receipt
90
- * page.
87
+ * page. Only the fields listed in `signed_field_names` are trusted: `decision` and
88
+ * `req_reference_number` must be signed, and unsigned fields are left out of the result.
91
89
  */
92
90
  handleCallback(request: CallbackRequest): PaymentCallback;
93
91
  /** Signs the fields listed in `signed_field_names`; `undefined` when a listed field is missing. */
@@ -11,35 +11,31 @@ const status_js_1 = require("../core/status.js");
11
11
  const validate_js_1 = require("../core/validate.js");
12
12
  const values_js_1 = require("../core/values.js");
13
13
  /**
14
- * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`.
14
+ * A CyberSource Secure Acceptance profile. A missing credential throws a `ConfigurationError`. The
15
+ * URL defaults to production.
15
16
  */
16
17
  class CyberSourceConfig {
17
- static SANDBOX_URL = 'https://testsecureacceptance.cybersource.com';
18
18
  static PRODUCTION_URL = 'https://secureacceptance.cybersource.com';
19
19
  profileId;
20
20
  accessKey;
21
21
  secretKey;
22
- sandbox;
23
22
  /** The base URL in use. */
24
23
  baseUrl;
25
24
  constructor(options) {
26
25
  this.profileId = (0, env_js_1.requireSetting)('cyber_source', 'profile_id', options.profileId);
27
26
  this.accessKey = (0, env_js_1.requireSetting)('cyber_source', 'access_key', options.accessKey);
28
27
  this.secretKey = (0, env_js_1.requireSetting)('cyber_source', 'secret_key', options.secretKey);
29
- this.sandbox = options.sandbox ?? true;
30
- this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ??
31
- (this.sandbox ? CyberSourceConfig.SANDBOX_URL : CyberSourceConfig.PRODUCTION_URL));
28
+ this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ?? CyberSourceConfig.PRODUCTION_URL);
32
29
  }
33
30
  /**
34
31
  * Reads `CYBER_SOURCE_PROFILE_ID`, `CYBER_SOURCE_ACCESS_KEY`, `CYBER_SOURCE_SECRET_KEY`,
35
- * `CYBER_SOURCE_SANDBOX` and `CYBER_SOURCE_BASE_URL`.
32
+ * and `CYBER_SOURCE_BASE_URL`.
36
33
  */
37
34
  static fromEnv(env = (0, env_js_1.defaultEnv)()) {
38
35
  return new CyberSourceConfig({
39
36
  profileId: (0, env_js_1.envFirst)(env, 'CYBER_SOURCE_PROFILE_ID'),
40
37
  accessKey: (0, env_js_1.envFirst)(env, 'CYBER_SOURCE_ACCESS_KEY'),
41
38
  secretKey: (0, env_js_1.envFirst)(env, 'CYBER_SOURCE_SECRET_KEY'),
42
- sandbox: (0, env_js_1.envSandbox)(env, 'CYBER_SOURCE_SANDBOX'),
43
39
  baseUrl: (0, env_js_1.envFirst)(env, 'CYBER_SOURCE_BASE_URL'),
44
40
  });
45
41
  }
@@ -77,7 +73,7 @@ const STATUSES = {
77
73
  REVIEW: 'pending',
78
74
  DECLINE: 'failed',
79
75
  ERROR: 'failed',
80
- CANCEL: 'cancelled',
76
+ CANCEL: 'canceled',
81
77
  };
82
78
  /**
83
79
  * CyberSource Secure Acceptance hosted checkout for card payments: signs the form and verifies the
@@ -113,9 +109,12 @@ class CyberSource {
113
109
  .string('cancelUrl', data.cancelUrl)
114
110
  .url('cancelUrl', data.cancelUrl)
115
111
  .max('cancelUrl', data.cancelUrl, 255)
116
- .pattern('currency', String(data.currency || 'MMK'), /^[A-Z]{3}$/, 'a three letter ISO 4217 code')
117
- .pattern('locale', String(data.locale || 'en-us'), /^[a-z]{2}-[a-z]{2}$/, 'a locale code such as en-us')
118
- .when(!TRANSACTION_TYPES.includes(String(data.transactionType || 'sale')), 'transactionType', 'The transactionType field is not a supported transaction type.')
112
+ .required('currency', data.currency)
113
+ .pattern('currency', data.currency, /^[A-Z]{3}$/, 'a three letter ISO 4217 code')
114
+ .required('transactionType', data.transactionType)
115
+ .when(!TRANSACTION_TYPES.includes(String(data.transactionType)), 'transactionType', 'The transactionType field is not a supported transaction type.')
116
+ .required('locale', data.locale)
117
+ .pattern('locale', data.locale, /^[a-z]{2}-[a-z]{2}$/, 'a locale code such as en-us')
119
118
  .validate();
120
119
  }
121
120
  /**
@@ -130,11 +129,11 @@ class CyberSource {
130
129
  transaction_uuid: (0, crypto_js_1.randomHex)(),
131
130
  signed_field_names: SIGNED_FIELDS.join(','),
132
131
  signed_date_time: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
133
- locale: data.locale || 'en-us',
134
- transaction_type: data.transactionType || 'sale',
132
+ locale: data.locale,
133
+ transaction_type: data.transactionType,
135
134
  reference_number: data.orderId,
136
135
  amount: String((0, validate_js_1.toAmount)(data.amount)),
137
- currency: data.currency || 'MMK',
136
+ currency: data.currency,
138
137
  override_custom_receipt_page: data.returnUrl ?? '',
139
138
  override_backoffice_post_url: data.callbackUrl,
140
139
  override_custom_cancel_page: data.cancelUrl ?? '',
@@ -149,31 +148,39 @@ class CyberSource {
149
148
  }
150
149
  /**
151
150
  * Verifies CyberSource's result post. The same check works for the browser post to your receipt
152
- * page.
151
+ * page. Only the fields listed in `signed_field_names` are trusted: `decision` and
152
+ * `req_reference_number` must be signed, and unsigned fields are left out of the result.
153
153
  */
154
154
  handleCallback(request) {
155
155
  const payload = (0, callback_js_1.losslessInput)(request);
156
156
  const expected = this.sign(payload);
157
- if (expected === undefined || !(0, crypto_js_1.safeEqual)(expected, (0, values_js_1.get)(payload, 'signature'))) {
157
+ const names = signedNames(payload);
158
+ if (expected === undefined ||
159
+ !(0, crypto_js_1.safeEqual)(expected, (0, values_js_1.get)(payload, 'signature')) ||
160
+ !names.includes('decision') ||
161
+ !names.includes('req_reference_number')) {
158
162
  throw new errors_js_1.SignatureVerificationError('CyberSource callback signature verification failed.', (0, json_js_1.toPlainObject)(payload));
159
163
  }
160
- const decision = (0, values_js_1.trimmed)(payload, 'decision').toUpperCase();
164
+ // A signature only covers the listed fields, so anything else in the post could have been
165
+ // added by the sender (e.g. a `decision` next to the signed request fields).
166
+ const signed = {};
167
+ for (const name of [...names, 'signature']) {
168
+ (0, json_js_1.setKey)(signed, name, payload[name]);
169
+ }
170
+ const decision = (0, values_js_1.trimmed)(signed, 'decision').toUpperCase();
161
171
  return new callback_js_1.PaymentCallback({
162
- orderId: (0, values_js_1.get)(payload, 'req_reference_number'),
172
+ orderId: (0, values_js_1.get)(signed, 'req_reference_number'),
163
173
  status: (0, status_js_1.resolveStatus)(STATUSES, decision),
164
174
  gatewayStatus: decision,
165
- gatewayReference: (0, values_js_1.optional)(payload, 'transaction_id'),
166
- amount: (0, values_js_1.get)(payload, 'auth_amount') || (0, values_js_1.optional)(payload, 'req_amount'),
167
- raw: (0, json_js_1.toPlainObject)(payload),
175
+ gatewayReference: (0, values_js_1.optional)(signed, 'transaction_id'),
176
+ amount: (0, values_js_1.get)(signed, 'auth_amount') || (0, values_js_1.optional)(signed, 'req_amount'),
177
+ raw: (0, json_js_1.toPlainObject)(signed),
168
178
  });
169
179
  }
170
180
  /** Signs the fields listed in `signed_field_names`; `undefined` when a listed field is missing. */
171
181
  sign(fields) {
172
182
  const pairs = [];
173
- for (const name of (0, values_js_1.get)(fields, 'signed_field_names').split(',')) {
174
- if (name === '') {
175
- continue;
176
- }
183
+ for (const name of signedNames(fields)) {
177
184
  const value = (0, values_js_1.scalarString)(fields[name]);
178
185
  if (value === undefined) {
179
186
  return undefined;
@@ -186,3 +193,9 @@ class CyberSource {
186
193
  }
187
194
  }
188
195
  exports.CyberSource = CyberSource;
196
+ /** The names listed in `signed_field_names`, in order. */
197
+ function signedNames(fields) {
198
+ return (0, values_js_1.get)(fields, 'signed_field_names')
199
+ .split(',')
200
+ .filter((name) => name !== '');
201
+ }
@@ -12,33 +12,45 @@ export interface KbzPayConfigOptions {
12
12
  appKey: string;
13
13
  /** The `merch_code` KBZ issued. */
14
14
  merchantCode: string;
15
- /** Use the UAT endpoints (default `true`). UAT and production issue separate credentials. */
16
- sandbox?: boolean | undefined;
17
- /** Overrides the API base URL, e.g. to go through a proxy. */
15
+ /**
16
+ * Seconds before the default HTTP client gives up on a request, a whole number greater than 0
17
+ * (or its text). Defaults to 30 (`DEFAULT_TIMEOUT_SECONDS`) when unset or blank. A
18
+ * client you pass keeps its own timeout.
19
+ */
20
+ timeoutSeconds?: number | string | null | undefined;
21
+ /** Overrides the API base URL, e.g. to point at UAT or go through a proxy. */
18
22
  apiUrl?: string | undefined;
19
23
  /** Overrides the PWA checkout URL. A trailing `#` or `#/` is normalized to `#/`. */
20
24
  pwaUrl?: string | undefined;
25
+ /**
26
+ * Seconds the customer has to pay each order, a multiple of 60 from 60 to 7200 (or its text).
27
+ * A payment's own `timeToLiveSeconds` overrides it; unset or blank leaves it to KBZ (7200).
28
+ */
29
+ timeToLiveSeconds?: number | string | null | undefined;
21
30
  }
22
31
  /**
23
- * KBZ Pay credentials and endpoints. A missing credential throws a `ConfigurationError`.
32
+ * KBZ Pay credentials and endpoints. A missing setting throws a `ConfigurationError`. The URLs
33
+ * default to production; UAT and production issue separate credentials.
24
34
  */
25
35
  export declare class KbzPayConfig {
26
- static readonly SANDBOX_API_URL = "http://api-uat.kbzpay.com/payment/gateway/uat";
27
36
  static readonly PRODUCTION_API_URL = "https://api.kbzpay.com/payment/gateway";
28
- static readonly SANDBOX_PWA_URL = "https://static.kbzpay.com/pgw/uat/pwa/#/";
29
37
  static readonly PRODUCTION_PWA_URL = "https://wap.kbzpay.com/pgw/pwa/#/";
30
38
  readonly appId: string;
31
39
  readonly appKey: string;
32
40
  readonly merchantCode: string;
33
- readonly sandbox: boolean;
41
+ /** Seconds before the default HTTP client gives up on a request. */
42
+ readonly timeoutSeconds: number;
34
43
  /** The API base URL in use. */
35
44
  readonly apiUrl: string;
36
45
  /** The PWA checkout URL in use, always ending in `/`. */
37
46
  readonly pwaUrl: string;
47
+ /** Seconds the customer has to pay each order unless the payment sets its own; `undefined` leaves it to KBZ. */
48
+ readonly timeToLiveSeconds: number | undefined;
38
49
  constructor(options: KbzPayConfigOptions);
39
50
  /**
40
- * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`, `KBZ_PAY_SANDBOX`,
41
- * `KBZ_PAY_BASE_URL` and `KBZ_PAY_PWA_BASE_REDIRECT_URL`.
51
+ * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`,
52
+ * `MYANMAR_PAYMENTS_HTTP_TIMEOUT`, `KBZ_PAY_BASE_URL`, `KBZ_PAY_PWA_BASE_REDIRECT_URL` and
53
+ * `KBZ_PAY_TIME_TO_LIVE_IN_SECONDS`.
42
54
  */
43
55
  static fromEnv(env?: EnvSource): KbzPayConfig;
44
56
  }
@@ -54,8 +66,12 @@ export interface KbzPayPaymentData {
54
66
  callbackUrl: string;
55
67
  /** The product name shown to the customer. */
56
68
  title?: string | undefined;
57
- /** How long the order stays payable, 1 to 120 minutes. KBZ defaults to 120. */
58
- timeoutMinutes?: number | undefined;
69
+ /**
70
+ * Seconds the customer has to pay before the order expires, 60 to 7200 in whole minutes (a multiple
71
+ * of 60). KBZ counts minutes, so 1800 is sent as `30m`. Overrides the config's `timeToLiveSeconds`;
72
+ * when both are unset KBZ allows 7200 (120 minutes).
73
+ */
74
+ timeToLiveSeconds?: number | undefined;
59
75
  /** Free text KBZ sends back unchanged in the callback, at most 512 characters once URL-encoded. */
60
76
  callbackInfo?: string | undefined;
61
77
  }
@@ -12,44 +12,65 @@ const status_js_1 = require("../core/status.js");
12
12
  const validate_js_1 = require("../core/validate.js");
13
13
  const values_js_1 = require("../core/values.js");
14
14
  const signer_js_1 = require("./signer.js");
15
+ /** What a valid KBZ Pay `time_to_live_in_seconds` is: KBZ counts whole minutes, up to 120. */
16
+ const TIME_TO_LIVE_REQUIREMENT = 'a multiple of 60 from 60 to 7200';
17
+ function resolveTimeToLive(value) {
18
+ if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
19
+ return undefined;
20
+ }
21
+ let seconds;
22
+ if (typeof value === 'number' && Number.isSafeInteger(value)) {
23
+ seconds = value;
24
+ }
25
+ else if (typeof value === 'string' && /^[+-]?\d+$/.test(value.trim())) {
26
+ seconds = Number.parseInt(value.trim(), 10);
27
+ }
28
+ if (seconds === undefined || seconds < 60 || seconds > 7200 || seconds % 60 !== 0) {
29
+ throw new errors_js_1.ConfigurationError('kbz_pay', 'time_to_live_in_seconds', true, TIME_TO_LIVE_REQUIREMENT);
30
+ }
31
+ return seconds;
32
+ }
15
33
  /**
16
- * KBZ Pay credentials and endpoints. A missing credential throws a `ConfigurationError`.
34
+ * KBZ Pay credentials and endpoints. A missing setting throws a `ConfigurationError`. The URLs
35
+ * default to production; UAT and production issue separate credentials.
17
36
  */
18
37
  class KbzPayConfig {
19
- static SANDBOX_API_URL = 'http://api-uat.kbzpay.com/payment/gateway/uat';
20
38
  static PRODUCTION_API_URL = 'https://api.kbzpay.com/payment/gateway';
21
- static SANDBOX_PWA_URL = 'https://static.kbzpay.com/pgw/uat/pwa/#/';
22
39
  static PRODUCTION_PWA_URL = 'https://wap.kbzpay.com/pgw/pwa/#/';
23
40
  appId;
24
41
  appKey;
25
42
  merchantCode;
26
- sandbox;
43
+ /** Seconds before the default HTTP client gives up on a request. */
44
+ timeoutSeconds;
27
45
  /** The API base URL in use. */
28
46
  apiUrl;
29
47
  /** The PWA checkout URL in use, always ending in `/`. */
30
48
  pwaUrl;
49
+ /** Seconds the customer has to pay each order unless the payment sets its own; `undefined` leaves it to KBZ. */
50
+ timeToLiveSeconds;
31
51
  constructor(options) {
32
52
  this.appId = (0, env_js_1.requireSetting)('kbz_pay', 'app_id', options.appId);
33
53
  this.appKey = (0, env_js_1.requireSetting)('kbz_pay', 'app_key', options.appKey);
34
54
  this.merchantCode = (0, env_js_1.requireSetting)('kbz_pay', 'merchant_code', options.merchantCode);
35
- this.sandbox = options.sandbox ?? true;
36
- this.apiUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.apiUrl) ??
37
- (this.sandbox ? KbzPayConfig.SANDBOX_API_URL : KbzPayConfig.PRODUCTION_API_URL));
38
- this.pwaUrl = `${(0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.pwaUrl) ??
39
- (this.sandbox ? KbzPayConfig.SANDBOX_PWA_URL : KbzPayConfig.PRODUCTION_PWA_URL))}/`;
55
+ this.timeoutSeconds = (0, env_js_1.resolveTimeout)('kbz_pay', options.timeoutSeconds);
56
+ this.apiUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.apiUrl) ?? KbzPayConfig.PRODUCTION_API_URL);
57
+ this.pwaUrl = `${(0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.pwaUrl) ?? KbzPayConfig.PRODUCTION_PWA_URL)}/`;
58
+ this.timeToLiveSeconds = resolveTimeToLive(options.timeToLiveSeconds);
40
59
  }
41
60
  /**
42
- * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`, `KBZ_PAY_SANDBOX`,
43
- * `KBZ_PAY_BASE_URL` and `KBZ_PAY_PWA_BASE_REDIRECT_URL`.
61
+ * Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`,
62
+ * `MYANMAR_PAYMENTS_HTTP_TIMEOUT`, `KBZ_PAY_BASE_URL`, `KBZ_PAY_PWA_BASE_REDIRECT_URL` and
63
+ * `KBZ_PAY_TIME_TO_LIVE_IN_SECONDS`.
44
64
  */
45
65
  static fromEnv(env = (0, env_js_1.defaultEnv)()) {
46
66
  return new KbzPayConfig({
47
67
  appId: (0, env_js_1.envFirst)(env, 'KBZ_PAY_APP_ID'),
48
68
  appKey: (0, env_js_1.envFirst)(env, 'KBZ_PAY_APP_KEY'),
49
69
  merchantCode: (0, env_js_1.envFirst)(env, 'KBZ_PAY_MERCHANT_CODE'),
50
- sandbox: (0, env_js_1.envSandbox)(env, 'KBZ_PAY_SANDBOX'),
70
+ timeoutSeconds: (0, env_js_1.envFirst)(env, env_js_1.HTTP_TIMEOUT_VARIABLE),
51
71
  apiUrl: (0, env_js_1.envFirst)(env, 'KBZ_PAY_BASE_URL'),
52
72
  pwaUrl: (0, env_js_1.envFirst)(env, 'KBZ_PAY_PWA_BASE_REDIRECT_URL'),
73
+ timeToLiveSeconds: (0, env_js_1.envFirst)(env, 'KBZ_PAY_TIME_TO_LIVE_IN_SECONDS'),
53
74
  });
54
75
  }
55
76
  }
@@ -60,7 +81,7 @@ const STATUSES = {
60
81
  WAIT_PAY: 'pending',
61
82
  PAYING: 'pending',
62
83
  PAY_FAILED: 'failed',
63
- ORDER_CLOSED: 'cancelled',
84
+ ORDER_CLOSED: 'canceled',
64
85
  ORDER_EXPIRED: 'expired',
65
86
  };
66
87
  /**
@@ -76,7 +97,7 @@ class KbzPay {
76
97
  constructor(config, options = {}) {
77
98
  this.config = config instanceof KbzPayConfig ? config : new KbzPayConfig(config);
78
99
  this.signer = new signer_js_1.KbzPaySigner(this.config.appKey);
79
- this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options));
100
+ this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options, this.config.timeoutSeconds));
80
101
  }
81
102
  /** A gateway configured from the `KBZ_PAY_*` environment variables. */
82
103
  static fromEnv(env = (0, env_js_1.defaultEnv)(), options = {}) {
@@ -95,7 +116,8 @@ class KbzPay {
95
116
  .max('callbackUrl', callbackUrl, 512)
96
117
  .when(callbackUrl.includes('?'), 'callbackUrl', 'The callbackUrl field must not contain a query string.')
97
118
  .string('title', data.title)
98
- .between('timeoutMinutes', data.timeoutMinutes, 1, 120)
119
+ .between('timeToLiveSeconds', data.timeToLiveSeconds, 60, 7200)
120
+ .when(Number.isInteger(data.timeToLiveSeconds) && data.timeToLiveSeconds % 60 !== 0, 'timeToLiveSeconds', 'The timeToLiveSeconds field must be a multiple of 60.')
99
121
  .string('callbackInfo', data.callbackInfo)
100
122
  .max('callbackInfo', typeof data.callbackInfo === 'string' ? (0, crypto_js_1.queryEscape)(data.callbackInfo) : undefined, 512)
101
123
  .validate();
@@ -128,9 +150,9 @@ class KbzPay {
128
150
  return new results_js_1.QrPayment({
129
151
  orderId: data.orderId,
130
152
  qrString: qrCode,
131
- expiresAt: data.timeoutMinutes === undefined
153
+ expiresAt: order.timeToLiveSeconds === undefined
132
154
  ? undefined
133
- : new Date(Date.now() + data.timeoutMinutes * 60_000),
155
+ : new Date(Date.now() + order.timeToLiveSeconds * 1000),
134
156
  reference: order.prepayId,
135
157
  raw: (0, json_js_1.toPlainObject)(order.response),
136
158
  });
@@ -201,8 +223,9 @@ class KbzPay {
201
223
  if (data.title) {
202
224
  biz.title = data.title;
203
225
  }
204
- if (data.timeoutMinutes !== undefined) {
205
- biz.timeout_express = `${data.timeoutMinutes}m`;
226
+ const timeToLiveSeconds = data.timeToLiveSeconds ?? this.config.timeToLiveSeconds;
227
+ if (timeToLiveSeconds !== undefined) {
228
+ biz.timeout_express = `${timeToLiveSeconds / 60}m`;
206
229
  }
207
230
  if (data.callbackInfo) {
208
231
  biz.callback_info = (0, crypto_js_1.queryEscape)(data.callbackInfo);
@@ -214,7 +237,7 @@ class KbzPay {
214
237
  if (prepayId === '') {
215
238
  throw new errors_js_1.ApiError('KBZ Pay did not return a prepay_id.', { raw: (0, json_js_1.toPlainObject)(response) });
216
239
  }
217
- return { prepayId, nonce, timestamp: time, response };
240
+ return { prepayId, nonce, timestamp: time, timeToLiveSeconds, response };
218
241
  }
219
242
  /** Sends a signed request and returns the `Response` object, throwing when KBZ reports an error. */
220
243
  async call(endpoint, method, version, biz, extra, nonce, time, signal) {
@@ -10,6 +10,9 @@ export declare class KbzPaySigner {
10
10
  signString(fields: Readonly<Record<string, unknown>>): string;
11
11
  /** The uppercase SHA-256 signature of `fields`. */
12
12
  sign(fields: Readonly<Record<string, unknown>>): string;
13
- /** Whether `fields.sign` matches, compared in constant time. */
13
+ /**
14
+ * Whether `fields.sign` matches, compared in constant time. Nested values are never signed, so
15
+ * fields holding an object or array fail rather than being partly trusted.
16
+ */
14
17
  verify(fields: Readonly<Record<string, unknown>>): boolean;
15
18
  }
@@ -27,10 +27,15 @@ class KbzPaySigner {
27
27
  sign(fields) {
28
28
  return (0, crypto_js_1.sha256Hex)(`${this.signString(fields)}&key=${this.appKey}`).toUpperCase();
29
29
  }
30
- /** Whether `fields.sign` matches, compared in constant time. */
30
+ /**
31
+ * Whether `fields.sign` matches, compared in constant time. Nested values are never signed, so
32
+ * fields holding an object or array fail rather than being partly trusted.
33
+ */
31
34
  verify(fields) {
32
35
  const sign = fields.sign;
33
- return typeof sign === 'string' && (0, crypto_js_1.safeEqual)(this.sign(fields), sign.toUpperCase());
36
+ return (typeof sign === 'string' &&
37
+ !Object.values(fields).some(values_js_1.isNested) &&
38
+ (0, crypto_js_1.safeEqual)(this.sign(fields), sign.toUpperCase()));
34
39
  }
35
40
  }
36
41
  exports.KbzPaySigner = KbzPaySigner;