@laranex/myanmar-payments 4.0.0-dev.1 → 4.0.0-dev.11
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/CHANGELOG.md +19 -4
- package/README.md +3 -3
- package/dist/cjs/aya-pay/aya-pay.d.ts +13 -9
- package/dist/cjs/aya-pay/aya-pay.js +16 -12
- package/dist/cjs/core/amount.d.ts +4 -3
- package/dist/cjs/core/amount.js +13 -4
- package/dist/cjs/core/callback.d.ts +14 -8
- package/dist/cjs/core/callback.js +19 -11
- package/dist/cjs/core/crypto.d.ts +6 -1
- package/dist/cjs/core/crypto.js +17 -3
- package/dist/cjs/core/env.d.ts +21 -5
- package/dist/cjs/core/env.js +49 -12
- package/dist/cjs/core/errors.d.ts +9 -3
- package/dist/cjs/core/errors.js +12 -4
- package/dist/cjs/core/http.d.ts +10 -11
- package/dist/cjs/core/http.js +7 -9
- package/dist/cjs/core/index.d.ts +2 -2
- package/dist/cjs/core/index.js +3 -2
- package/dist/cjs/core/json.d.ts +2 -2
- package/dist/cjs/core/json.js +3 -3
- package/dist/cjs/core/status.d.ts +3 -3
- package/dist/cjs/core/status.js +2 -2
- package/dist/cjs/core/values.d.ts +2 -0
- package/dist/cjs/core/values.js +5 -0
- package/dist/cjs/cyber-source/cyber-source.d.ts +12 -14
- package/dist/cjs/cyber-source/cyber-source.js +39 -26
- package/dist/cjs/kbz-pay/kbz-pay.d.ts +36 -12
- package/dist/cjs/kbz-pay/kbz-pay.js +54 -23
- package/dist/cjs/kbz-pay/signer.d.ts +4 -1
- package/dist/cjs/kbz-pay/signer.js +7 -2
- package/dist/cjs/wave-money/wave-money.d.ts +34 -16
- package/dist/cjs/wave-money/wave-money.js +38 -31
- package/dist/cjs/yoma-mmqr/yoma-mmqr.d.ts +17 -13
- package/dist/cjs/yoma-mmqr/yoma-mmqr.js +21 -19
- package/dist/esm/aya-pay/aya-pay.d.ts +13 -9
- package/dist/esm/aya-pay/aya-pay.js +18 -14
- package/dist/esm/core/amount.d.ts +4 -3
- package/dist/esm/core/amount.js +13 -4
- package/dist/esm/core/callback.d.ts +14 -8
- package/dist/esm/core/callback.js +19 -11
- package/dist/esm/core/crypto.d.ts +6 -1
- package/dist/esm/core/crypto.js +17 -3
- package/dist/esm/core/env.d.ts +21 -5
- package/dist/esm/core/env.js +45 -10
- package/dist/esm/core/errors.d.ts +9 -3
- package/dist/esm/core/errors.js +12 -4
- package/dist/esm/core/http.d.ts +10 -11
- package/dist/esm/core/http.js +6 -8
- package/dist/esm/core/index.d.ts +2 -2
- package/dist/esm/core/index.js +2 -1
- package/dist/esm/core/json.d.ts +2 -2
- package/dist/esm/core/json.js +3 -3
- package/dist/esm/core/status.d.ts +3 -3
- package/dist/esm/core/status.js +2 -2
- package/dist/esm/core/values.d.ts +2 -0
- package/dist/esm/core/values.js +4 -0
- package/dist/esm/cyber-source/cyber-source.d.ts +12 -14
- package/dist/esm/cyber-source/cyber-source.js +41 -28
- package/dist/esm/kbz-pay/kbz-pay.d.ts +36 -12
- package/dist/esm/kbz-pay/kbz-pay.js +56 -25
- package/dist/esm/kbz-pay/signer.d.ts +4 -1
- package/dist/esm/kbz-pay/signer.js +8 -3
- package/dist/esm/wave-money/wave-money.d.ts +34 -16
- package/dist/esm/wave-money/wave-money.js +41 -34
- package/dist/esm/yoma-mmqr/yoma-mmqr.d.ts +17 -13
- package/dist/esm/yoma-mmqr/yoma-mmqr.js +23 -21
- package/package.json +2 -2
- package/skills/node-myanmar-payments/SKILL.md +26 -22
|
@@ -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_minutes` is: KBZ counts whole minutes, up to 120. */
|
|
16
|
+
const TIME_TO_LIVE_REQUIREMENT = 'a whole number from 1 to 120';
|
|
17
|
+
function resolveTimeToLive(value) {
|
|
18
|
+
if (value === undefined || value === null || (typeof value === 'string' && value.trim() === '')) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
let minutes;
|
|
22
|
+
if (typeof value === 'number' && Number.isSafeInteger(value)) {
|
|
23
|
+
minutes = value;
|
|
24
|
+
}
|
|
25
|
+
else if (typeof value === 'string' && /^[+-]?\d+$/.test(value.trim())) {
|
|
26
|
+
minutes = Number.parseInt(value.trim(), 10);
|
|
27
|
+
}
|
|
28
|
+
if (minutes === undefined || minutes < 1 || minutes > 120) {
|
|
29
|
+
throw new errors_js_1.ConfigurationError('kbz_pay', 'time_to_live_in_minutes', true, TIME_TO_LIVE_REQUIREMENT);
|
|
30
|
+
}
|
|
31
|
+
return minutes;
|
|
32
|
+
}
|
|
15
33
|
/**
|
|
16
|
-
* KBZ Pay credentials and endpoints. A missing
|
|
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
|
-
|
|
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
|
+
/** Minutes the customer has to pay each order unless the payment sets its own; `undefined` when unset. */
|
|
50
|
+
timeToLiveMinutes;
|
|
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.
|
|
36
|
-
this.apiUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.apiUrl) ??
|
|
37
|
-
|
|
38
|
-
this.
|
|
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.timeToLiveMinutes = resolveTimeToLive(options.timeToLiveMinutes);
|
|
40
59
|
}
|
|
41
60
|
/**
|
|
42
|
-
* Reads `KBZ_PAY_APP_ID`, `KBZ_PAY_APP_KEY`, `KBZ_PAY_MERCHANT_CODE`,
|
|
43
|
-
* `KBZ_PAY_BASE_URL` and
|
|
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_MINUTES`.
|
|
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
|
-
|
|
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
|
+
timeToLiveMinutes: (0, env_js_1.envFirst)(env, 'KBZ_PAY_TIME_TO_LIVE_IN_MINUTES'),
|
|
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: '
|
|
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,7 @@ 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('
|
|
119
|
+
.between('timeToLiveMinutes', data.timeToLiveMinutes, 1, 120)
|
|
99
120
|
.string('callbackInfo', data.callbackInfo)
|
|
100
121
|
.max('callbackInfo', typeof data.callbackInfo === 'string' ? (0, crypto_js_1.queryEscape)(data.callbackInfo) : undefined, 512)
|
|
101
122
|
.validate();
|
|
@@ -105,6 +126,8 @@ class KbzPay {
|
|
|
105
126
|
*
|
|
106
127
|
* The redirect only works on a phone with the KBZ Pay app installed, and its Referer must match
|
|
107
128
|
* the URL registered with KBZ.
|
|
129
|
+
* Throws `InvalidPaymentDataError` before any request when neither the payment nor the config sets
|
|
130
|
+
* a time to live.
|
|
108
131
|
*/
|
|
109
132
|
async pwa(data, options = {}) {
|
|
110
133
|
const order = await this.precreate(data, 'PWAAPP', options);
|
|
@@ -116,7 +139,11 @@ class KbzPay {
|
|
|
116
139
|
raw: (0, json_js_1.toPlainObject)(order.response),
|
|
117
140
|
});
|
|
118
141
|
}
|
|
119
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Creates an order and returns a QR payload for the customer to scan with the KBZ Pay app.
|
|
144
|
+
* Throws `InvalidPaymentDataError` before any request when neither the payment nor the config sets
|
|
145
|
+
* a time to live.
|
|
146
|
+
*/
|
|
120
147
|
async qr(data, options = {}) {
|
|
121
148
|
const order = await this.precreate(data, 'PAY_BY_QRCODE', options);
|
|
122
149
|
const qrCode = (0, values_js_1.get)(order.response, 'qrCode');
|
|
@@ -128,9 +155,7 @@ class KbzPay {
|
|
|
128
155
|
return new results_js_1.QrPayment({
|
|
129
156
|
orderId: data.orderId,
|
|
130
157
|
qrString: qrCode,
|
|
131
|
-
expiresAt:
|
|
132
|
-
? undefined
|
|
133
|
-
: new Date(Date.now() + data.timeoutMinutes * 60_000),
|
|
158
|
+
expiresAt: new Date(Date.now() + order.timeToLiveMinutes * 60_000),
|
|
134
159
|
reference: order.prepayId,
|
|
135
160
|
raw: (0, json_js_1.toPlainObject)(order.response),
|
|
136
161
|
});
|
|
@@ -138,6 +163,8 @@ class KbzPay {
|
|
|
138
163
|
/**
|
|
139
164
|
* Creates an order and returns the signed values your mobile app passes to `KBZPay.startPay()`.
|
|
140
165
|
* The SDK's own result only means the payment screen closed; rely on the callback or `status()`.
|
|
166
|
+
* Throws `InvalidPaymentDataError` before any request when neither the payment nor the config sets
|
|
167
|
+
* a time to live.
|
|
141
168
|
*/
|
|
142
169
|
async app(data, options = {}) {
|
|
143
170
|
const order = await this.precreate(data, 'APP', options);
|
|
@@ -190,6 +217,12 @@ class KbzPay {
|
|
|
190
217
|
}
|
|
191
218
|
async precreate(data, tradeType, options) {
|
|
192
219
|
KbzPay.validate(data);
|
|
220
|
+
const timeToLiveMinutes = data.timeToLiveMinutes ?? this.config.timeToLiveMinutes;
|
|
221
|
+
if (timeToLiveMinutes === undefined) {
|
|
222
|
+
throw new errors_js_1.InvalidPaymentDataError({
|
|
223
|
+
timeToLiveMinutes: 'The timeToLiveMinutes field is required when the kbz_pay configuration has no [time_to_live_in_minutes].',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
193
226
|
const biz = {
|
|
194
227
|
appid: this.config.appId,
|
|
195
228
|
merch_code: this.config.merchantCode,
|
|
@@ -201,9 +234,7 @@ class KbzPay {
|
|
|
201
234
|
if (data.title) {
|
|
202
235
|
biz.title = data.title;
|
|
203
236
|
}
|
|
204
|
-
|
|
205
|
-
biz.timeout_express = `${data.timeoutMinutes}m`;
|
|
206
|
-
}
|
|
237
|
+
biz.timeout_express = `${timeToLiveMinutes}m`;
|
|
207
238
|
if (data.callbackInfo) {
|
|
208
239
|
biz.callback_info = (0, crypto_js_1.queryEscape)(data.callbackInfo);
|
|
209
240
|
}
|
|
@@ -214,7 +245,7 @@ class KbzPay {
|
|
|
214
245
|
if (prepayId === '') {
|
|
215
246
|
throw new errors_js_1.ApiError('KBZ Pay did not return a prepay_id.', { raw: (0, json_js_1.toPlainObject)(response) });
|
|
216
247
|
}
|
|
217
|
-
return { prepayId, nonce, timestamp: time, response };
|
|
248
|
+
return { prepayId, nonce, timestamp: time, timeToLiveMinutes, response };
|
|
218
249
|
}
|
|
219
250
|
/** Sends a signed request and returns the `Response` object, throwing when KBZ reports an error. */
|
|
220
251
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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' &&
|
|
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;
|
|
@@ -11,38 +11,45 @@ export interface WaveMoneyConfigOptions {
|
|
|
11
11
|
secretKey: string;
|
|
12
12
|
/** Your business name, shown on Wave's payment page. */
|
|
13
13
|
merchantName: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/**
|
|
15
|
+
* How long the customer has to pay, in seconds: a whole number greater than 0 (or its text). A
|
|
16
|
+
* payment's own `timeToLiveSeconds` overrides it. Unset or blank is allowed, but then every
|
|
17
|
+
* payment must set its own.
|
|
18
|
+
*/
|
|
19
|
+
timeToLiveSeconds?: number | string | null | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Seconds before the default HTTP client gives up on a request, a whole number greater than 0
|
|
22
|
+
* (or its text). Defaults to 30 (`DEFAULT_TIMEOUT_SECONDS`) when unset or blank. A
|
|
23
|
+
* client you pass keeps its own timeout.
|
|
24
|
+
*/
|
|
25
|
+
timeoutSeconds?: number | string | null | undefined;
|
|
26
|
+
/** Overrides the API base URL, e.g. to point at UAT. */
|
|
19
27
|
baseUrl?: string | undefined;
|
|
20
28
|
/** Overrides the host the customer is redirected to. Wave serves it without the API port. */
|
|
21
29
|
authenticateUrl?: string | undefined;
|
|
22
30
|
}
|
|
23
31
|
/**
|
|
24
|
-
* Wave Money credentials and endpoints. A missing
|
|
32
|
+
* Wave Money credentials and endpoints. A missing setting throws a `ConfigurationError`. The URLs
|
|
33
|
+
* default to production.
|
|
25
34
|
*/
|
|
26
35
|
export declare class WaveMoneyConfig {
|
|
27
|
-
static readonly SANDBOX_URL = "https://preprodpayments.wavemoney.io:8107";
|
|
28
36
|
static readonly PRODUCTION_URL = "https://payments.wavemoney.io";
|
|
29
|
-
static readonly SANDBOX_AUTHENTICATE_URL = "https://preprodpayments.wavemoney.io";
|
|
30
37
|
static readonly PRODUCTION_AUTHENTICATE_URL = "https://payments.wavemoney.io";
|
|
31
|
-
static readonly DEFAULT_TIME_TO_LIVE_SECONDS = 300;
|
|
32
38
|
readonly merchantId: string;
|
|
33
39
|
readonly secretKey: string;
|
|
34
40
|
readonly merchantName: string;
|
|
35
|
-
/** Seconds the customer has to pay. */
|
|
36
|
-
readonly timeToLiveSeconds: number;
|
|
37
|
-
|
|
41
|
+
/** Seconds the customer has to pay unless the payment sets its own; `undefined` when unset. */
|
|
42
|
+
readonly timeToLiveSeconds: number | undefined;
|
|
43
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
44
|
+
readonly timeoutSeconds: number;
|
|
38
45
|
/** The API base URL in use. */
|
|
39
46
|
readonly baseUrl: string;
|
|
40
47
|
/** The host the customer is redirected to. */
|
|
41
48
|
readonly authenticateUrl: string;
|
|
42
49
|
constructor(options: WaveMoneyConfigOptions);
|
|
43
50
|
/**
|
|
44
|
-
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME
|
|
45
|
-
*
|
|
51
|
+
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME`,
|
|
52
|
+
* `WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT`, `WAVE_MONEY_BASE_URL`
|
|
46
53
|
* and `WAVE_MONEY_AUTHENTICATE_URL`.
|
|
47
54
|
*/
|
|
48
55
|
static fromEnv(env?: EnvSource): WaveMoneyConfig;
|
|
@@ -76,6 +83,16 @@ export interface WaveMoneyPaymentData {
|
|
|
76
83
|
* this.
|
|
77
84
|
*/
|
|
78
85
|
merchantReferenceId?: string | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* The business name shown on Wave's page for this payment. Defaults to the configured
|
|
88
|
+
* `merchantName` when unset.
|
|
89
|
+
*/
|
|
90
|
+
merchantName?: string | null | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* How long the customer has to pay this payment, in seconds: a whole number greater than 0.
|
|
93
|
+
* Defaults to the configured `timeToLiveSeconds` when unset; one of the two is required.
|
|
94
|
+
*/
|
|
95
|
+
timeToLiveSeconds?: number | null | undefined;
|
|
79
96
|
}
|
|
80
97
|
/**
|
|
81
98
|
* Wave Money (WavePay payment gateway): redirect payments and callbacks. Wave has no status API,
|
|
@@ -98,13 +115,14 @@ export declare class WaveMoney {
|
|
|
98
115
|
/**
|
|
99
116
|
* Creates a payment request and returns Wave's page to redirect the customer to. Once `data`
|
|
100
117
|
* passes validation it fills `data.merchantReferenceId` with a random id when empty, so store it
|
|
101
|
-
* afterwards. Invalid data is left untouched.
|
|
118
|
+
* afterwards. Invalid data is left untouched. Throws `InvalidPaymentDataError` before any request
|
|
119
|
+
* when neither the payment nor the config sets a time to live.
|
|
102
120
|
*/
|
|
103
121
|
initiate(data: WaveMoneyPaymentData, options?: RequestOptions): Promise<RedirectPayment>;
|
|
104
122
|
/**
|
|
105
123
|
* Verifies Wave's callback. Only `PAYMENT_CONFIRMED` means the customer paid. `orderId` falls
|
|
106
124
|
* back to `merchantReferenceId` when it is missing, null or empty, because Wave marks `orderId` as
|
|
107
|
-
* optional.
|
|
125
|
+
* optional. A hashed field holding an object or array fails verification.
|
|
108
126
|
*/
|
|
109
127
|
handleCallback(request: CallbackRequest): PaymentCallback;
|
|
110
128
|
private hash;
|
|
@@ -13,20 +13,19 @@ const status_js_1 = require("../core/status.js");
|
|
|
13
13
|
const validate_js_1 = require("../core/validate.js");
|
|
14
14
|
const values_js_1 = require("../core/values.js");
|
|
15
15
|
/**
|
|
16
|
-
* Wave Money credentials and endpoints. A missing
|
|
16
|
+
* Wave Money credentials and endpoints. A missing setting throws a `ConfigurationError`. The URLs
|
|
17
|
+
* default to production.
|
|
17
18
|
*/
|
|
18
19
|
class WaveMoneyConfig {
|
|
19
|
-
static SANDBOX_URL = 'https://preprodpayments.wavemoney.io:8107';
|
|
20
20
|
static PRODUCTION_URL = 'https://payments.wavemoney.io';
|
|
21
|
-
static SANDBOX_AUTHENTICATE_URL = 'https://preprodpayments.wavemoney.io';
|
|
22
21
|
static PRODUCTION_AUTHENTICATE_URL = 'https://payments.wavemoney.io';
|
|
23
|
-
static DEFAULT_TIME_TO_LIVE_SECONDS = 300;
|
|
24
22
|
merchantId;
|
|
25
23
|
secretKey;
|
|
26
24
|
merchantName;
|
|
27
|
-
/** Seconds the customer has to pay. */
|
|
25
|
+
/** Seconds the customer has to pay unless the payment sets its own; `undefined` when unset. */
|
|
28
26
|
timeToLiveSeconds;
|
|
29
|
-
|
|
27
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
28
|
+
timeoutSeconds;
|
|
30
29
|
/** The API base URL in use. */
|
|
31
30
|
baseUrl;
|
|
32
31
|
/** The host the customer is redirected to. */
|
|
@@ -35,31 +34,23 @@ class WaveMoneyConfig {
|
|
|
35
34
|
this.merchantId = (0, env_js_1.requireSetting)('wave_money', 'merchant_id', options.merchantId);
|
|
36
35
|
this.secretKey = (0, env_js_1.requireSetting)('wave_money', 'secret_key', options.secretKey);
|
|
37
36
|
this.merchantName = (0, env_js_1.requireSetting)('wave_money', 'merchant_name', options.merchantName);
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
: WaveMoneyConfig.DEFAULT_TIME_TO_LIVE_SECONDS;
|
|
43
|
-
this.sandbox = options.sandbox ?? true;
|
|
44
|
-
this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ??
|
|
45
|
-
(this.sandbox ? WaveMoneyConfig.SANDBOX_URL : WaveMoneyConfig.PRODUCTION_URL));
|
|
46
|
-
this.authenticateUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.authenticateUrl) ??
|
|
47
|
-
(this.sandbox
|
|
48
|
-
? WaveMoneyConfig.SANDBOX_AUTHENTICATE_URL
|
|
49
|
-
: WaveMoneyConfig.PRODUCTION_AUTHENTICATE_URL));
|
|
37
|
+
this.timeToLiveSeconds = (0, env_js_1.optionalSeconds)('wave_money', 'time_to_live_in_seconds', options.timeToLiveSeconds);
|
|
38
|
+
this.timeoutSeconds = (0, env_js_1.resolveTimeout)('wave_money', options.timeoutSeconds);
|
|
39
|
+
this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ?? WaveMoneyConfig.PRODUCTION_URL);
|
|
40
|
+
this.authenticateUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.authenticateUrl) ?? WaveMoneyConfig.PRODUCTION_AUTHENTICATE_URL);
|
|
50
41
|
}
|
|
51
42
|
/**
|
|
52
|
-
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME
|
|
53
|
-
*
|
|
43
|
+
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME`,
|
|
44
|
+
* `WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT`, `WAVE_MONEY_BASE_URL`
|
|
54
45
|
* and `WAVE_MONEY_AUTHENTICATE_URL`.
|
|
55
46
|
*/
|
|
56
47
|
static fromEnv(env = (0, env_js_1.defaultEnv)()) {
|
|
57
48
|
return new WaveMoneyConfig({
|
|
58
49
|
merchantId: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_MERCHANT_ID'),
|
|
59
50
|
secretKey: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_SECRET_KEY'),
|
|
60
|
-
merchantName: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_MERCHANT_NAME'
|
|
61
|
-
timeToLiveSeconds: (0, env_js_1.
|
|
62
|
-
|
|
51
|
+
merchantName: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_MERCHANT_NAME'),
|
|
52
|
+
timeToLiveSeconds: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS'),
|
|
53
|
+
timeoutSeconds: (0, env_js_1.envFirst)(env, env_js_1.HTTP_TIMEOUT_VARIABLE),
|
|
63
54
|
baseUrl: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_BASE_URL'),
|
|
64
55
|
authenticateUrl: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_AUTHENTICATE_URL'),
|
|
65
56
|
});
|
|
@@ -71,7 +62,7 @@ const STATUSES = {
|
|
|
71
62
|
INSUFFICIENT_BALANCE: 'pending',
|
|
72
63
|
ACCOUNT_LOCKED: 'failed',
|
|
73
64
|
BILL_COLLECTION_FAILED: 'failed',
|
|
74
|
-
PAYMENT_REQUEST_CANCELLED: '
|
|
65
|
+
PAYMENT_REQUEST_CANCELLED: 'canceled',
|
|
75
66
|
TRANSACTION_TIMED_OUT: 'expired',
|
|
76
67
|
SCHEDULER_TRANSACTION_TIMED_OUT: 'expired',
|
|
77
68
|
};
|
|
@@ -98,7 +89,7 @@ class WaveMoney {
|
|
|
98
89
|
transport;
|
|
99
90
|
constructor(config, options = {}) {
|
|
100
91
|
this.config = config instanceof WaveMoneyConfig ? config : new WaveMoneyConfig(config);
|
|
101
|
-
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options));
|
|
92
|
+
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options, this.config.timeoutSeconds));
|
|
102
93
|
}
|
|
103
94
|
/** A gateway configured from the `WAVE_MONEY_*` environment variables. */
|
|
104
95
|
static fromEnv(env = (0, env_js_1.defaultEnv)(), options = {}) {
|
|
@@ -136,7 +127,11 @@ class WaveMoney {
|
|
|
136
127
|
.url('returnUrl', data.returnUrl)
|
|
137
128
|
.required('description', data.description)
|
|
138
129
|
.when(items.length === 0, 'items', 'The items field must have at least one item.')
|
|
139
|
-
.string('merchantReferenceId', data.merchantReferenceId)
|
|
130
|
+
.string('merchantReferenceId', data.merchantReferenceId)
|
|
131
|
+
.string('merchantName', data.merchantName)
|
|
132
|
+
.when(typeof data.merchantName === 'string' && data.merchantName.trim() === '', 'merchantName', 'The merchantName field is required.')
|
|
133
|
+
.when(isSet(data.timeToLiveSeconds) && !Number.isInteger(data.timeToLiveSeconds), 'timeToLiveSeconds', 'The timeToLiveSeconds field must be an integer.')
|
|
134
|
+
.when(isSet(data.timeToLiveSeconds) && data.timeToLiveSeconds < 1, 'timeToLiveSeconds', 'The timeToLiveSeconds field must be greater than 0.');
|
|
140
135
|
items.forEach((item, index) => {
|
|
141
136
|
validator
|
|
142
137
|
.required(`items.${index}.name`, item?.name)
|
|
@@ -154,15 +149,22 @@ class WaveMoney {
|
|
|
154
149
|
/**
|
|
155
150
|
* Creates a payment request and returns Wave's page to redirect the customer to. Once `data`
|
|
156
151
|
* passes validation it fills `data.merchantReferenceId` with a random id when empty, so store it
|
|
157
|
-
* afterwards. Invalid data is left untouched.
|
|
152
|
+
* afterwards. Invalid data is left untouched. Throws `InvalidPaymentDataError` before any request
|
|
153
|
+
* when neither the payment nor the config sets a time to live.
|
|
158
154
|
*/
|
|
159
155
|
async initiate(data, options = {}) {
|
|
160
156
|
WaveMoney.validate(data);
|
|
157
|
+
const timeToLiveSeconds = data.timeToLiveSeconds ?? this.config.timeToLiveSeconds;
|
|
158
|
+
if (timeToLiveSeconds === undefined) {
|
|
159
|
+
throw new errors_js_1.InvalidPaymentDataError({
|
|
160
|
+
timeToLiveSeconds: 'The timeToLiveSeconds field is required when the wave_money configuration has no [time_to_live_in_seconds].',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
161
163
|
if (!data.merchantReferenceId) {
|
|
162
164
|
data.merchantReferenceId = (0, crypto_js_1.randomHex)();
|
|
163
165
|
}
|
|
164
166
|
const amount = WaveMoney.resolvedAmount(data).toString();
|
|
165
|
-
const ttl = String(
|
|
167
|
+
const ttl = String(timeToLiveSeconds);
|
|
166
168
|
// Wave documents items as [{"name": "...", "amount": 1000}] with a numeric amount; the amount
|
|
167
169
|
// text is written as a JSON number without passing through a float.
|
|
168
170
|
const items = `[${data.items
|
|
@@ -177,7 +179,7 @@ class WaveMoney {
|
|
|
177
179
|
backend_result_url: data.callbackUrl,
|
|
178
180
|
amount,
|
|
179
181
|
payment_description: data.description,
|
|
180
|
-
merchant_name: this.config.merchantName,
|
|
182
|
+
merchant_name: data.merchantName ?? this.config.merchantName,
|
|
181
183
|
items,
|
|
182
184
|
hash: this.hash([
|
|
183
185
|
ttl,
|
|
@@ -209,13 +211,15 @@ class WaveMoney {
|
|
|
209
211
|
/**
|
|
210
212
|
* Verifies Wave's callback. Only `PAYMENT_CONFIRMED` means the customer paid. `orderId` falls
|
|
211
213
|
* back to `merchantReferenceId` when it is missing, null or empty, because Wave marks `orderId` as
|
|
212
|
-
* optional.
|
|
214
|
+
* optional. A hashed field holding an object or array fails verification.
|
|
213
215
|
*/
|
|
214
216
|
handleCallback(request) {
|
|
215
217
|
const payload = (0, callback_js_1.losslessBody)(request);
|
|
216
218
|
const expected = this.hash(CALLBACK_FIELDS.map((field) => (0, values_js_1.scalarString)(payload[field]) ?? 'null'));
|
|
217
219
|
const hashValue = payload.hashValue;
|
|
218
|
-
if (typeof hashValue !== 'string' ||
|
|
220
|
+
if (typeof hashValue !== 'string' ||
|
|
221
|
+
CALLBACK_FIELDS.some((field) => (0, values_js_1.isNested)(payload[field])) ||
|
|
222
|
+
!(0, crypto_js_1.safeEqual)(expected, hashValue.toLowerCase())) {
|
|
219
223
|
throw new errors_js_1.SignatureVerificationError('Wave Money callback hash verification failed.', (0, json_js_1.toPlainObject)(payload));
|
|
220
224
|
}
|
|
221
225
|
const gatewayStatus = (0, values_js_1.trimmed)(payload, 'status');
|
|
@@ -233,6 +237,9 @@ class WaveMoney {
|
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
239
|
exports.WaveMoney = WaveMoney;
|
|
240
|
+
function isSet(value) {
|
|
241
|
+
return value !== undefined && value !== null;
|
|
242
|
+
}
|
|
236
243
|
function errorMessage(body) {
|
|
237
244
|
const errors = body.errors;
|
|
238
245
|
if ((0, json_js_1.isLosslessObject)(errors)) {
|
|
@@ -16,35 +16,39 @@ export interface YomaMmqrConfigOptions {
|
|
|
16
16
|
webhookHashKey: string;
|
|
17
17
|
/** The secret you shared with Yoma; when set, callbacks must carry it in `X-Webhook-Secret`. */
|
|
18
18
|
webhookSecret?: string | undefined;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/**
|
|
19
|
+
/** The `{version}` segment of the API paths, e.g. `v1rc`. */
|
|
20
|
+
apiVersion: string;
|
|
21
|
+
/**
|
|
22
|
+
* Seconds before the default HTTP client gives up on a request, a whole number greater than 0
|
|
23
|
+
* (or its text). Defaults to 30 (`DEFAULT_TIMEOUT_SECONDS`) when unset or blank. A
|
|
24
|
+
* client you pass keeps its own timeout.
|
|
25
|
+
*/
|
|
26
|
+
timeoutSeconds?: number | string | null | undefined;
|
|
27
|
+
/** Overrides the API base URL, e.g. to point at UAT. */
|
|
22
28
|
baseUrl?: string | undefined;
|
|
23
|
-
/** The `{version}` segment of the API paths (default `v1rc`). */
|
|
24
|
-
apiVersion?: string | undefined;
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
27
|
-
* Yoma MMQR credentials and endpoints. A missing
|
|
31
|
+
* Yoma MMQR credentials and endpoints. A missing setting throws a `ConfigurationError`. The URL
|
|
32
|
+
* defaults to production.
|
|
28
33
|
*/
|
|
29
34
|
export declare class YomaMmqrConfig {
|
|
30
|
-
static readonly SANDBOX_URL = "https://devapi.yomabank.net";
|
|
31
35
|
static readonly PRODUCTION_URL = "https://paymenthubapi.yomabank.com";
|
|
32
|
-
static readonly DEFAULT_API_VERSION = "v1rc";
|
|
33
36
|
readonly merchantId: string;
|
|
34
37
|
readonly clientId: string;
|
|
35
38
|
readonly clientSecret: string;
|
|
36
39
|
readonly webhookHashKey: string;
|
|
37
40
|
readonly webhookSecret: string | undefined;
|
|
38
|
-
|
|
41
|
+
/** The `{version}` segment of the API paths. */
|
|
42
|
+
readonly apiVersion: string;
|
|
43
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
44
|
+
readonly timeoutSeconds: number;
|
|
39
45
|
/** The base URL in use. */
|
|
40
46
|
readonly baseUrl: string;
|
|
41
|
-
/** The API version in use. */
|
|
42
|
-
readonly apiVersion: string;
|
|
43
47
|
constructor(options: YomaMmqrConfigOptions);
|
|
44
48
|
/**
|
|
45
49
|
* Reads `YOMA_MMQR_MERCHANT_ID`, `YOMA_MMQR_CLIENT_ID`, `YOMA_MMQR_CLIENT_SECRET`,
|
|
46
|
-
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `
|
|
47
|
-
* `
|
|
50
|
+
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `YOMA_MMQR_API_VERSION`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT`,
|
|
51
|
+
* `YOMA_MMQR_WEBHOOK_SECRET` and `YOMA_MMQR_BASE_URL`.
|
|
48
52
|
*/
|
|
49
53
|
static fromEnv(env?: EnvSource): YomaMmqrConfig;
|
|
50
54
|
}
|
|
@@ -13,37 +13,36 @@ const status_js_1 = require("../core/status.js");
|
|
|
13
13
|
const validate_js_1 = require("../core/validate.js");
|
|
14
14
|
const values_js_1 = require("../core/values.js");
|
|
15
15
|
/**
|
|
16
|
-
* Yoma MMQR credentials and endpoints. A missing
|
|
16
|
+
* Yoma MMQR credentials and endpoints. A missing setting throws a `ConfigurationError`. The URL
|
|
17
|
+
* defaults to production.
|
|
17
18
|
*/
|
|
18
19
|
class YomaMmqrConfig {
|
|
19
|
-
static SANDBOX_URL = 'https://devapi.yomabank.net';
|
|
20
20
|
static PRODUCTION_URL = 'https://paymenthubapi.yomabank.com';
|
|
21
|
-
static DEFAULT_API_VERSION = 'v1rc';
|
|
22
21
|
merchantId;
|
|
23
22
|
clientId;
|
|
24
23
|
clientSecret;
|
|
25
24
|
webhookHashKey;
|
|
26
25
|
webhookSecret;
|
|
27
|
-
|
|
26
|
+
/** The `{version}` segment of the API paths. */
|
|
27
|
+
apiVersion;
|
|
28
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
29
|
+
timeoutSeconds;
|
|
28
30
|
/** The base URL in use. */
|
|
29
31
|
baseUrl;
|
|
30
|
-
/** The API version in use. */
|
|
31
|
-
apiVersion;
|
|
32
32
|
constructor(options) {
|
|
33
33
|
this.merchantId = (0, env_js_1.requireSetting)('yoma_mmqr', 'merchant_id', options.merchantId);
|
|
34
34
|
this.clientId = (0, env_js_1.requireSetting)('yoma_mmqr', 'client_id', options.clientId);
|
|
35
35
|
this.clientSecret = (0, env_js_1.requireSetting)('yoma_mmqr', 'client_secret', options.clientSecret);
|
|
36
36
|
this.webhookHashKey = (0, env_js_1.requireSetting)('yoma_mmqr', 'webhook_hashkey', options.webhookHashKey);
|
|
37
|
+
this.apiVersion = (0, env_js_1.requireSetting)('yoma_mmqr', 'api_version', options.apiVersion);
|
|
38
|
+
this.timeoutSeconds = (0, env_js_1.resolveTimeout)('yoma_mmqr', options.timeoutSeconds);
|
|
37
39
|
this.webhookSecret = (0, env_js_1.optionalSetting)(options.webhookSecret);
|
|
38
|
-
this.
|
|
39
|
-
this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ??
|
|
40
|
-
(this.sandbox ? YomaMmqrConfig.SANDBOX_URL : YomaMmqrConfig.PRODUCTION_URL));
|
|
41
|
-
this.apiVersion = (0, env_js_1.optionalSetting)(options.apiVersion) ?? YomaMmqrConfig.DEFAULT_API_VERSION;
|
|
40
|
+
this.baseUrl = (0, env_js_1.trimUrl)((0, env_js_1.optionalSetting)(options.baseUrl) ?? YomaMmqrConfig.PRODUCTION_URL);
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* Reads `YOMA_MMQR_MERCHANT_ID`, `YOMA_MMQR_CLIENT_ID`, `YOMA_MMQR_CLIENT_SECRET`,
|
|
45
|
-
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `
|
|
46
|
-
* `
|
|
44
|
+
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `YOMA_MMQR_API_VERSION`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT`,
|
|
45
|
+
* `YOMA_MMQR_WEBHOOK_SECRET` and `YOMA_MMQR_BASE_URL`.
|
|
47
46
|
*/
|
|
48
47
|
static fromEnv(env = (0, env_js_1.defaultEnv)()) {
|
|
49
48
|
return new YomaMmqrConfig({
|
|
@@ -51,10 +50,10 @@ class YomaMmqrConfig {
|
|
|
51
50
|
clientId: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_CLIENT_ID'),
|
|
52
51
|
clientSecret: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_CLIENT_SECRET'),
|
|
53
52
|
webhookHashKey: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_WEBHOOK_HASHKEY'),
|
|
53
|
+
apiVersion: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_API_VERSION'),
|
|
54
|
+
timeoutSeconds: (0, env_js_1.envFirst)(env, env_js_1.HTTP_TIMEOUT_VARIABLE),
|
|
54
55
|
webhookSecret: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_WEBHOOK_SECRET'),
|
|
55
|
-
sandbox: (0, env_js_1.envSandbox)(env, 'YOMA_MMQR_SANDBOX'),
|
|
56
56
|
baseUrl: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_BASE_URL'),
|
|
57
|
-
apiVersion: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_API_VERSION'),
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
59
|
}
|
|
@@ -79,7 +78,7 @@ class YomaMmqr {
|
|
|
79
78
|
pendingToken;
|
|
80
79
|
constructor(config, options = {}) {
|
|
81
80
|
this.config = config instanceof YomaMmqrConfig ? config : new YomaMmqrConfig(config);
|
|
82
|
-
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options));
|
|
81
|
+
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options, this.config.timeoutSeconds));
|
|
83
82
|
this.cache = options.tokenCache ?? new cache_js_1.MemoryTokenCache();
|
|
84
83
|
}
|
|
85
84
|
/** A gateway configured from the `YOMA_MMQR_*` environment variables. */
|
|
@@ -161,7 +160,9 @@ class YomaMmqr {
|
|
|
161
160
|
const orderNumber = (0, values_js_1.get)(payload, 'orderNumber');
|
|
162
161
|
const status = (0, values_js_1.trimmed)(payload, 'status');
|
|
163
162
|
const expected = (0, crypto_js_1.hmacSha256Hex)(orderNumber + this.config.webhookHashKey, `orderNumber=${orderNumber}&status=${status}`);
|
|
164
|
-
if (orderNumber === '' ||
|
|
163
|
+
if (orderNumber === '' ||
|
|
164
|
+
(0, values_js_1.isNested)(payload.status) ||
|
|
165
|
+
!(0, crypto_js_1.safeEqual)(expected, (0, values_js_1.get)(payload, 'hashValue').toLowerCase())) {
|
|
165
166
|
throw new errors_js_1.SignatureVerificationError('Yoma MMQR callback hash verification failed.', (0, json_js_1.toPlainObject)(payload));
|
|
166
167
|
}
|
|
167
168
|
return new callback_js_1.PaymentCallback({
|
|
@@ -210,13 +211,14 @@ class YomaMmqr {
|
|
|
210
211
|
if (!response.successful() || token === '') {
|
|
211
212
|
throw apiError('token', response.status, body, (0, values_js_1.get)(body, 'error'));
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
-
const
|
|
214
|
+
// The leading digits of `expires_in`; missing, non-numeric or 0 means an hour.
|
|
215
|
+
const expiresIn = Number(/^\d+/.exec((0, values_js_1.get)(body, 'expires_in'))?.[0] ?? 0);
|
|
216
|
+
const ttl = Math.max(60, (expiresIn > 0 ? expiresIn : 3600) - 60);
|
|
215
217
|
await this.cache.set(this.tokenCacheKey(), token, ttl);
|
|
216
218
|
return token;
|
|
217
219
|
}
|
|
218
220
|
tokenCacheKey() {
|
|
219
|
-
return `
|
|
221
|
+
return `myanmar-payments.yoma-mmqr.token.${(0, crypto_js_1.sha256Hex)(`${this.config.baseUrl}|${this.config.clientId}`)}`;
|
|
220
222
|
}
|
|
221
223
|
}
|
|
222
224
|
exports.YomaMmqr = YomaMmqr;
|