@laranex/myanmar-payments 4.0.0-dev.7 → 4.0.0-dev.9
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 +2 -0
- package/dist/cjs/kbz-pay/kbz-pay.d.ts +5 -2
- package/dist/cjs/kbz-pay/kbz-pay.js +6 -5
- package/dist/cjs/wave-money/wave-money.d.ts +10 -0
- package/dist/cjs/wave-money/wave-money.js +10 -3
- package/dist/esm/kbz-pay/kbz-pay.d.ts +5 -2
- package/dist/esm/kbz-pay/kbz-pay.js +6 -5
- package/dist/esm/wave-money/wave-money.d.ts +10 -0
- package/dist/esm/wave-money/wave-money.js +10 -3
- package/package.json +1 -1
- package/skills/node-myanmar-payments/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -31,3 +31,5 @@ Initial release. The version number matches the other Laranex Myanmar payments p
|
|
|
31
31
|
- Yoma MMQR caches its access token under `myanmar-payments.yoma-mmqr.token.<sha256 of base URL and client id>` (was `node-myanmar-payments.…`), the same key as the PHP, Go and Python SDKs, and reads `expires_in` from its leading digits.
|
|
32
32
|
- `Amount.equals()` compares by value, ignoring leading zeros too (`'01000'` equals `Amount.kyat(1000)`); text that is not plain digits with an optional fraction is never equal.
|
|
33
33
|
- `CallbackRequest.rawBody` holds the exact bytes received (a string body is encoded as UTF-8), next to `body` as text, like Python's `raw_body` and Go's `Body`.
|
|
34
|
+
- `WaveMoneyPaymentData` takes optional `merchantName` and `timeToLiveSeconds`, which override the configured `merchantName` and `timeToLiveSeconds` for one payment (request hash included); a blank `merchantName` or a `timeToLiveSeconds` that is not a whole number greater than 0 throws `InvalidPaymentDataError`, like the PHP SDK.
|
|
35
|
+
- `KbzPayPaymentData.timeoutMinutes` is renamed to `timeToLiveSeconds` and takes seconds, like Wave Money: 60 to 7200, in whole minutes (a multiple of 60), because KBZ's `timeout_express` counts minutes (`1800` is sent as `30m`). Upgrading: multiply the old value by 60.
|
|
@@ -58,8 +58,11 @@ export interface KbzPayPaymentData {
|
|
|
58
58
|
callbackUrl: string;
|
|
59
59
|
/** The product name shown to the customer. */
|
|
60
60
|
title?: string | undefined;
|
|
61
|
-
/**
|
|
62
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Seconds the customer has to pay before the order expires, 60 to 7200 in whole minutes (a multiple
|
|
63
|
+
* of 60). KBZ counts minutes, so 1800 is sent as `30m`. KBZ defaults to 7200 (120 minutes).
|
|
64
|
+
*/
|
|
65
|
+
timeToLiveSeconds?: number | undefined;
|
|
63
66
|
/** Free text KBZ sends back unchanged in the callback, at most 512 characters once URL-encoded. */
|
|
64
67
|
callbackInfo?: string | undefined;
|
|
65
68
|
}
|
|
@@ -93,7 +93,8 @@ class KbzPay {
|
|
|
93
93
|
.max('callbackUrl', callbackUrl, 512)
|
|
94
94
|
.when(callbackUrl.includes('?'), 'callbackUrl', 'The callbackUrl field must not contain a query string.')
|
|
95
95
|
.string('title', data.title)
|
|
96
|
-
.between('
|
|
96
|
+
.between('timeToLiveSeconds', data.timeToLiveSeconds, 60, 7200)
|
|
97
|
+
.when(Number.isInteger(data.timeToLiveSeconds) && data.timeToLiveSeconds % 60 !== 0, 'timeToLiveSeconds', 'The timeToLiveSeconds field must be a multiple of 60.')
|
|
97
98
|
.string('callbackInfo', data.callbackInfo)
|
|
98
99
|
.max('callbackInfo', typeof data.callbackInfo === 'string' ? (0, crypto_js_1.queryEscape)(data.callbackInfo) : undefined, 512)
|
|
99
100
|
.validate();
|
|
@@ -126,9 +127,9 @@ class KbzPay {
|
|
|
126
127
|
return new results_js_1.QrPayment({
|
|
127
128
|
orderId: data.orderId,
|
|
128
129
|
qrString: qrCode,
|
|
129
|
-
expiresAt: data.
|
|
130
|
+
expiresAt: data.timeToLiveSeconds === undefined
|
|
130
131
|
? undefined
|
|
131
|
-
: new Date(Date.now() + data.
|
|
132
|
+
: new Date(Date.now() + data.timeToLiveSeconds * 1000),
|
|
132
133
|
reference: order.prepayId,
|
|
133
134
|
raw: (0, json_js_1.toPlainObject)(order.response),
|
|
134
135
|
});
|
|
@@ -199,8 +200,8 @@ class KbzPay {
|
|
|
199
200
|
if (data.title) {
|
|
200
201
|
biz.title = data.title;
|
|
201
202
|
}
|
|
202
|
-
if (data.
|
|
203
|
-
biz.timeout_express = `${data.
|
|
203
|
+
if (data.timeToLiveSeconds !== undefined) {
|
|
204
|
+
biz.timeout_express = `${data.timeToLiveSeconds / 60}m`;
|
|
204
205
|
}
|
|
205
206
|
if (data.callbackInfo) {
|
|
206
207
|
biz.callback_info = (0, crypto_js_1.queryEscape)(data.callbackInfo);
|
|
@@ -79,6 +79,16 @@ export interface WaveMoneyPaymentData {
|
|
|
79
79
|
* this.
|
|
80
80
|
*/
|
|
81
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;
|
|
82
92
|
}
|
|
83
93
|
/**
|
|
84
94
|
* Wave Money (WavePay payment gateway): redirect payments and callbacks. Wave has no status API,
|
|
@@ -127,7 +127,11 @@ class WaveMoney {
|
|
|
127
127
|
.url('returnUrl', data.returnUrl)
|
|
128
128
|
.required('description', data.description)
|
|
129
129
|
.when(items.length === 0, 'items', 'The items field must have at least one item.')
|
|
130
|
-
.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.');
|
|
131
135
|
items.forEach((item, index) => {
|
|
132
136
|
validator
|
|
133
137
|
.required(`items.${index}.name`, item?.name)
|
|
@@ -153,7 +157,7 @@ class WaveMoney {
|
|
|
153
157
|
data.merchantReferenceId = (0, crypto_js_1.randomHex)();
|
|
154
158
|
}
|
|
155
159
|
const amount = WaveMoney.resolvedAmount(data).toString();
|
|
156
|
-
const ttl = String(this.config.timeToLiveSeconds);
|
|
160
|
+
const ttl = String(data.timeToLiveSeconds ?? this.config.timeToLiveSeconds);
|
|
157
161
|
// Wave documents items as [{"name": "...", "amount": 1000}] with a numeric amount; the amount
|
|
158
162
|
// text is written as a JSON number without passing through a float.
|
|
159
163
|
const items = `[${data.items
|
|
@@ -168,7 +172,7 @@ class WaveMoney {
|
|
|
168
172
|
backend_result_url: data.callbackUrl,
|
|
169
173
|
amount,
|
|
170
174
|
payment_description: data.description,
|
|
171
|
-
merchant_name: this.config.merchantName,
|
|
175
|
+
merchant_name: data.merchantName ?? this.config.merchantName,
|
|
172
176
|
items,
|
|
173
177
|
hash: this.hash([
|
|
174
178
|
ttl,
|
|
@@ -226,6 +230,9 @@ class WaveMoney {
|
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
232
|
exports.WaveMoney = WaveMoney;
|
|
233
|
+
function isSet(value) {
|
|
234
|
+
return value !== undefined && value !== null;
|
|
235
|
+
}
|
|
229
236
|
function errorMessage(body) {
|
|
230
237
|
const errors = body.errors;
|
|
231
238
|
if ((0, json_js_1.isLosslessObject)(errors)) {
|
|
@@ -58,8 +58,11 @@ export interface KbzPayPaymentData {
|
|
|
58
58
|
callbackUrl: string;
|
|
59
59
|
/** The product name shown to the customer. */
|
|
60
60
|
title?: string | undefined;
|
|
61
|
-
/**
|
|
62
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Seconds the customer has to pay before the order expires, 60 to 7200 in whole minutes (a multiple
|
|
63
|
+
* of 60). KBZ counts minutes, so 1800 is sent as `30m`. KBZ defaults to 7200 (120 minutes).
|
|
64
|
+
*/
|
|
65
|
+
timeToLiveSeconds?: number | undefined;
|
|
63
66
|
/** Free text KBZ sends back unchanged in the callback, at most 512 characters once URL-encoded. */
|
|
64
67
|
callbackInfo?: string | undefined;
|
|
65
68
|
}
|
|
@@ -90,7 +90,8 @@ export class KbzPay {
|
|
|
90
90
|
.max('callbackUrl', callbackUrl, 512)
|
|
91
91
|
.when(callbackUrl.includes('?'), 'callbackUrl', 'The callbackUrl field must not contain a query string.')
|
|
92
92
|
.string('title', data.title)
|
|
93
|
-
.between('
|
|
93
|
+
.between('timeToLiveSeconds', data.timeToLiveSeconds, 60, 7200)
|
|
94
|
+
.when(Number.isInteger(data.timeToLiveSeconds) && data.timeToLiveSeconds % 60 !== 0, 'timeToLiveSeconds', 'The timeToLiveSeconds field must be a multiple of 60.')
|
|
94
95
|
.string('callbackInfo', data.callbackInfo)
|
|
95
96
|
.max('callbackInfo', typeof data.callbackInfo === 'string' ? queryEscape(data.callbackInfo) : undefined, 512)
|
|
96
97
|
.validate();
|
|
@@ -123,9 +124,9 @@ export class KbzPay {
|
|
|
123
124
|
return new QrPayment({
|
|
124
125
|
orderId: data.orderId,
|
|
125
126
|
qrString: qrCode,
|
|
126
|
-
expiresAt: data.
|
|
127
|
+
expiresAt: data.timeToLiveSeconds === undefined
|
|
127
128
|
? undefined
|
|
128
|
-
: new Date(Date.now() + data.
|
|
129
|
+
: new Date(Date.now() + data.timeToLiveSeconds * 1000),
|
|
129
130
|
reference: order.prepayId,
|
|
130
131
|
raw: toPlainObject(order.response),
|
|
131
132
|
});
|
|
@@ -196,8 +197,8 @@ export class KbzPay {
|
|
|
196
197
|
if (data.title) {
|
|
197
198
|
biz.title = data.title;
|
|
198
199
|
}
|
|
199
|
-
if (data.
|
|
200
|
-
biz.timeout_express = `${data.
|
|
200
|
+
if (data.timeToLiveSeconds !== undefined) {
|
|
201
|
+
biz.timeout_express = `${data.timeToLiveSeconds / 60}m`;
|
|
201
202
|
}
|
|
202
203
|
if (data.callbackInfo) {
|
|
203
204
|
biz.callback_info = queryEscape(data.callbackInfo);
|
|
@@ -79,6 +79,16 @@ export interface WaveMoneyPaymentData {
|
|
|
79
79
|
* this.
|
|
80
80
|
*/
|
|
81
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;
|
|
82
92
|
}
|
|
83
93
|
/**
|
|
84
94
|
* Wave Money (WavePay payment gateway): redirect payments and callbacks. Wave has no status API,
|
|
@@ -123,7 +123,11 @@ export class WaveMoney {
|
|
|
123
123
|
.url('returnUrl', data.returnUrl)
|
|
124
124
|
.required('description', data.description)
|
|
125
125
|
.when(items.length === 0, 'items', 'The items field must have at least one item.')
|
|
126
|
-
.string('merchantReferenceId', data.merchantReferenceId)
|
|
126
|
+
.string('merchantReferenceId', data.merchantReferenceId)
|
|
127
|
+
.string('merchantName', data.merchantName)
|
|
128
|
+
.when(typeof data.merchantName === 'string' && data.merchantName.trim() === '', 'merchantName', 'The merchantName field is required.')
|
|
129
|
+
.when(isSet(data.timeToLiveSeconds) && !Number.isInteger(data.timeToLiveSeconds), 'timeToLiveSeconds', 'The timeToLiveSeconds field must be an integer.')
|
|
130
|
+
.when(isSet(data.timeToLiveSeconds) && data.timeToLiveSeconds < 1, 'timeToLiveSeconds', 'The timeToLiveSeconds field must be greater than 0.');
|
|
127
131
|
items.forEach((item, index) => {
|
|
128
132
|
validator
|
|
129
133
|
.required(`items.${index}.name`, item?.name)
|
|
@@ -149,7 +153,7 @@ export class WaveMoney {
|
|
|
149
153
|
data.merchantReferenceId = randomHex();
|
|
150
154
|
}
|
|
151
155
|
const amount = WaveMoney.resolvedAmount(data).toString();
|
|
152
|
-
const ttl = String(this.config.timeToLiveSeconds);
|
|
156
|
+
const ttl = String(data.timeToLiveSeconds ?? this.config.timeToLiveSeconds);
|
|
153
157
|
// Wave documents items as [{"name": "...", "amount": 1000}] with a numeric amount; the amount
|
|
154
158
|
// text is written as a JSON number without passing through a float.
|
|
155
159
|
const items = `[${data.items
|
|
@@ -164,7 +168,7 @@ export class WaveMoney {
|
|
|
164
168
|
backend_result_url: data.callbackUrl,
|
|
165
169
|
amount,
|
|
166
170
|
payment_description: data.description,
|
|
167
|
-
merchant_name: this.config.merchantName,
|
|
171
|
+
merchant_name: data.merchantName ?? this.config.merchantName,
|
|
168
172
|
items,
|
|
169
173
|
hash: this.hash([
|
|
170
174
|
ttl,
|
|
@@ -221,6 +225,9 @@ export class WaveMoney {
|
|
|
221
225
|
return hmacSha256Hex(this.config.secretKey, parts.join(''));
|
|
222
226
|
}
|
|
223
227
|
}
|
|
228
|
+
function isSet(value) {
|
|
229
|
+
return value !== undefined && value !== null;
|
|
230
|
+
}
|
|
224
231
|
function errorMessage(body) {
|
|
225
232
|
const errors = body.errors;
|
|
226
233
|
if (isLosslessObject(errors)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@laranex/myanmar-payments",
|
|
3
|
-
"version": "4.0.0-dev.
|
|
3
|
+
"version": "4.0.0-dev.9",
|
|
4
4
|
"description": "Node.js SDK for Myanmar payment gateways: KBZ Pay, Wave Money, AYA Pay, Yoma MMQR and CyberSource. Typed requests and results, exact amounts, no runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"laranex",
|
|
@@ -58,7 +58,7 @@ const payment = await kbz.pwa({
|
|
|
58
58
|
res.writeHead(302, { Location: payment.url }).end();
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
- `RedirectPayment` (`url`) from `kbz.pwa(data)` and `wave.initiate(data)`. Wave fills `data.merchantReferenceId` when empty; store it.
|
|
61
|
+
- `RedirectPayment` (`url`) from `kbz.pwa(data)` and `wave.initiate(data)`. Wave fills `data.merchantReferenceId` when empty; store it. Pass `merchantName` or `timeToLiveSeconds` in Wave's payment data to override the configured `WAVE_MONEY_MERCHANT_NAME` or `WAVE_MONEY_TIME_TO_LIVE_IN_SECONDS` for that one payment (the settings stay required).
|
|
62
62
|
- `FormPayment` from `aya.initiate(data)` and `cyberSource.initiate(data)` (no network call): send `payment.toHtml()` as an auto-submitting page, or render `action`, `fields` and `enctype` yourself.
|
|
63
63
|
- `QrPayment` from `kbz.qr(data)` (encode `qrString`) and `yoma.initiate(data)` (`qrImageDataUri()`, `expiresAt`, `reference`). A Yoma QR lives `YomaMmqr.QR_LIFETIME_SECONDS` (120); renew it with `yoma.renewQr(orderId)`.
|
|
64
64
|
- `AppPayment` from `kbz.app(data)`: `res.json(payment)` sends `orderId`, `orderInfo`, `sign` and `signType` to your mobile app.
|