@laranex/myanmar-payments 0.0.0-stage → 4.0.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE.md +21 -0
- package/README.md +10 -2
- package/dist/cjs/aya-pay/aya-pay.d.ts +122 -0
- package/dist/cjs/aya-pay/aya-pay.js +301 -0
- package/dist/cjs/aya-pay/index.d.ts +1 -0
- package/dist/cjs/aya-pay/index.js +8 -0
- package/dist/cjs/core/amount.d.ts +57 -0
- package/dist/cjs/core/amount.js +107 -0
- package/dist/cjs/core/cache.d.ts +26 -0
- package/dist/cjs/core/cache.js +35 -0
- package/dist/cjs/core/callback.d.ts +180 -0
- package/dist/cjs/core/callback.js +304 -0
- package/dist/cjs/core/crypto.d.ts +19 -0
- package/dist/cjs/core/crypto.js +50 -0
- package/dist/cjs/core/env.d.ts +21 -0
- package/dist/cjs/core/env.js +54 -0
- package/dist/cjs/core/errors.d.ts +56 -0
- package/dist/cjs/core/errors.js +78 -0
- package/dist/cjs/core/http.d.ts +81 -0
- package/dist/cjs/core/http.js +128 -0
- package/dist/cjs/core/index.d.ts +8 -0
- package/dist/cjs/core/index.js +30 -0
- package/dist/cjs/core/json.d.ts +44 -0
- package/dist/cjs/core/json.js +254 -0
- package/dist/cjs/core/results.d.ts +122 -0
- package/dist/cjs/core/results.js +147 -0
- package/dist/cjs/core/status.d.ts +50 -0
- package/dist/cjs/core/status.js +60 -0
- package/dist/cjs/core/validate.d.ts +46 -0
- package/dist/cjs/core/validate.js +148 -0
- package/dist/cjs/core/values.d.ts +16 -0
- package/dist/cjs/core/values.js +45 -0
- package/dist/cjs/cyber-source/cyber-source.d.ts +95 -0
- package/dist/cjs/cyber-source/cyber-source.js +188 -0
- package/dist/cjs/cyber-source/index.d.ts +1 -0
- package/dist/cjs/cyber-source/index.js +7 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/kbz-pay/index.d.ts +2 -0
- package/dist/cjs/kbz-pay/index.js +8 -0
- package/dist/cjs/kbz-pay/kbz-pay.d.ts +102 -0
- package/dist/cjs/kbz-pay/kbz-pay.js +266 -0
- package/dist/cjs/kbz-pay/signer.d.ts +15 -0
- package/dist/cjs/kbz-pay/signer.js +36 -0
- package/dist/cjs/myanmar-payments.d.ts +50 -0
- package/dist/cjs/myanmar-payments.js +73 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/wave-money/index.d.ts +1 -0
- package/dist/cjs/wave-money/index.js +6 -0
- package/dist/cjs/wave-money/wave-money.d.ts +111 -0
- package/dist/cjs/wave-money/wave-money.js +251 -0
- package/dist/cjs/yoma-mmqr/index.d.ts +1 -0
- package/dist/cjs/yoma-mmqr/index.js +6 -0
- package/dist/cjs/yoma-mmqr/yoma-mmqr.d.ts +105 -0
- package/dist/cjs/yoma-mmqr/yoma-mmqr.js +233 -0
- package/dist/esm/aya-pay/aya-pay.d.ts +122 -0
- package/dist/esm/aya-pay/aya-pay.js +296 -0
- package/dist/esm/aya-pay/index.d.ts +1 -0
- package/dist/esm/aya-pay/index.js +1 -0
- package/dist/esm/core/amount.d.ts +57 -0
- package/dist/esm/core/amount.js +103 -0
- package/dist/esm/core/cache.d.ts +26 -0
- package/dist/esm/core/cache.js +31 -0
- package/dist/esm/core/callback.d.ts +180 -0
- package/dist/esm/core/callback.js +294 -0
- package/dist/esm/core/crypto.d.ts +19 -0
- package/dist/esm/core/crypto.js +41 -0
- package/dist/esm/core/env.d.ts +21 -0
- package/dist/esm/core/env.js +45 -0
- package/dist/esm/core/errors.d.ts +56 -0
- package/dist/esm/core/errors.js +70 -0
- package/dist/esm/core/http.d.ts +81 -0
- package/dist/esm/core/http.js +121 -0
- package/dist/esm/core/index.d.ts +8 -0
- package/dist/esm/core/index.js +7 -0
- package/dist/esm/core/json.d.ts +44 -0
- package/dist/esm/core/json.js +244 -0
- package/dist/esm/core/results.d.ts +122 -0
- package/dist/esm/core/results.js +139 -0
- package/dist/esm/core/status.d.ts +50 -0
- package/dist/esm/core/status.js +56 -0
- package/dist/esm/core/validate.d.ts +46 -0
- package/dist/esm/core/validate.js +142 -0
- package/dist/esm/core/values.d.ts +16 -0
- package/dist/esm/core/values.js +38 -0
- package/dist/esm/cyber-source/cyber-source.d.ts +95 -0
- package/dist/esm/cyber-source/cyber-source.js +184 -0
- package/dist/esm/cyber-source/index.d.ts +1 -0
- package/dist/esm/cyber-source/index.js +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/kbz-pay/index.d.ts +2 -0
- package/dist/esm/kbz-pay/index.js +2 -0
- package/dist/esm/kbz-pay/kbz-pay.d.ts +102 -0
- package/dist/esm/kbz-pay/kbz-pay.js +262 -0
- package/dist/esm/kbz-pay/signer.d.ts +15 -0
- package/dist/esm/kbz-pay/signer.js +32 -0
- package/dist/esm/myanmar-payments.d.ts +50 -0
- package/dist/esm/myanmar-payments.js +69 -0
- package/dist/esm/wave-money/index.d.ts +1 -0
- package/dist/esm/wave-money/index.js +1 -0
- package/dist/esm/wave-money/wave-money.d.ts +111 -0
- package/dist/esm/wave-money/wave-money.js +246 -0
- package/dist/esm/yoma-mmqr/index.d.ts +1 -0
- package/dist/esm/yoma-mmqr/index.js +1 -0
- package/dist/esm/yoma-mmqr/yoma-mmqr.d.ts +105 -0
- package/dist/esm/yoma-mmqr/yoma-mmqr.js +229 -0
- package/package.json +151 -4
- package/skills/node-myanmar-payments/SKILL.md +109 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WaveMoney = exports.WaveMoneyConfig = void 0;
|
|
4
|
+
const amount_js_1 = require("../core/amount.js");
|
|
5
|
+
const callback_js_1 = require("../core/callback.js");
|
|
6
|
+
const crypto_js_1 = require("../core/crypto.js");
|
|
7
|
+
const env_js_1 = require("../core/env.js");
|
|
8
|
+
const errors_js_1 = require("../core/errors.js");
|
|
9
|
+
const http_js_1 = require("../core/http.js");
|
|
10
|
+
const json_js_1 = require("../core/json.js");
|
|
11
|
+
const results_js_1 = require("../core/results.js");
|
|
12
|
+
const status_js_1 = require("../core/status.js");
|
|
13
|
+
const validate_js_1 = require("../core/validate.js");
|
|
14
|
+
const values_js_1 = require("../core/values.js");
|
|
15
|
+
/**
|
|
16
|
+
* Wave Money credentials and endpoints. A missing credential throws a `ConfigurationError`.
|
|
17
|
+
*/
|
|
18
|
+
class WaveMoneyConfig {
|
|
19
|
+
static SANDBOX_URL = 'https://preprodpayments.wavemoney.io:8107';
|
|
20
|
+
static PRODUCTION_URL = 'https://payments.wavemoney.io';
|
|
21
|
+
static SANDBOX_AUTHENTICATE_URL = 'https://preprodpayments.wavemoney.io';
|
|
22
|
+
static PRODUCTION_AUTHENTICATE_URL = 'https://payments.wavemoney.io';
|
|
23
|
+
static DEFAULT_TIME_TO_LIVE_SECONDS = 300;
|
|
24
|
+
merchantId;
|
|
25
|
+
secretKey;
|
|
26
|
+
merchantName;
|
|
27
|
+
/** Seconds the customer has to pay. */
|
|
28
|
+
timeToLiveSeconds;
|
|
29
|
+
sandbox;
|
|
30
|
+
/** The API base URL in use. */
|
|
31
|
+
baseUrl;
|
|
32
|
+
/** The host the customer is redirected to. */
|
|
33
|
+
authenticateUrl;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.merchantId = (0, env_js_1.requireSetting)('wave_money', 'merchant_id', options.merchantId);
|
|
36
|
+
this.secretKey = (0, env_js_1.requireSetting)('wave_money', 'secret_key', options.secretKey);
|
|
37
|
+
this.merchantName = (0, env_js_1.requireSetting)('wave_money', 'merchant_name', options.merchantName);
|
|
38
|
+
const ttl = options.timeToLiveSeconds;
|
|
39
|
+
this.timeToLiveSeconds =
|
|
40
|
+
ttl !== undefined && Number.isInteger(ttl) && ttl > 0
|
|
41
|
+
? ttl
|
|
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));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reads `WAVE_MONEY_MERCHANT_ID`, `WAVE_MONEY_SECRET_KEY`, `WAVE_MONEY_MERCHANT_NAME` (falling back
|
|
53
|
+
* to `APP_NAME`), `WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS`, `WAVE_MONEY_SANDBOX`, `WAVE_MONEY_BASE_URL`
|
|
54
|
+
* and `WAVE_MONEY_AUTHENTICATE_URL`.
|
|
55
|
+
*/
|
|
56
|
+
static fromEnv(env = (0, env_js_1.defaultEnv)()) {
|
|
57
|
+
return new WaveMoneyConfig({
|
|
58
|
+
merchantId: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_MERCHANT_ID'),
|
|
59
|
+
secretKey: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_SECRET_KEY'),
|
|
60
|
+
merchantName: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_MERCHANT_NAME', 'APP_NAME'),
|
|
61
|
+
timeToLiveSeconds: (0, env_js_1.envInt)(env, 'WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS'),
|
|
62
|
+
sandbox: (0, env_js_1.envSandbox)(env, 'WAVE_MONEY_SANDBOX'),
|
|
63
|
+
baseUrl: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_BASE_URL'),
|
|
64
|
+
authenticateUrl: (0, env_js_1.envFirst)(env, 'WAVE_MONEY_AUTHENTICATE_URL'),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.WaveMoneyConfig = WaveMoneyConfig;
|
|
69
|
+
const STATUSES = {
|
|
70
|
+
PAYMENT_CONFIRMED: 'successful',
|
|
71
|
+
INSUFFICIENT_BALANCE: 'pending',
|
|
72
|
+
ACCOUNT_LOCKED: 'failed',
|
|
73
|
+
BILL_COLLECTION_FAILED: 'failed',
|
|
74
|
+
PAYMENT_REQUEST_CANCELLED: 'cancelled',
|
|
75
|
+
TRANSACTION_TIMED_OUT: 'expired',
|
|
76
|
+
SCHEDULER_TRANSACTION_TIMED_OUT: 'expired',
|
|
77
|
+
};
|
|
78
|
+
const CALLBACK_FIELDS = [
|
|
79
|
+
'status',
|
|
80
|
+
'timeToLiveSeconds',
|
|
81
|
+
'merchantId',
|
|
82
|
+
'orderId',
|
|
83
|
+
'amount',
|
|
84
|
+
'backendResultUrl',
|
|
85
|
+
'merchantReferenceId',
|
|
86
|
+
'initiatorMsisdn',
|
|
87
|
+
'transactionId',
|
|
88
|
+
'paymentRequestId',
|
|
89
|
+
'requestTime',
|
|
90
|
+
];
|
|
91
|
+
/**
|
|
92
|
+
* Wave Money (WavePay payment gateway): redirect payments and callbacks. Wave has no status API,
|
|
93
|
+
* so the callback is the only result.
|
|
94
|
+
*/
|
|
95
|
+
class WaveMoney {
|
|
96
|
+
/** The configuration in use. */
|
|
97
|
+
config;
|
|
98
|
+
transport;
|
|
99
|
+
constructor(config, options = {}) {
|
|
100
|
+
this.config = config instanceof WaveMoneyConfig ? config : new WaveMoneyConfig(config);
|
|
101
|
+
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options));
|
|
102
|
+
}
|
|
103
|
+
/** A gateway configured from the `WAVE_MONEY_*` environment variables. */
|
|
104
|
+
static fromEnv(env = (0, env_js_1.defaultEnv)(), options = {}) {
|
|
105
|
+
return new WaveMoney(WaveMoneyConfig.fromEnv(env), options);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The total that will be charged: `amount`, or the exact sum of the items when it is unset.
|
|
109
|
+
* `undefined` when an item amount is missing, malformed or has decimals.
|
|
110
|
+
*/
|
|
111
|
+
static resolvedAmount(data) {
|
|
112
|
+
if (data.amount !== undefined) {
|
|
113
|
+
return (0, validate_js_1.toAmount)(data.amount);
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(data.items) || data.items.length === 0) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
let total = 0n;
|
|
119
|
+
for (const item of data.items) {
|
|
120
|
+
const amount = (0, validate_js_1.toAmount)(item?.amount);
|
|
121
|
+
if (amount === undefined || amount.decimalPlaces() > 0) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
total += BigInt(amount.toString());
|
|
125
|
+
}
|
|
126
|
+
return amount_js_1.Amount.kyat(total);
|
|
127
|
+
}
|
|
128
|
+
/** Checks the request against Wave's documented rules; throws an `InvalidPaymentDataError`. */
|
|
129
|
+
static validate(data) {
|
|
130
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
131
|
+
const validator = new validate_js_1.Validator()
|
|
132
|
+
.required('orderId', data.orderId)
|
|
133
|
+
.required('callbackUrl', data.callbackUrl)
|
|
134
|
+
.url('callbackUrl', data.callbackUrl)
|
|
135
|
+
.required('returnUrl', data.returnUrl)
|
|
136
|
+
.url('returnUrl', data.returnUrl)
|
|
137
|
+
.required('description', data.description)
|
|
138
|
+
.when(items.length === 0, 'items', 'The items field must have at least one item.')
|
|
139
|
+
.string('merchantReferenceId', data.merchantReferenceId);
|
|
140
|
+
items.forEach((item, index) => {
|
|
141
|
+
validator
|
|
142
|
+
.required(`items.${index}.name`, item?.name)
|
|
143
|
+
.amount(`items.${index}.amount`, item?.amount, { gateway: 'Wave Money', maxDecimals: 0 });
|
|
144
|
+
});
|
|
145
|
+
const resolved = WaveMoney.resolvedAmount(data);
|
|
146
|
+
if (!(items.length > 0 && data.amount === undefined && resolved === undefined)) {
|
|
147
|
+
validator.amount('amount', data.amount ?? resolved, {
|
|
148
|
+
gateway: 'Wave Money',
|
|
149
|
+
maxDecimals: 0,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
validator.validate();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a payment request and returns Wave's page to redirect the customer to. Once `data`
|
|
156
|
+
* passes validation it fills `data.merchantReferenceId` with a random id when empty, so store it
|
|
157
|
+
* afterwards. Invalid data is left untouched.
|
|
158
|
+
*/
|
|
159
|
+
async initiate(data, options = {}) {
|
|
160
|
+
WaveMoney.validate(data);
|
|
161
|
+
if (!data.merchantReferenceId) {
|
|
162
|
+
data.merchantReferenceId = (0, crypto_js_1.randomHex)();
|
|
163
|
+
}
|
|
164
|
+
const amount = WaveMoney.resolvedAmount(data).toString();
|
|
165
|
+
const ttl = String(this.config.timeToLiveSeconds);
|
|
166
|
+
// Wave documents items as [{"name": "...", "amount": 1000}] with a numeric amount; the amount
|
|
167
|
+
// text is written as a JSON number without passing through a float.
|
|
168
|
+
const items = `[${data.items
|
|
169
|
+
.map((item) => `{"name":${JSON.stringify(item.name)},"amount":${String((0, validate_js_1.toAmount)(item.amount))}}`)
|
|
170
|
+
.join(',')}]`;
|
|
171
|
+
const response = await this.transport.postForm(`${this.config.baseUrl}/payment`, {
|
|
172
|
+
time_to_live_in_seconds: ttl,
|
|
173
|
+
merchant_id: this.config.merchantId,
|
|
174
|
+
order_id: data.orderId,
|
|
175
|
+
merchant_reference_id: data.merchantReferenceId,
|
|
176
|
+
frontend_result_url: data.returnUrl,
|
|
177
|
+
backend_result_url: data.callbackUrl,
|
|
178
|
+
amount,
|
|
179
|
+
payment_description: data.description,
|
|
180
|
+
merchant_name: this.config.merchantName,
|
|
181
|
+
items,
|
|
182
|
+
hash: this.hash([
|
|
183
|
+
ttl,
|
|
184
|
+
this.config.merchantId,
|
|
185
|
+
data.orderId,
|
|
186
|
+
amount,
|
|
187
|
+
data.callbackUrl,
|
|
188
|
+
data.merchantReferenceId,
|
|
189
|
+
]),
|
|
190
|
+
}, {}, options.signal);
|
|
191
|
+
const body = response.json();
|
|
192
|
+
const transactionId = (0, values_js_1.get)(body, 'transaction_id');
|
|
193
|
+
if (!response.successful() || (0, values_js_1.get)(body, 'message') !== 'success' || transactionId === '') {
|
|
194
|
+
const message = errorMessage(body);
|
|
195
|
+
throw new errors_js_1.ApiError(`Wave Money payment request failed with HTTP ${response.status}: ${message}`, {
|
|
196
|
+
gatewayCode: 'errors' in body ? 'VALIDATION_ERROR' : (0, values_js_1.optional)(body, 'message'),
|
|
197
|
+
gatewayMessage: message,
|
|
198
|
+
httpStatus: response.status,
|
|
199
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return new results_js_1.RedirectPayment({
|
|
203
|
+
orderId: data.orderId,
|
|
204
|
+
url: `${this.config.authenticateUrl}/authenticate?transaction_id=${(0, crypto_js_1.queryEscape)(transactionId)}`,
|
|
205
|
+
gatewayReference: transactionId,
|
|
206
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Verifies Wave's callback. Only `PAYMENT_CONFIRMED` means the customer paid. `orderId` falls
|
|
211
|
+
* back to `merchantReferenceId` when it is missing, null or empty, because Wave marks `orderId` as
|
|
212
|
+
* optional.
|
|
213
|
+
*/
|
|
214
|
+
handleCallback(request) {
|
|
215
|
+
const payload = (0, callback_js_1.losslessBody)(request);
|
|
216
|
+
const expected = this.hash(CALLBACK_FIELDS.map((field) => (0, values_js_1.scalarString)(payload[field]) ?? 'null'));
|
|
217
|
+
const hashValue = payload.hashValue;
|
|
218
|
+
if (typeof hashValue !== 'string' || !(0, crypto_js_1.safeEqual)(expected, hashValue.toLowerCase())) {
|
|
219
|
+
throw new errors_js_1.SignatureVerificationError('Wave Money callback hash verification failed.', (0, json_js_1.toPlainObject)(payload));
|
|
220
|
+
}
|
|
221
|
+
const gatewayStatus = (0, values_js_1.trimmed)(payload, 'status');
|
|
222
|
+
return new callback_js_1.PaymentCallback({
|
|
223
|
+
orderId: (0, values_js_1.get)(payload, 'orderId') || (0, values_js_1.get)(payload, 'merchantReferenceId'),
|
|
224
|
+
status: (0, status_js_1.resolveStatus)(STATUSES, gatewayStatus),
|
|
225
|
+
gatewayStatus,
|
|
226
|
+
gatewayReference: (0, values_js_1.optional)(payload, 'transactionId'),
|
|
227
|
+
amount: (0, values_js_1.optional)(payload, 'amount'),
|
|
228
|
+
raw: (0, json_js_1.toPlainObject)(payload),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
hash(parts) {
|
|
232
|
+
return (0, crypto_js_1.hmacSha256Hex)(this.config.secretKey, parts.join(''));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.WaveMoney = WaveMoney;
|
|
236
|
+
function errorMessage(body) {
|
|
237
|
+
const errors = body.errors;
|
|
238
|
+
if ((0, json_js_1.isLosslessObject)(errors)) {
|
|
239
|
+
return Object.keys(errors)
|
|
240
|
+
.sort()
|
|
241
|
+
.map((field) => {
|
|
242
|
+
const list = errors[field];
|
|
243
|
+
const texts = (Array.isArray(list) ? list : [list])
|
|
244
|
+
.map((item) => (0, values_js_1.scalarString)(item))
|
|
245
|
+
.filter((text) => text !== undefined);
|
|
246
|
+
return `${field}: ${texts.join(' ')}`;
|
|
247
|
+
})
|
|
248
|
+
.join('; ');
|
|
249
|
+
}
|
|
250
|
+
return (0, values_js_1.get)(body, 'message') || 'unexpected response';
|
|
251
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { YomaMmqr, YomaMmqrConfig, type YomaMmqrConfigOptions, type YomaMmqrOptions, type YomaMmqrPaymentData, } from './yoma-mmqr.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YomaMmqrConfig = exports.YomaMmqr = void 0;
|
|
4
|
+
var yoma_mmqr_js_1 = require("./yoma-mmqr.js");
|
|
5
|
+
Object.defineProperty(exports, "YomaMmqr", { enumerable: true, get: function () { return yoma_mmqr_js_1.YomaMmqr; } });
|
|
6
|
+
Object.defineProperty(exports, "YomaMmqrConfig", { enumerable: true, get: function () { return yoma_mmqr_js_1.YomaMmqrConfig; } });
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type AmountInput } from '../core/amount.js';
|
|
2
|
+
import { type TokenCache } from '../core/cache.js';
|
|
3
|
+
import { CallbackRequest, PaymentCallback, PaymentStatusResult } from '../core/callback.js';
|
|
4
|
+
import { type EnvSource } from '../core/env.js';
|
|
5
|
+
import { type GatewayOptions, type RequestOptions } from '../core/http.js';
|
|
6
|
+
import { QrPayment } from '../core/results.js';
|
|
7
|
+
/** The settings of {@link YomaMmqrConfig}. */
|
|
8
|
+
export interface YomaMmqrConfigOptions {
|
|
9
|
+
/** The merchant id Yoma issued. */
|
|
10
|
+
merchantId: string;
|
|
11
|
+
/** The OAuth client id. */
|
|
12
|
+
clientId: string;
|
|
13
|
+
/** The OAuth client secret. */
|
|
14
|
+
clientSecret: string;
|
|
15
|
+
/** The hash key Yoma issued for verifying callbacks. */
|
|
16
|
+
webhookHashKey: string;
|
|
17
|
+
/** The secret you shared with Yoma; when set, callbacks must carry it in `X-Webhook-Secret`. */
|
|
18
|
+
webhookSecret?: string | undefined;
|
|
19
|
+
/** Use the UAT payment hub (default `true`). */
|
|
20
|
+
sandbox?: boolean | undefined;
|
|
21
|
+
/** Overrides the API base URL. */
|
|
22
|
+
baseUrl?: string | undefined;
|
|
23
|
+
/** The `{version}` segment of the API paths (default `v1rc`). */
|
|
24
|
+
apiVersion?: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Yoma MMQR credentials and endpoints. A missing credential throws a `ConfigurationError`.
|
|
28
|
+
*/
|
|
29
|
+
export declare class YomaMmqrConfig {
|
|
30
|
+
static readonly SANDBOX_URL = "https://devapi.yomabank.net";
|
|
31
|
+
static readonly PRODUCTION_URL = "https://paymenthubapi.yomabank.com";
|
|
32
|
+
static readonly DEFAULT_API_VERSION = "v1rc";
|
|
33
|
+
readonly merchantId: string;
|
|
34
|
+
readonly clientId: string;
|
|
35
|
+
readonly clientSecret: string;
|
|
36
|
+
readonly webhookHashKey: string;
|
|
37
|
+
readonly webhookSecret: string | undefined;
|
|
38
|
+
readonly sandbox: boolean;
|
|
39
|
+
/** The base URL in use. */
|
|
40
|
+
readonly baseUrl: string;
|
|
41
|
+
/** The API version in use. */
|
|
42
|
+
readonly apiVersion: string;
|
|
43
|
+
constructor(options: YomaMmqrConfigOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Reads `YOMA_MMQR_MERCHANT_ID`, `YOMA_MMQR_CLIENT_ID`, `YOMA_MMQR_CLIENT_SECRET`,
|
|
46
|
+
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `YOMA_MMQR_WEBHOOK_SECRET`, `YOMA_MMQR_SANDBOX`,
|
|
47
|
+
* `YOMA_MMQR_BASE_URL` and `YOMA_MMQR_API_VERSION`.
|
|
48
|
+
*/
|
|
49
|
+
static fromEnv(env?: EnvSource): YomaMmqrConfig;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A Yoma MMQR order. Yoma accepts each order number once; renew an expired QR with `renewQr()`.
|
|
53
|
+
* Yoma documents no decimals, so amounts are whole kyat.
|
|
54
|
+
*/
|
|
55
|
+
export interface YomaMmqrPaymentData {
|
|
56
|
+
/** Your unique order number, at most 20 characters. */
|
|
57
|
+
orderId: string;
|
|
58
|
+
/** The amount in whole kyat, e.g. `Amount.kyat(1000)` or `1000`. */
|
|
59
|
+
amount: AmountInput;
|
|
60
|
+
/** Shown on the payment slip, at most 50 characters. */
|
|
61
|
+
description: string;
|
|
62
|
+
}
|
|
63
|
+
/** Options of {@link YomaMmqr}: the HTTP options plus the access token cache. */
|
|
64
|
+
export interface YomaMmqrOptions extends GatewayOptions {
|
|
65
|
+
/** Keeps the access token between calls; defaults to a {@link MemoryTokenCache}. */
|
|
66
|
+
tokenCache?: TokenCache | undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Yoma Bank MMQR: QR payments, status checks and callbacks. Access tokens are cached in a
|
|
70
|
+
* {@link TokenCache}; share the gateway (or pass a shared cache) so they survive between calls.
|
|
71
|
+
*/
|
|
72
|
+
export declare class YomaMmqr {
|
|
73
|
+
/** How long a generated QR stays payable, in seconds. */
|
|
74
|
+
static readonly QR_LIFETIME_SECONDS = 120;
|
|
75
|
+
/** The configuration in use. */
|
|
76
|
+
readonly config: YomaMmqrConfig;
|
|
77
|
+
private readonly transport;
|
|
78
|
+
private readonly cache;
|
|
79
|
+
private pendingToken;
|
|
80
|
+
constructor(config: YomaMmqrConfig | YomaMmqrConfigOptions, options?: YomaMmqrOptions);
|
|
81
|
+
/** A gateway configured from the `YOMA_MMQR_*` environment variables. */
|
|
82
|
+
static fromEnv(env?: EnvSource, options?: YomaMmqrOptions): YomaMmqr;
|
|
83
|
+
/** Checks the order against Yoma's documented limits; throws an `InvalidPaymentDataError`. */
|
|
84
|
+
static validate(data: YomaMmqrPaymentData): void;
|
|
85
|
+
/** Checks the order out with Yoma and generates its first QR. Call it once per order. */
|
|
86
|
+
initiate(data: YomaMmqrPaymentData, options?: RequestOptions): Promise<QrPayment>;
|
|
87
|
+
/**
|
|
88
|
+
* Generates a new QR for an order that is already checked out, e.g. after the previous one
|
|
89
|
+
* expired. The previous QR's reference stops working.
|
|
90
|
+
*/
|
|
91
|
+
renewQr(orderId: string, options?: RequestOptions): Promise<QrPayment>;
|
|
92
|
+
/** Checks a QR's payment status by its reference (`QrPayment.reference`). An expired QR reports `expired`. */
|
|
93
|
+
status(reference: string, options?: RequestOptions): Promise<PaymentStatusResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Verifies Yoma's payment callback: HMAC-SHA256 of `orderNumber=..&status=..` keyed with the
|
|
96
|
+
* order number followed by the webhook hash key.
|
|
97
|
+
*/
|
|
98
|
+
handleCallback(request: CallbackRequest): PaymentCallback;
|
|
99
|
+
/** Drops the cached access token, e.g. after rotating the client secret. */
|
|
100
|
+
forgetToken(): Promise<void>;
|
|
101
|
+
private call;
|
|
102
|
+
private token;
|
|
103
|
+
private fetchToken;
|
|
104
|
+
private tokenCacheKey;
|
|
105
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YomaMmqr = exports.YomaMmqrConfig = void 0;
|
|
4
|
+
const cache_js_1 = require("../core/cache.js");
|
|
5
|
+
const callback_js_1 = require("../core/callback.js");
|
|
6
|
+
const crypto_js_1 = require("../core/crypto.js");
|
|
7
|
+
const env_js_1 = require("../core/env.js");
|
|
8
|
+
const errors_js_1 = require("../core/errors.js");
|
|
9
|
+
const http_js_1 = require("../core/http.js");
|
|
10
|
+
const json_js_1 = require("../core/json.js");
|
|
11
|
+
const results_js_1 = require("../core/results.js");
|
|
12
|
+
const status_js_1 = require("../core/status.js");
|
|
13
|
+
const validate_js_1 = require("../core/validate.js");
|
|
14
|
+
const values_js_1 = require("../core/values.js");
|
|
15
|
+
/**
|
|
16
|
+
* Yoma MMQR credentials and endpoints. A missing credential throws a `ConfigurationError`.
|
|
17
|
+
*/
|
|
18
|
+
class YomaMmqrConfig {
|
|
19
|
+
static SANDBOX_URL = 'https://devapi.yomabank.net';
|
|
20
|
+
static PRODUCTION_URL = 'https://paymenthubapi.yomabank.com';
|
|
21
|
+
static DEFAULT_API_VERSION = 'v1rc';
|
|
22
|
+
merchantId;
|
|
23
|
+
clientId;
|
|
24
|
+
clientSecret;
|
|
25
|
+
webhookHashKey;
|
|
26
|
+
webhookSecret;
|
|
27
|
+
sandbox;
|
|
28
|
+
/** The base URL in use. */
|
|
29
|
+
baseUrl;
|
|
30
|
+
/** The API version in use. */
|
|
31
|
+
apiVersion;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.merchantId = (0, env_js_1.requireSetting)('yoma_mmqr', 'merchant_id', options.merchantId);
|
|
34
|
+
this.clientId = (0, env_js_1.requireSetting)('yoma_mmqr', 'client_id', options.clientId);
|
|
35
|
+
this.clientSecret = (0, env_js_1.requireSetting)('yoma_mmqr', 'client_secret', options.clientSecret);
|
|
36
|
+
this.webhookHashKey = (0, env_js_1.requireSetting)('yoma_mmqr', 'webhook_hashkey', options.webhookHashKey);
|
|
37
|
+
this.webhookSecret = (0, env_js_1.optionalSetting)(options.webhookSecret);
|
|
38
|
+
this.sandbox = options.sandbox ?? true;
|
|
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;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads `YOMA_MMQR_MERCHANT_ID`, `YOMA_MMQR_CLIENT_ID`, `YOMA_MMQR_CLIENT_SECRET`,
|
|
45
|
+
* `YOMA_MMQR_WEBHOOK_HASHKEY`, `YOMA_MMQR_WEBHOOK_SECRET`, `YOMA_MMQR_SANDBOX`,
|
|
46
|
+
* `YOMA_MMQR_BASE_URL` and `YOMA_MMQR_API_VERSION`.
|
|
47
|
+
*/
|
|
48
|
+
static fromEnv(env = (0, env_js_1.defaultEnv)()) {
|
|
49
|
+
return new YomaMmqrConfig({
|
|
50
|
+
merchantId: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_MERCHANT_ID'),
|
|
51
|
+
clientId: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_CLIENT_ID'),
|
|
52
|
+
clientSecret: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_CLIENT_SECRET'),
|
|
53
|
+
webhookHashKey: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_WEBHOOK_HASHKEY'),
|
|
54
|
+
webhookSecret: (0, env_js_1.envFirst)(env, 'YOMA_MMQR_WEBHOOK_SECRET'),
|
|
55
|
+
sandbox: (0, env_js_1.envSandbox)(env, 'YOMA_MMQR_SANDBOX'),
|
|
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
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.YomaMmqrConfig = YomaMmqrConfig;
|
|
62
|
+
const STATUSES = {
|
|
63
|
+
SUCCESS: 'successful',
|
|
64
|
+
PENDING: 'pending',
|
|
65
|
+
FAILED: 'failed',
|
|
66
|
+
FAIL: 'failed',
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Yoma Bank MMQR: QR payments, status checks and callbacks. Access tokens are cached in a
|
|
70
|
+
* {@link TokenCache}; share the gateway (or pass a shared cache) so they survive between calls.
|
|
71
|
+
*/
|
|
72
|
+
class YomaMmqr {
|
|
73
|
+
/** How long a generated QR stays payable, in seconds. */
|
|
74
|
+
static QR_LIFETIME_SECONDS = 120;
|
|
75
|
+
/** The configuration in use. */
|
|
76
|
+
config;
|
|
77
|
+
transport;
|
|
78
|
+
cache;
|
|
79
|
+
pendingToken;
|
|
80
|
+
constructor(config, options = {}) {
|
|
81
|
+
this.config = config instanceof YomaMmqrConfig ? config : new YomaMmqrConfig(config);
|
|
82
|
+
this.transport = new http_js_1.Transport((0, http_js_1.httpClientFrom)(options));
|
|
83
|
+
this.cache = options.tokenCache ?? new cache_js_1.MemoryTokenCache();
|
|
84
|
+
}
|
|
85
|
+
/** A gateway configured from the `YOMA_MMQR_*` environment variables. */
|
|
86
|
+
static fromEnv(env = (0, env_js_1.defaultEnv)(), options = {}) {
|
|
87
|
+
return new YomaMmqr(YomaMmqrConfig.fromEnv(env), options);
|
|
88
|
+
}
|
|
89
|
+
/** Checks the order against Yoma's documented limits; throws an `InvalidPaymentDataError`. */
|
|
90
|
+
static validate(data) {
|
|
91
|
+
new validate_js_1.Validator()
|
|
92
|
+
.required('orderId', data.orderId)
|
|
93
|
+
.max('orderId', data.orderId, 20)
|
|
94
|
+
.amount('amount', data.amount, { gateway: 'Yoma MMQR', maxDecimals: 0 })
|
|
95
|
+
.required('description', data.description)
|
|
96
|
+
.max('description', data.description, 50)
|
|
97
|
+
.validate();
|
|
98
|
+
}
|
|
99
|
+
/** Checks the order out with Yoma and generates its first QR. Call it once per order. */
|
|
100
|
+
async initiate(data, options = {}) {
|
|
101
|
+
YomaMmqr.validate(data);
|
|
102
|
+
const body = await this.call('payment/checkout', {
|
|
103
|
+
merchantId: this.config.merchantId,
|
|
104
|
+
orderNumber: data.orderId,
|
|
105
|
+
amount: String((0, validate_js_1.toAmount)(data.amount)),
|
|
106
|
+
description: data.description,
|
|
107
|
+
}, [], options.signal);
|
|
108
|
+
if (body.checkOutStatus !== true) {
|
|
109
|
+
throw new errors_js_1.ApiError('Yoma MMQR did not confirm the checkout.', { raw: (0, json_js_1.toPlainObject)(body) });
|
|
110
|
+
}
|
|
111
|
+
return this.renewQr(data.orderId, options);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generates a new QR for an order that is already checked out, e.g. after the previous one
|
|
115
|
+
* expired. The previous QR's reference stops working.
|
|
116
|
+
*/
|
|
117
|
+
async renewQr(orderId, options = {}) {
|
|
118
|
+
const body = await this.call('qr/generate', { merchantId: this.config.merchantId, orderNumber: orderId }, [], options.signal);
|
|
119
|
+
const qrString = (0, values_js_1.get)(body, 'qrString');
|
|
120
|
+
const reference = (0, values_js_1.get)(body, 'refLabel');
|
|
121
|
+
if (qrString === '' || reference === '') {
|
|
122
|
+
throw new errors_js_1.ApiError('Yoma MMQR did not return a QR.', { raw: (0, json_js_1.toPlainObject)(body) });
|
|
123
|
+
}
|
|
124
|
+
return new results_js_1.QrPayment({
|
|
125
|
+
orderId,
|
|
126
|
+
qrImage: qrString,
|
|
127
|
+
expiresAt: new Date(Date.now() + YomaMmqr.QR_LIFETIME_SECONDS * 1000),
|
|
128
|
+
reference,
|
|
129
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Checks a QR's payment status by its reference (`QrPayment.reference`). An expired QR reports `expired`. */
|
|
133
|
+
async status(reference, options = {}) {
|
|
134
|
+
const body = await this.call('payment/check-status', { merchantId: this.config.merchantId, refLabel: reference }, ['QR EXPIRED'], options.signal);
|
|
135
|
+
if ((0, values_js_1.get)(body, 'errorCode') === 'QR EXPIRED') {
|
|
136
|
+
return new callback_js_1.PaymentStatusResult({
|
|
137
|
+
status: 'expired',
|
|
138
|
+
gatewayStatus: 'QR EXPIRED',
|
|
139
|
+
gatewayReference: reference,
|
|
140
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const paymentStatus = (0, values_js_1.trimmed)(body, 'paymentStatus');
|
|
144
|
+
return new callback_js_1.PaymentStatusResult({
|
|
145
|
+
status: (0, status_js_1.resolveStatus)(STATUSES, paymentStatus.toUpperCase()),
|
|
146
|
+
gatewayStatus: paymentStatus,
|
|
147
|
+
gatewayReference: (0, values_js_1.get)(body, 'refLabel') || reference,
|
|
148
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Verifies Yoma's payment callback: HMAC-SHA256 of `orderNumber=..&status=..` keyed with the
|
|
153
|
+
* order number followed by the webhook hash key.
|
|
154
|
+
*/
|
|
155
|
+
handleCallback(request) {
|
|
156
|
+
const payload = (0, callback_js_1.losslessBody)(request);
|
|
157
|
+
if (this.config.webhookSecret !== undefined &&
|
|
158
|
+
!(0, crypto_js_1.safeEqual)(this.config.webhookSecret, request.header('X-Webhook-Secret') ?? '')) {
|
|
159
|
+
throw new errors_js_1.SignatureVerificationError('Yoma MMQR callback has a missing or wrong X-Webhook-Secret header.', (0, json_js_1.toPlainObject)(payload));
|
|
160
|
+
}
|
|
161
|
+
const orderNumber = (0, values_js_1.get)(payload, 'orderNumber');
|
|
162
|
+
const status = (0, values_js_1.trimmed)(payload, 'status');
|
|
163
|
+
const expected = (0, crypto_js_1.hmacSha256Hex)(orderNumber + this.config.webhookHashKey, `orderNumber=${orderNumber}&status=${status}`);
|
|
164
|
+
if (orderNumber === '' || !(0, crypto_js_1.safeEqual)(expected, (0, values_js_1.get)(payload, 'hashValue').toLowerCase())) {
|
|
165
|
+
throw new errors_js_1.SignatureVerificationError('Yoma MMQR callback hash verification failed.', (0, json_js_1.toPlainObject)(payload));
|
|
166
|
+
}
|
|
167
|
+
return new callback_js_1.PaymentCallback({
|
|
168
|
+
orderId: orderNumber,
|
|
169
|
+
status: (0, status_js_1.resolveStatus)(STATUSES, status.toUpperCase()),
|
|
170
|
+
gatewayStatus: status,
|
|
171
|
+
raw: (0, json_js_1.toPlainObject)(payload),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Drops the cached access token, e.g. after rotating the client secret. */
|
|
175
|
+
async forgetToken() {
|
|
176
|
+
await this.cache.delete(this.tokenCacheKey());
|
|
177
|
+
}
|
|
178
|
+
async call(path, data, allowedErrors, signal) {
|
|
179
|
+
const url = `${this.config.baseUrl}/payment-gateway/${this.config.apiVersion}/api/${path}`;
|
|
180
|
+
const send = async () => this.transport.postJson(url, data, { Authorization: `Bearer ${await this.token()}` }, signal);
|
|
181
|
+
let response = await send();
|
|
182
|
+
if (response.status === 401) {
|
|
183
|
+
await this.forgetToken();
|
|
184
|
+
response = await send();
|
|
185
|
+
}
|
|
186
|
+
const body = response.json();
|
|
187
|
+
const errorCode = (0, values_js_1.get)(body, 'errorCode');
|
|
188
|
+
if (!response.successful() || (errorCode !== '' && !allowedErrors.includes(errorCode))) {
|
|
189
|
+
throw apiError(path, response.status, body, errorCode);
|
|
190
|
+
}
|
|
191
|
+
return body;
|
|
192
|
+
}
|
|
193
|
+
async token() {
|
|
194
|
+
const cached = await this.cache.get(this.tokenCacheKey());
|
|
195
|
+
if (typeof cached === 'string' && cached !== '') {
|
|
196
|
+
return cached;
|
|
197
|
+
}
|
|
198
|
+
// Concurrent calls share one token request; it ignores any one caller's signal so aborting
|
|
199
|
+
// that caller does not fail the others (the client timeout still applies).
|
|
200
|
+
this.pendingToken ??= this.fetchToken().finally(() => {
|
|
201
|
+
this.pendingToken = undefined;
|
|
202
|
+
});
|
|
203
|
+
return this.pendingToken;
|
|
204
|
+
}
|
|
205
|
+
async fetchToken() {
|
|
206
|
+
const credentials = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
|
|
207
|
+
const response = await this.transport.postForm(`${this.config.baseUrl}/token`, { grant_type: 'client_credentials' }, { Authorization: `Basic ${credentials}` });
|
|
208
|
+
const body = response.json();
|
|
209
|
+
const token = (0, values_js_1.get)(body, 'access_token');
|
|
210
|
+
if (!response.successful() || token === '') {
|
|
211
|
+
throw apiError('token', response.status, body, (0, values_js_1.get)(body, 'error'));
|
|
212
|
+
}
|
|
213
|
+
const expiresIn = Number.parseInt((0, values_js_1.get)(body, 'expires_in'), 10);
|
|
214
|
+
const ttl = Math.max(60, (Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 3600) - 60);
|
|
215
|
+
await this.cache.set(this.tokenCacheKey(), token, ttl);
|
|
216
|
+
return token;
|
|
217
|
+
}
|
|
218
|
+
tokenCacheKey() {
|
|
219
|
+
return `node-myanmar-payments.yoma-mmqr.token.${(0, crypto_js_1.sha256Hex)(`${this.config.baseUrl}|${this.config.clientId}`)}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
exports.YomaMmqr = YomaMmqr;
|
|
223
|
+
function apiError(endpoint, status, body, errorCode) {
|
|
224
|
+
const message = (0, values_js_1.optional)(body, 'errorDescription') || (0, values_js_1.optional)(body, 'error_description');
|
|
225
|
+
return new errors_js_1.ApiError(errorCode
|
|
226
|
+
? `Yoma MMQR ${endpoint} failed: [${errorCode}] ${message ?? ''}`.trimEnd()
|
|
227
|
+
: `Yoma MMQR ${endpoint} failed with HTTP ${status}.`, {
|
|
228
|
+
gatewayCode: errorCode || undefined,
|
|
229
|
+
gatewayMessage: message || undefined,
|
|
230
|
+
httpStatus: status,
|
|
231
|
+
raw: (0, json_js_1.toPlainObject)(body),
|
|
232
|
+
});
|
|
233
|
+
}
|