@laranex/myanmar-payments 4.0.0-dev.1 → 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.
- 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 +14 -5
- package/dist/cjs/core/env.js +35 -11
- 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 +27 -11
- package/dist/cjs/kbz-pay/kbz-pay.js +43 -20
- 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 +26 -13
- package/dist/cjs/wave-money/wave-money.js +29 -29
- 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 +14 -5
- package/dist/esm/core/env.js +32 -9
- 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 +27 -11
- package/dist/esm/kbz-pay/kbz-pay.js +45 -22
- 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 +26 -13
- package/dist/esm/wave-money/wave-money.js +31 -31
- 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
|
@@ -11,38 +11,41 @@ export interface WaveMoneyConfigOptions {
|
|
|
11
11
|
secretKey: string;
|
|
12
12
|
/** Your business name, shown on Wave's payment page. */
|
|
13
13
|
merchantName: string;
|
|
14
|
-
/** How long the customer has to pay, in seconds (
|
|
15
|
-
timeToLiveSeconds
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** How long the customer has to pay, in seconds: a whole number greater than 0 (or its text). */
|
|
15
|
+
timeToLiveSeconds: number | string;
|
|
16
|
+
/**
|
|
17
|
+
* Seconds before the default HTTP client gives up on a request, a whole number greater than 0
|
|
18
|
+
* (or its text). Defaults to 30 (`DEFAULT_TIMEOUT_SECONDS`) when unset or blank. A
|
|
19
|
+
* client you pass keeps its own timeout.
|
|
20
|
+
*/
|
|
21
|
+
timeoutSeconds?: number | string | null | undefined;
|
|
22
|
+
/** Overrides the API base URL, e.g. to point at UAT. */
|
|
19
23
|
baseUrl?: string | undefined;
|
|
20
24
|
/** Overrides the host the customer is redirected to. Wave serves it without the API port. */
|
|
21
25
|
authenticateUrl?: string | undefined;
|
|
22
26
|
}
|
|
23
27
|
/**
|
|
24
|
-
* Wave Money credentials and endpoints. A missing
|
|
28
|
+
* Wave Money credentials and endpoints. A missing setting throws a `ConfigurationError`. The URLs
|
|
29
|
+
* default to production.
|
|
25
30
|
*/
|
|
26
31
|
export declare class WaveMoneyConfig {
|
|
27
|
-
static readonly SANDBOX_URL = "https://preprodpayments.wavemoney.io:8107";
|
|
28
32
|
static readonly PRODUCTION_URL = "https://payments.wavemoney.io";
|
|
29
|
-
static readonly SANDBOX_AUTHENTICATE_URL = "https://preprodpayments.wavemoney.io";
|
|
30
33
|
static readonly PRODUCTION_AUTHENTICATE_URL = "https://payments.wavemoney.io";
|
|
31
|
-
static readonly DEFAULT_TIME_TO_LIVE_SECONDS = 300;
|
|
32
34
|
readonly merchantId: string;
|
|
33
35
|
readonly secretKey: string;
|
|
34
36
|
readonly merchantName: string;
|
|
35
37
|
/** Seconds the customer has to pay. */
|
|
36
38
|
readonly timeToLiveSeconds: number;
|
|
37
|
-
|
|
39
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
40
|
+
readonly timeoutSeconds: number;
|
|
38
41
|
/** The API base URL in use. */
|
|
39
42
|
readonly baseUrl: string;
|
|
40
43
|
/** The host the customer is redirected to. */
|
|
41
44
|
readonly authenticateUrl: string;
|
|
42
45
|
constructor(options: WaveMoneyConfigOptions);
|
|
43
46
|
/**
|
|
44
|
-
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME
|
|
45
|
-
*
|
|
47
|
+
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME`,
|
|
48
|
+
* `WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT`, `WAVE_MONEY_BASE_URL`
|
|
46
49
|
* and `WAVE_MONEY_AUTHENTICATE_URL`.
|
|
47
50
|
*/
|
|
48
51
|
static fromEnv(env?: EnvSource): WaveMoneyConfig;
|
|
@@ -76,6 +79,16 @@ export interface WaveMoneyPaymentData {
|
|
|
76
79
|
* this.
|
|
77
80
|
*/
|
|
78
81
|
merchantReferenceId?: string | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* The business name shown on Wave's page for this payment. Defaults to the configured
|
|
84
|
+
* `merchantName` when unset.
|
|
85
|
+
*/
|
|
86
|
+
merchantName?: string | null | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* How long the customer has to pay this payment, in seconds: a whole number greater than 0.
|
|
89
|
+
* Defaults to the configured `timeToLiveSeconds` when unset.
|
|
90
|
+
*/
|
|
91
|
+
timeToLiveSeconds?: number | null | undefined;
|
|
79
92
|
}
|
|
80
93
|
/**
|
|
81
94
|
* Wave Money (WavePay payment gateway): redirect payments and callbacks. Wave has no status API,
|
|
@@ -104,7 +117,7 @@ export declare class WaveMoney {
|
|
|
104
117
|
/**
|
|
105
118
|
* Verifies Wave's callback. Only `PAYMENT_CONFIRMED` means the customer paid. `orderId` falls
|
|
106
119
|
* back to `merchantReferenceId` when it is missing, null or empty, because Wave marks `orderId` as
|
|
107
|
-
* optional.
|
|
120
|
+
* optional. A hashed field holding an object or array fails verification.
|
|
108
121
|
*/
|
|
109
122
|
handleCallback(request: CallbackRequest): PaymentCallback;
|
|
110
123
|
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
25
|
/** Seconds the customer has to pay. */
|
|
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.requireSeconds)('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)
|
|
@@ -162,7 +157,7 @@ class WaveMoney {
|
|
|
162
157
|
data.merchantReferenceId = (0, crypto_js_1.randomHex)();
|
|
163
158
|
}
|
|
164
159
|
const amount = WaveMoney.resolvedAmount(data).toString();
|
|
165
|
-
const ttl = String(this.config.timeToLiveSeconds);
|
|
160
|
+
const ttl = String(data.timeToLiveSeconds ?? this.config.timeToLiveSeconds);
|
|
166
161
|
// Wave documents items as [{"name": "...", "amount": 1000}] with a numeric amount; the amount
|
|
167
162
|
// text is written as a JSON number without passing through a float.
|
|
168
163
|
const items = `[${data.items
|
|
@@ -177,7 +172,7 @@ class WaveMoney {
|
|
|
177
172
|
backend_result_url: data.callbackUrl,
|
|
178
173
|
amount,
|
|
179
174
|
payment_description: data.description,
|
|
180
|
-
merchant_name: this.config.merchantName,
|
|
175
|
+
merchant_name: data.merchantName ?? this.config.merchantName,
|
|
181
176
|
items,
|
|
182
177
|
hash: this.hash([
|
|
183
178
|
ttl,
|
|
@@ -209,13 +204,15 @@ class WaveMoney {
|
|
|
209
204
|
/**
|
|
210
205
|
* Verifies Wave's callback. Only `PAYMENT_CONFIRMED` means the customer paid. `orderId` falls
|
|
211
206
|
* back to `merchantReferenceId` when it is missing, null or empty, because Wave marks `orderId` as
|
|
212
|
-
* optional.
|
|
207
|
+
* optional. A hashed field holding an object or array fails verification.
|
|
213
208
|
*/
|
|
214
209
|
handleCallback(request) {
|
|
215
210
|
const payload = (0, callback_js_1.losslessBody)(request);
|
|
216
211
|
const expected = this.hash(CALLBACK_FIELDS.map((field) => (0, values_js_1.scalarString)(payload[field]) ?? 'null'));
|
|
217
212
|
const hashValue = payload.hashValue;
|
|
218
|
-
if (typeof hashValue !== 'string' ||
|
|
213
|
+
if (typeof hashValue !== 'string' ||
|
|
214
|
+
CALLBACK_FIELDS.some((field) => (0, values_js_1.isNested)(payload[field])) ||
|
|
215
|
+
!(0, crypto_js_1.safeEqual)(expected, hashValue.toLowerCase())) {
|
|
219
216
|
throw new errors_js_1.SignatureVerificationError('Wave Money callback hash verification failed.', (0, json_js_1.toPlainObject)(payload));
|
|
220
217
|
}
|
|
221
218
|
const gatewayStatus = (0, values_js_1.trimmed)(payload, 'status');
|
|
@@ -233,6 +230,9 @@ class WaveMoney {
|
|
|
233
230
|
}
|
|
234
231
|
}
|
|
235
232
|
exports.WaveMoney = WaveMoney;
|
|
233
|
+
function isSet(value) {
|
|
234
|
+
return value !== undefined && value !== null;
|
|
235
|
+
}
|
|
236
236
|
function errorMessage(body) {
|
|
237
237
|
const errors = body.errors;
|
|
238
238
|
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;
|
|
@@ -9,27 +9,31 @@ export interface AyaPayConfigOptions {
|
|
|
9
9
|
appKey: string;
|
|
10
10
|
/** The secret that signs requests and verifies callbacks. */
|
|
11
11
|
appSecret: string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Seconds before the default HTTP client gives up on a request, a whole number greater than 0
|
|
14
|
+
* (or its text). Defaults to 30 (`DEFAULT_TIMEOUT_SECONDS`) when unset or blank. A
|
|
15
|
+
* client you pass keeps its own timeout.
|
|
16
|
+
*/
|
|
17
|
+
timeoutSeconds?: number | string | null | undefined;
|
|
18
|
+
/** Overrides the gateway base URL, e.g. to point at UAT. */
|
|
15
19
|
baseUrl?: string | undefined;
|
|
16
20
|
}
|
|
17
21
|
/**
|
|
18
|
-
* AYA Payment Gateway (APG) credentials and endpoints. A missing
|
|
19
|
-
* `ConfigurationError`.
|
|
22
|
+
* AYA Payment Gateway (APG) credentials and endpoints. A missing setting throws a
|
|
23
|
+
* `ConfigurationError`. The URL defaults to production.
|
|
20
24
|
*/
|
|
21
25
|
export declare class AyaPayConfig {
|
|
22
|
-
static readonly SANDBOX_URL = "https://uat-pgw.ayainnovation.com";
|
|
23
26
|
static readonly PRODUCTION_URL = "https://pgw.ayainnovation.com";
|
|
24
27
|
readonly appKey: string;
|
|
25
28
|
readonly appSecret: string;
|
|
26
|
-
|
|
29
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
30
|
+
readonly timeoutSeconds: number;
|
|
27
31
|
/** The base URL in use. */
|
|
28
32
|
readonly baseUrl: string;
|
|
29
33
|
constructor(options: AyaPayConfigOptions);
|
|
30
34
|
/**
|
|
31
|
-
* Reads `AYA_PAY_APP_KEY`, `AYA_PAY_APP_SECRET`, `
|
|
32
|
-
* back to the `AYA_PGW_*` names.
|
|
35
|
+
* Reads `AYA_PAY_APP_KEY`, `AYA_PAY_APP_SECRET`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT` and
|
|
36
|
+
* `AYA_PAY_BASE_URL`, falling back to the `AYA_PGW_*` names.
|
|
33
37
|
*/
|
|
34
38
|
static fromEnv(env?: EnvSource): AyaPayConfig;
|
|
35
39
|
}
|
|
@@ -1,42 +1,41 @@
|
|
|
1
1
|
import {} from '../core/amount.js';
|
|
2
2
|
import { CallbackRequest, losslessInput, losslessQueryInput, PaymentCallback, PaymentStatusResult, } from '../core/callback.js';
|
|
3
3
|
import { decodeBase64, hmacSha256Hex, safeEqual } from '../core/crypto.js';
|
|
4
|
-
import { defaultEnv, envFirst,
|
|
4
|
+
import { defaultEnv, envFirst, HTTP_TIMEOUT_VARIABLE, optionalSetting, requireSetting, resolveTimeout, trimUrl, } from '../core/env.js';
|
|
5
5
|
import { ApiError, SignatureVerificationError } from '../core/errors.js';
|
|
6
6
|
import { httpClientFrom, Transport, } from '../core/http.js';
|
|
7
7
|
import { isLosslessObject, parseJsonObject, toPlainObject, } from '../core/json.js';
|
|
8
8
|
import { FormPayment } from '../core/results.js';
|
|
9
9
|
import { resolveStatus } from '../core/status.js';
|
|
10
10
|
import { toAmount, Validator } from '../core/validate.js';
|
|
11
|
-
import { get, object, optional, scalarString, trimmed } from '../core/values.js';
|
|
11
|
+
import { get, isNested, object, optional, scalarString, trimmed } from '../core/values.js';
|
|
12
12
|
/**
|
|
13
|
-
* AYA Payment Gateway (APG) credentials and endpoints. A missing
|
|
14
|
-
* `ConfigurationError`.
|
|
13
|
+
* AYA Payment Gateway (APG) credentials and endpoints. A missing setting throws a
|
|
14
|
+
* `ConfigurationError`. The URL defaults to production.
|
|
15
15
|
*/
|
|
16
16
|
export class AyaPayConfig {
|
|
17
|
-
static SANDBOX_URL = 'https://uat-pgw.ayainnovation.com';
|
|
18
17
|
static PRODUCTION_URL = 'https://pgw.ayainnovation.com';
|
|
19
18
|
appKey;
|
|
20
19
|
appSecret;
|
|
21
|
-
|
|
20
|
+
/** Seconds before the default HTTP client gives up on a request. */
|
|
21
|
+
timeoutSeconds;
|
|
22
22
|
/** The base URL in use. */
|
|
23
23
|
baseUrl;
|
|
24
24
|
constructor(options) {
|
|
25
25
|
this.appKey = requireSetting('aya_pay', 'app_key', options.appKey);
|
|
26
26
|
this.appSecret = requireSetting('aya_pay', 'app_secret', options.appSecret);
|
|
27
|
-
this.
|
|
28
|
-
this.baseUrl = trimUrl(optionalSetting(options.baseUrl) ??
|
|
29
|
-
(this.sandbox ? AyaPayConfig.SANDBOX_URL : AyaPayConfig.PRODUCTION_URL));
|
|
27
|
+
this.timeoutSeconds = resolveTimeout('aya_pay', options.timeoutSeconds);
|
|
28
|
+
this.baseUrl = trimUrl(optionalSetting(options.baseUrl) ?? AyaPayConfig.PRODUCTION_URL);
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
|
-
* Reads `AYA_PAY_APP_KEY`, `AYA_PAY_APP_SECRET`, `
|
|
33
|
-
* back to the `AYA_PGW_*` names.
|
|
31
|
+
* Reads `AYA_PAY_APP_KEY`, `AYA_PAY_APP_SECRET`, `MYANMAR_PAYMENTS_HTTP_TIMEOUT` and
|
|
32
|
+
* `AYA_PAY_BASE_URL`, falling back to the `AYA_PGW_*` names.
|
|
34
33
|
*/
|
|
35
34
|
static fromEnv(env = defaultEnv()) {
|
|
36
35
|
return new AyaPayConfig({
|
|
37
36
|
appKey: envFirst(env, 'AYA_PAY_APP_KEY', 'AYA_PGW_APP_KEY'),
|
|
38
37
|
appSecret: envFirst(env, 'AYA_PAY_APP_SECRET', 'AYA_PGW_APP_SECRET'),
|
|
39
|
-
|
|
38
|
+
timeoutSeconds: envFirst(env, HTTP_TIMEOUT_VARIABLE),
|
|
40
39
|
baseUrl: envFirst(env, 'AYA_PAY_BASE_URL', 'AYA_PGW_BASE_URL'),
|
|
41
40
|
});
|
|
42
41
|
}
|
|
@@ -119,7 +118,7 @@ export class AyaPay {
|
|
|
119
118
|
transport;
|
|
120
119
|
constructor(config, options = {}) {
|
|
121
120
|
this.config = config instanceof AyaPayConfig ? config : new AyaPayConfig(config);
|
|
122
|
-
this.transport = new Transport(httpClientFrom(options));
|
|
121
|
+
this.transport = new Transport(httpClientFrom(options, this.config.timeoutSeconds));
|
|
123
122
|
}
|
|
124
123
|
/** A gateway configured from the `AYA_PAY_*` (or `AYA_PGW_*`) environment variables. */
|
|
125
124
|
static fromEnv(env = defaultEnv(), options = {}) {
|
|
@@ -242,7 +241,8 @@ export class AyaPay {
|
|
|
242
241
|
/** Decodes a `payload` + `checkSum` pair and returns the payload if the checksum matches. */
|
|
243
242
|
verifiedPayload(input, context) {
|
|
244
243
|
const fail = () => new SignatureVerificationError(`AYA Pay ${context} checksum verification failed.`, toPlainObject(input));
|
|
245
|
-
|
|
244
|
+
// Base64 never contains spaces: a space is a `+` that an unencoded query string turned into one.
|
|
245
|
+
const json = decodeBase64(get(input, 'payload').replace(/ /g, '+'));
|
|
246
246
|
const payload = json === undefined ? undefined : parseJsonObject(json);
|
|
247
247
|
if (payload === undefined) {
|
|
248
248
|
throw fail();
|
|
@@ -251,6 +251,10 @@ export class AyaPay {
|
|
|
251
251
|
for (const field of PAYLOAD_FIELDS) {
|
|
252
252
|
const key = field === 'currencyCode' && 'currenyCode' in payload ? 'currenyCode' : field;
|
|
253
253
|
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
254
|
+
// A nested value is never signed, so a payload carrying one is rejected.
|
|
255
|
+
if (isNested(payload[key])) {
|
|
256
|
+
throw fail();
|
|
257
|
+
}
|
|
254
258
|
parts.push(scalarString(payload[key]) ?? '');
|
|
255
259
|
}
|
|
256
260
|
}
|
|
@@ -43,9 +43,10 @@ export declare class Amount {
|
|
|
43
43
|
/** Whether the amount is greater than zero. */
|
|
44
44
|
isPositive(): boolean;
|
|
45
45
|
/**
|
|
46
|
-
* Whether both amounts have the same value
|
|
47
|
-
* (`1000`, `
|
|
48
|
-
*
|
|
46
|
+
* Whether both amounts have the same value. Leading zeros of the whole part and trailing zeros of
|
|
47
|
+
* the fraction are ignored (`1000`, `01000` and `1000.00` are equal). Text must be plain digits
|
|
48
|
+
* with an optional fraction, so `1,000` or ` 1000` is never equal, and neither is `null` or
|
|
49
|
+
* `undefined` (a gateway amount that was not sent never matches).
|
|
49
50
|
*/
|
|
50
51
|
equals(other: Amount | string | null | undefined): boolean;
|
|
51
52
|
}
|
package/dist/esm/core/amount.js
CHANGED
|
@@ -85,19 +85,28 @@ export class Amount {
|
|
|
85
85
|
return !this.isZero();
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
|
-
* Whether both amounts have the same value
|
|
89
|
-
* (`1000`, `
|
|
90
|
-
*
|
|
88
|
+
* Whether both amounts have the same value. Leading zeros of the whole part and trailing zeros of
|
|
89
|
+
* the fraction are ignored (`1000`, `01000` and `1000.00` are equal). Text must be plain digits
|
|
90
|
+
* with an optional fraction, so `1,000` or ` 1000` is never equal, and neither is `null` or
|
|
91
|
+
* `undefined` (a gateway amount that was not sent never matches).
|
|
91
92
|
*/
|
|
92
93
|
equals(other) {
|
|
93
94
|
if (other === undefined || other === null) {
|
|
94
95
|
return false;
|
|
95
96
|
}
|
|
96
|
-
const normalize = (value) => value.includes('.') ? value.replace(/0+$/, '').replace(/\.$/, '') : value;
|
|
97
97
|
const text = other instanceof Amount ? other.value : other;
|
|
98
|
+
if (typeof text !== 'string' || !PATTERN.test(text)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
98
101
|
return normalize(this.value) === normalize(text);
|
|
99
102
|
}
|
|
100
103
|
}
|
|
104
|
+
function normalize(value) {
|
|
105
|
+
const [whole = '', fraction = ''] = value.split('.');
|
|
106
|
+
const trimmedWhole = whole.replace(/^0+/, '') || '0';
|
|
107
|
+
const trimmedFraction = fraction.replace(/0+$/, '');
|
|
108
|
+
return trimmedFraction === '' ? trimmedWhole : `${trimmedWhole}.${trimmedFraction}`;
|
|
109
|
+
}
|
|
101
110
|
function invalid(message) {
|
|
102
111
|
return new InvalidPaymentDataError({ amount: message });
|
|
103
112
|
}
|
|
@@ -16,8 +16,8 @@ export interface CallbackRequestInit {
|
|
|
16
16
|
query?: QueryInput | null | undefined;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* The parts of a Node `http.IncomingMessage` this package reads, so Express
|
|
20
|
-
*
|
|
19
|
+
* The parts of a Node `http.IncomingMessage` this package reads, so Express requests work too (and
|
|
20
|
+
* Koa's `ctx.req` when no body parser consumed it).
|
|
21
21
|
*/
|
|
22
22
|
export interface NodeRequestLike extends AsyncIterable<unknown> {
|
|
23
23
|
headers: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
@@ -42,8 +42,10 @@ export interface WebRequestLike {
|
|
|
42
42
|
* {@link CallbackRequest.fromNodeRequest} or {@link CallbackRequest.fromWebRequest}.
|
|
43
43
|
*/
|
|
44
44
|
export declare class CallbackRequest {
|
|
45
|
-
/** The
|
|
45
|
+
/** The request body as text: a string body as given, bytes decoded as UTF-8. */
|
|
46
46
|
readonly body: string;
|
|
47
|
+
/** The exact bytes received. A string body is encoded as UTF-8. */
|
|
48
|
+
readonly rawBody: Uint8Array;
|
|
47
49
|
/** The request headers, with lowercase names. Repeated headers are joined with `, `. */
|
|
48
50
|
readonly headers: Readonly<Record<string, string>>;
|
|
49
51
|
/** The query string parameters (the first value of each). */
|
|
@@ -57,10 +59,11 @@ export declare class CallbackRequest {
|
|
|
57
59
|
*/
|
|
58
60
|
static fromJson(payload: unknown, headers?: HeadersInput | null): CallbackRequest;
|
|
59
61
|
/**
|
|
60
|
-
* Reads a Node `http.IncomingMessage` (also Express `req`,
|
|
61
|
-
* Reads the raw body from the stream, or from `rawBody`/`body` when middleware captured
|
|
62
|
-
* text or bytes. When a body parser already consumed the stream, the parsed `body` is
|
|
63
|
-
* again as JSON or a form.
|
|
62
|
+
* Reads a Node `http.IncomingMessage` (also Express `req`, or Koa `ctx.req` without a body
|
|
63
|
+
* parser). Reads the raw body from the stream, or from `rawBody`/`body` when middleware captured
|
|
64
|
+
* it as text or bytes. When a body parser already consumed the stream, the parsed `body` is
|
|
65
|
+
* encoded again as JSON or a form. Fastify parses the body before your handler runs: use
|
|
66
|
+
* {@link CallbackRequest.from} with the raw body there.
|
|
64
67
|
*/
|
|
65
68
|
static fromNodeRequest(request: NodeRequestLike): Promise<CallbackRequest>;
|
|
66
69
|
/**
|
|
@@ -70,7 +73,10 @@ export declare class CallbackRequest {
|
|
|
70
73
|
static fromWebRequest(request: WebRequestLike): Promise<CallbackRequest>;
|
|
71
74
|
/** The named header, matched case-insensitively, or `undefined`. */
|
|
72
75
|
header(name: string): string | undefined;
|
|
73
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* The body decoded as JSON or as a urlencoded form. JSON numbers keep their exact text, as strings
|
|
78
|
+
* (`1000.50` stays `"1000.50"`).
|
|
79
|
+
*/
|
|
74
80
|
parsedBody(): Record<string, unknown>;
|
|
75
81
|
/** The parsed body merged over the query string. */
|
|
76
82
|
input(): Record<string, unknown>;
|