@mmpay/aya-mmqr 0.0.0-stage → 1.0.13
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/README.md +251 -2
- package/bun.lock +23 -0
- package/dist/cjs/index.d.ts +78 -0
- package/dist/cjs/index.js +331 -0
- package/dist/cjs/types/index.d.ts +154 -0
- package/dist/cjs/types/index.js +2 -0
- package/dist/esm/index.d.ts +78 -0
- package/dist/esm/index.js +325 -0
- package/dist/esm/types/index.d.ts +154 -0
- package/dist/esm/types/index.js +1 -0
- package/package.json +53 -4
- package/src/index.ts +360 -0
- package/src/types/index.ts +156 -0
- package/test/decrypttest.js +33 -0
- package/test/test.js +65 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.json +19 -0
package/README.md
CHANGED
|
@@ -1,3 +1,252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 📘 AYA Pay Merchant API Integration Documentation (APIM)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An unofficial, personal-use TypeScript API integration package for connecting AYAPay Merchant's MMQR functionalities into Node.js applications.
|
|
4
|
+
It provides a simple class-based wrapper around the AYAPay REST API, handling authentication, token management, dynamic QR generation, status checking, and refunds.
|
|
5
|
+
|
|
6
|
+
> **Disclaimer:** This is an unofficial, community-driven integration package intended for personal reuse. It is not affiliated with, endorsed by, or maintained by AYA Bank.
|
|
7
|
+
|
|
8
|
+
## 🚀 Installation
|
|
9
|
+
|
|
10
|
+
Install the package via npm:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @mmpay/aya-mmqr --save
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Implementation IDEA
|
|
18
|
+
```javascript
|
|
19
|
+
import { AYAPayMerchantApi, SDKOptions } from '@mmpay/aya-mmqr';
|
|
20
|
+
|
|
21
|
+
const options: SDKOptions = {
|
|
22
|
+
baseUrl: 'https://sandboxxxxx.aya-payx.com/api', // <<< Request AYA Team. I cannot disclose here for security reasons
|
|
23
|
+
prefixUrl: 'xxx', // <<< Request AYA Team. I cannot disclose here for security reasons
|
|
24
|
+
consumerKey: 'YOUR_CONSUMER_KEY',
|
|
25
|
+
consumerSecret: 'YOUR_CONSUMER_SECRET',
|
|
26
|
+
decryptionKey: 'YOUR_DECRYPTION_KEY_32_BYTES',
|
|
27
|
+
phone: 'MERCHANT_APP_PHONE_NUMBER',
|
|
28
|
+
password: 'MERCHANT_APP_PASSWORD', // Password Or Pincode
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ayaPayClient = AYAPayMerchantApi(options);
|
|
32
|
+
|
|
33
|
+
// DEVELOPMENT QR
|
|
34
|
+
const qrResponse = await ayaPayClient.requestQR({
|
|
35
|
+
amount: 5000,
|
|
36
|
+
currency: 'MMK',
|
|
37
|
+
externalTransactionId: 'ORD-' + new Date().getTime(),
|
|
38
|
+
serviceCode: 'xxx-qr', // << I cannot disclose all of these ENUM for security reasons, Please ask merchant onboarding teams
|
|
39
|
+
// externalAdditionalData: optional
|
|
40
|
+
})
|
|
41
|
+
console.log(qrResponse)
|
|
42
|
+
|
|
43
|
+
const qrStatusResponse = await ayaPayClient.paymentStatusQR({
|
|
44
|
+
referenceNumber: qrResponse.data.referenceNumber,
|
|
45
|
+
externalTransactionId: qrResponse.data.externalTransactionId,
|
|
46
|
+
})
|
|
47
|
+
console.log(qrStatusResponse)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
const omResponse = await ayaPayClient.requestPush({
|
|
51
|
+
amount: 5000,
|
|
52
|
+
currency: 'MMK',
|
|
53
|
+
externalTransactionId: 'ORD-' + new Date().getTime(),
|
|
54
|
+
serviceCode: 'xxx-om', // << I cannot disclose all of these ENUM for security reasons, Please ask merchant onboarding teams
|
|
55
|
+
// externalAdditionalData: optional
|
|
56
|
+
})
|
|
57
|
+
console.log(omResponse)
|
|
58
|
+
|
|
59
|
+
const omStatusResponse = await ayaPayClient.paymentStatusPush({
|
|
60
|
+
referenceNumber: omResponse.data.referenceNumber,
|
|
61
|
+
externalTransactionId: omResponse.data.externalTransactionId,
|
|
62
|
+
})
|
|
63
|
+
console.log(omStatusResponse)
|
|
64
|
+
|
|
65
|
+
// Now you can call methods like:
|
|
66
|
+
// await ayaPayClient.getToken({ grantType: 'client_credentials' });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📝 SDK Configuration and Initialization
|
|
70
|
+
|
|
71
|
+
The SDK requires specific credentials and endpoint information for initialization.
|
|
72
|
+
|
|
73
|
+
### `SDKOptions` Interface
|
|
74
|
+
|
|
75
|
+
This configuration object is passed to the initialization function.
|
|
76
|
+
|
|
77
|
+
| Property | Type | Required | Description |
|
|
78
|
+
| :--- | :--- | :---: | :--- |
|
|
79
|
+
| `baseUrl` | `string` | **Yes** | The base URL for the AYA Pay API (e.g., `https://api.gateway.com`). |
|
|
80
|
+
| `consumerKey` | `string` | **Yes** | Your merchant consumer key used for generating the Basic Token. |
|
|
81
|
+
| `consumerSecret` | `string` | **Yes** | Your merchant consumer secret used for generating the Basic Token. |
|
|
82
|
+
| `decryptionKey` | `string` | **Yes** | The AES-256-ECB secret key for decrypting payment callback data. |
|
|
83
|
+
| `basicKey` | `string` | No | *(Present in interface but currently unused in methods)*. |
|
|
84
|
+
|
|
85
|
+
### Initialization Example (TypeScript)
|
|
86
|
+
|
|
87
|
+
The `AYAPayMerchantApi` function returns a class instance containing all core methods.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import { AYAPayMerchantApi, SDKOptions } from '@mmpay/aya-mmqr';
|
|
91
|
+
|
|
92
|
+
const options: SDKOptions = {
|
|
93
|
+
baseUrl: 'https://sandbox.ayapay.com/api', // <<< Request HERE I cannot disclose for security reasons
|
|
94
|
+
consumerKey: 'YOUR_CONSUMER_KEY',
|
|
95
|
+
consumerSecret: 'YOUR_CONSUMER_SECRET',
|
|
96
|
+
decryptionKey: 'YOUR_DECRYPTION_KEY_32_BYTES',
|
|
97
|
+
basicKey: '', // Can be an empty string
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const ayaPayClient = AYAPayMerchantApi(options);
|
|
101
|
+
|
|
102
|
+
// Now you can call methods like:
|
|
103
|
+
// await ayaPayClient.getToken({ grantType: 'client_credentials' });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 🔑 Authentication and Token Management
|
|
109
|
+
|
|
110
|
+
All merchant API interactions require two tokens: the **Key Token** (for `Token` header) and the **API Token** (for `Authorization` header).
|
|
111
|
+
|
|
112
|
+
### 1. `getToken()`
|
|
113
|
+
|
|
114
|
+
Requests the primary Key Token using the Client Credentials grant type. This token is stored internally and used in the `Token` header.
|
|
115
|
+
|
|
116
|
+
| Parameter | Type | Description |
|
|
117
|
+
| :--- | :--- | :--- |
|
|
118
|
+
| `options.grantType` | `string` | Must be set to `'client_credentials'`. |
|
|
119
|
+
|
|
120
|
+
#### `getTokenResponse` (Success Response)
|
|
121
|
+
|
|
122
|
+
| Property | Type | Description |
|
|
123
|
+
| :--- | :--- | :--- |
|
|
124
|
+
| `accessToken` | `string` | The Key Token obtained. |
|
|
125
|
+
| `tokenType` | `string` | Token type, e.g., `'Bearer'`. |
|
|
126
|
+
| `expiresIn` | `number` | Token expiry time in seconds. |
|
|
127
|
+
|
|
128
|
+
### 2. `login(options: loginRequest)`
|
|
129
|
+
|
|
130
|
+
Authenticates the merchant to retrieve the merchant-specific API Token and Refresh Token.
|
|
131
|
+
|
|
132
|
+
#### `loginRequest` (Parameters)
|
|
133
|
+
|
|
134
|
+
| Property | Type | Description |
|
|
135
|
+
| :--- | :--- | :--- |
|
|
136
|
+
| `phone` | `string` | Merchant's registered phone number. |
|
|
137
|
+
| `password` | `string` | Merchant's password. |
|
|
138
|
+
|
|
139
|
+
#### `loginResponse` (Success Response)
|
|
140
|
+
|
|
141
|
+
| Property | Type | Description |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
| `err` | `number` | Error code (0 for success). |
|
|
144
|
+
| `message` | `string` | Status message. |
|
|
145
|
+
| `token.token` | `string` | The API Token, stored internally and used for payment methods. |
|
|
146
|
+
| `refreshToken.token` | `string` | The Refresh Token. |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 💳 Payment Methods
|
|
151
|
+
|
|
152
|
+
### 1. `requestQR(options: PaymentCreateRequest)`
|
|
153
|
+
|
|
154
|
+
Generates a QR code for a customer to scan and pay.
|
|
155
|
+
|
|
156
|
+
| Parameter | Type | Description |
|
|
157
|
+
| :--- | :--- | :--- |
|
|
158
|
+
| `options` | `PaymentCreateRequest` | Object containing payment details. |
|
|
159
|
+
|
|
160
|
+
### 2. `requestPush(options: PaymentCreateRequest)`
|
|
161
|
+
|
|
162
|
+
Initiates a payment by sending a push notification request to the customer's payment app.
|
|
163
|
+
|
|
164
|
+
| Parameter | Type | Description |
|
|
165
|
+
| :--- | :--- | :--- |
|
|
166
|
+
| `options` | `PaymentCreateRequest` | Object containing payment details. |
|
|
167
|
+
|
|
168
|
+
#### `PaymentCreateRequest` (Parameters for QR/Push)
|
|
169
|
+
|
|
170
|
+
| Property | Type | Description |
|
|
171
|
+
| :--- | :--- | :--- |
|
|
172
|
+
| `amount` | `number` | Transaction amount. |
|
|
173
|
+
| `currency` | `string` | Currency code (e.g., `'MMK'`). |
|
|
174
|
+
| `externalTransactionId` | `string` | **Unique ID** from the merchant's system. |
|
|
175
|
+
| `serviceCode` | `string` | Merchant's service code. |
|
|
176
|
+
| `externalAdditionalData` | `string` | Optional field for extra merchant data. |
|
|
177
|
+
|
|
178
|
+
#### `PaymentCreateResponse` (Success Response)
|
|
179
|
+
|
|
180
|
+
| Property | Type | Description |
|
|
181
|
+
| :--- | :--- | :--- |
|
|
182
|
+
| `data.qrdata` | `string` | (QR) Raw QR code string. |
|
|
183
|
+
| `data.mmqrdata` | `string` | (QR) Myanmar QR code string. |
|
|
184
|
+
| `data.referenceNumber` | `string` | **Unique** transaction reference number from AYA Pay. |
|
|
185
|
+
| `data.amount` | `string` | Transaction amount confirmed. |
|
|
186
|
+
| `data.expiredAt` | `string` | Timestamp when the payment request expires. |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 🔍 Status Check Methods
|
|
191
|
+
|
|
192
|
+
### 1. `paymentStatusQR(options: PaymentStatusRequest)`
|
|
193
|
+
|
|
194
|
+
Checks the status of a previously requested QR payment.
|
|
195
|
+
|
|
196
|
+
### 2. `paymentStatusPush(options: PaymentStatusRequest)`
|
|
197
|
+
|
|
198
|
+
Checks the status of a previously requested Push payment.
|
|
199
|
+
|
|
200
|
+
#### `PaymentStatusRequest` (Parameters for Status Check)
|
|
201
|
+
|
|
202
|
+
| Property | Type | Description |
|
|
203
|
+
| :--- | :--- | :--- |
|
|
204
|
+
| `referenceNumber` | `string` | The AYA Pay unique transaction reference number. |
|
|
205
|
+
| `externalTransactionId` | `string` | The merchant's unique transaction ID. |
|
|
206
|
+
|
|
207
|
+
#### `PaymentStatusResponse` (Success Response)
|
|
208
|
+
|
|
209
|
+
| Property | Type | Description |
|
|
210
|
+
| :--- | :--- | :--- |
|
|
211
|
+
| `status` | `string` | The transaction status (e.g., `'PENDING'`, `'SUCCESS'`, `'FAILED'`). |
|
|
212
|
+
| `transRefId` | `string` | Alias for the AYA Pay transaction ID. |
|
|
213
|
+
| `err` | `number` | Error code (0 for success). |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 🔔 Callback Verification
|
|
218
|
+
|
|
219
|
+
This utility method is used by the merchant's callback endpoint to securely decrypt the transaction result payload sent by the AYA Pay system.
|
|
220
|
+
|
|
221
|
+
### `verifyCallback(options: CallbackEncoded)`
|
|
222
|
+
|
|
223
|
+
Decrypts the Base64-encoded, encrypted payload using AES-256-ECB with the configured `decryptionKey`.
|
|
224
|
+
|
|
225
|
+
| Parameter | Type | Description |
|
|
226
|
+
| :--- | :--- | :--- |
|
|
227
|
+
| `options.paymentResult` | `string` | The encrypted and Base64-encoded payment payload from the callback body. |
|
|
228
|
+
| `options.refundResult` | `string` | The encrypted refund payload (if applicable). |
|
|
229
|
+
| `options.externalTransactionId` | `string` | The merchant's unique ID for context. |
|
|
230
|
+
|
|
231
|
+
| Returns | Type | Description |
|
|
232
|
+
| :--- | :--- | :--- |
|
|
233
|
+
| `Promise<CallbackDecoded>` | The raw, decrypted JSON string of the transaction result. **Must be parsed** into an object for use. |
|
|
234
|
+
|
|
235
|
+
### Decryption Example
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Example handler for the merchant's callback endpoint (e.g., /api/payment/callback)
|
|
239
|
+
|
|
240
|
+
async function handleCallback(encodedBody) {
|
|
241
|
+
try {
|
|
242
|
+
const decryptedPayload = await ayaPayClient.verifyCallback(encodedBody);
|
|
243
|
+
// The result is a JSON string, which must be parsed
|
|
244
|
+
const paymentResult = JSON.parse(decryptedPayload as string);
|
|
245
|
+
// Process paymentResult (e.g., update order status)
|
|
246
|
+
console.log('Decrypted Status:', paymentResult.status);
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Decryption Failed:', error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
package/bun.lock
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 2,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@mmpay/aya-mmqr",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/node": "^26.0.0",
|
|
9
|
+
"dotenv": "^17.2.2",
|
|
10
|
+
"typescript": "^5.4.0",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
"packages": {
|
|
15
|
+
"@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="],
|
|
16
|
+
|
|
17
|
+
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
|
18
|
+
|
|
19
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
20
|
+
|
|
21
|
+
"undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CallbackDecoded, CallbackEncoded, getTokenResponse, loginResponse, PaymentCreateRequest, PaymentCreateResponse, PaymentStatusRequest, PaymentStatusResponse } from './types';
|
|
2
|
+
export { CallbackDecoded, CallbackEncoded, getTokenResponse, loginResponse, PaymentCreateRequest, PaymentCreateResponse, PaymentStatusRequest, PaymentStatusResponse };
|
|
3
|
+
/**
|
|
4
|
+
* @InstanceOptions
|
|
5
|
+
*/
|
|
6
|
+
export interface InstanceOptions {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
prefixUrl: string;
|
|
9
|
+
consumerKey: string;
|
|
10
|
+
consumerSecret: string;
|
|
11
|
+
decryptionKey: string;
|
|
12
|
+
phone: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @AYAPayMerchantApi
|
|
17
|
+
* @param {InstanceOptions} options
|
|
18
|
+
* @returns {AYAPayMerchantClass} A status message string.
|
|
19
|
+
*/
|
|
20
|
+
export declare function AYAPayMerchantApi(options: InstanceOptions): AYAPayMerchantClass;
|
|
21
|
+
/**
|
|
22
|
+
* @AYAPayMerchantClass
|
|
23
|
+
*/
|
|
24
|
+
declare class AYAPayMerchantClass {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(options: InstanceOptions);
|
|
27
|
+
/**
|
|
28
|
+
* basicToken
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
private basicToken;
|
|
32
|
+
/**
|
|
33
|
+
* getToken
|
|
34
|
+
* @returns {Promise<getTokenResponse>}
|
|
35
|
+
*/
|
|
36
|
+
private getToken;
|
|
37
|
+
/**
|
|
38
|
+
* login
|
|
39
|
+
* @returns {Promise<loginResponse>}
|
|
40
|
+
*/
|
|
41
|
+
private login;
|
|
42
|
+
/**
|
|
43
|
+
* authenticate
|
|
44
|
+
*/
|
|
45
|
+
authenticate(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* requestQR
|
|
48
|
+
* @param {PaymentCreateRequest} options
|
|
49
|
+
* ...
|
|
50
|
+
* @returns {Promise<PaymentCreateResponse>}
|
|
51
|
+
*/
|
|
52
|
+
requestQR(options: PaymentCreateRequest): Promise<PaymentCreateResponse>;
|
|
53
|
+
/**
|
|
54
|
+
* paymentStatusQR
|
|
55
|
+
* ...
|
|
56
|
+
*/
|
|
57
|
+
paymentStatusQR(options: PaymentStatusRequest): Promise<PaymentStatusResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* requestPush
|
|
60
|
+
* ...
|
|
61
|
+
*/
|
|
62
|
+
requestPush(options: PaymentCreateRequest): Promise<PaymentCreateResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* paymentStatusPush
|
|
65
|
+
* ...
|
|
66
|
+
*/
|
|
67
|
+
paymentStatusPush(options: PaymentStatusRequest): Promise<PaymentStatusResponse>;
|
|
68
|
+
/**
|
|
69
|
+
* verifyCallback
|
|
70
|
+
* ...
|
|
71
|
+
*/
|
|
72
|
+
verifyCallback(options: CallbackEncoded): Promise<CallbackDecoded>;
|
|
73
|
+
/**
|
|
74
|
+
* verifyCallbackRefund
|
|
75
|
+
* ...
|
|
76
|
+
*/
|
|
77
|
+
verifyCallbackRefund(options: CallbackEncoded): Promise<CallbackDecoded>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AYAPayMerchantApi = AYAPayMerchantApi;
|
|
7
|
+
const node_buffer_1 = require("node:buffer");
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
+
/**
|
|
10
|
+
* @AYAPayMerchantApi
|
|
11
|
+
* @param {InstanceOptions} options
|
|
12
|
+
* @returns {AYAPayMerchantClass} A status message string.
|
|
13
|
+
*/
|
|
14
|
+
function AYAPayMerchantApi(options) {
|
|
15
|
+
return new AYAPayMerchantClass({
|
|
16
|
+
baseUrl: options.baseUrl,
|
|
17
|
+
prefixUrl: options.prefixUrl,
|
|
18
|
+
consumerKey: options.consumerKey,
|
|
19
|
+
consumerSecret: options.consumerSecret,
|
|
20
|
+
decryptionKey: options.decryptionKey,
|
|
21
|
+
phone: options.phone,
|
|
22
|
+
password: options.password,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @AYAPayMerchantClass
|
|
27
|
+
*/
|
|
28
|
+
class AYAPayMerchantClass {
|
|
29
|
+
#baseUrl;
|
|
30
|
+
#prefixUrl;
|
|
31
|
+
#consumerKey;
|
|
32
|
+
#consumerSecret;
|
|
33
|
+
#decryptionKey;
|
|
34
|
+
#phone;
|
|
35
|
+
#password;
|
|
36
|
+
#keyToken = '';
|
|
37
|
+
#apiToken = '';
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.#baseUrl = options.baseUrl;
|
|
40
|
+
this.#prefixUrl = options.prefixUrl;
|
|
41
|
+
this.#consumerKey = options.consumerKey;
|
|
42
|
+
this.#consumerSecret = options.consumerSecret;
|
|
43
|
+
this.#decryptionKey = options.decryptionKey;
|
|
44
|
+
this.#phone = options.phone;
|
|
45
|
+
this.#password = options.password;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* basicToken
|
|
49
|
+
* @returns
|
|
50
|
+
*/
|
|
51
|
+
basicToken() {
|
|
52
|
+
const rawString = `${this.#consumerKey}:${this.#consumerSecret}`;
|
|
53
|
+
const buffer = node_buffer_1.Buffer.from(rawString);
|
|
54
|
+
const base64String = buffer.toString('base64');
|
|
55
|
+
return base64String;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* getToken
|
|
59
|
+
* @returns {Promise<getTokenResponse>}
|
|
60
|
+
*/
|
|
61
|
+
async getToken() {
|
|
62
|
+
try {
|
|
63
|
+
const config = {
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
66
|
+
'Authorization': `Basic ${this.basicToken()}`
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const body = {
|
|
70
|
+
grant_type: 'client_credentials'
|
|
71
|
+
};
|
|
72
|
+
const formBody = new URLSearchParams();
|
|
73
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
formBody.append(key, String(value));
|
|
76
|
+
});
|
|
77
|
+
const response = await fetch(`${this.#baseUrl}/token`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: config.headers,
|
|
80
|
+
body: formBody
|
|
81
|
+
});
|
|
82
|
+
const originResponse = (await response.json());
|
|
83
|
+
const modifiedResponse = {
|
|
84
|
+
accessToken: originResponse.access_token,
|
|
85
|
+
scope: originResponse.scope,
|
|
86
|
+
tokenType: originResponse.token_type,
|
|
87
|
+
expiresIn: originResponse.expires_in,
|
|
88
|
+
};
|
|
89
|
+
this.#keyToken = modifiedResponse.accessToken;
|
|
90
|
+
return modifiedResponse;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(error);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* login
|
|
99
|
+
* @returns {Promise<loginResponse>}
|
|
100
|
+
*/
|
|
101
|
+
async login() {
|
|
102
|
+
try {
|
|
103
|
+
const config = {
|
|
104
|
+
headers: {
|
|
105
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
106
|
+
'Token': `Bearer ${this.#keyToken}`
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const body = {
|
|
110
|
+
phone: this.#phone,
|
|
111
|
+
password: this.#password,
|
|
112
|
+
};
|
|
113
|
+
const formBody = new URLSearchParams();
|
|
114
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
115
|
+
if (value !== undefined)
|
|
116
|
+
formBody.append(key, String(value));
|
|
117
|
+
});
|
|
118
|
+
const response = await fetch(`${this.#baseUrl}/${this.#prefixUrl}/1.0.0/thirdparty/merchant/login`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: config.headers,
|
|
121
|
+
body: formBody
|
|
122
|
+
});
|
|
123
|
+
const loginResponse = (await response.json());
|
|
124
|
+
this.#apiToken = loginResponse.token.token;
|
|
125
|
+
return loginResponse;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(error);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* authenticate
|
|
134
|
+
*/
|
|
135
|
+
async authenticate() {
|
|
136
|
+
await this.getToken();
|
|
137
|
+
await this.login();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* requestQR
|
|
141
|
+
* @param {PaymentCreateRequest} options
|
|
142
|
+
* ...
|
|
143
|
+
* @returns {Promise<PaymentCreateResponse>}
|
|
144
|
+
*/
|
|
145
|
+
async requestQR(options) {
|
|
146
|
+
try {
|
|
147
|
+
await this.authenticate();
|
|
148
|
+
const config = {
|
|
149
|
+
headers: {
|
|
150
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
151
|
+
'Token': `Bearer ${this.#keyToken}`,
|
|
152
|
+
'Authorization': `Bearer ${this.#apiToken}`,
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const body = {
|
|
156
|
+
amount: options.amount,
|
|
157
|
+
currency: options.currency,
|
|
158
|
+
externalTransactionId: options.externalTransactionId,
|
|
159
|
+
externalAdditionalData: options.externalAdditionalData,
|
|
160
|
+
serviceCode: options.serviceCode,
|
|
161
|
+
MMQR: options.MMQR,
|
|
162
|
+
timelimit: options.timelimit,
|
|
163
|
+
};
|
|
164
|
+
const formBody = new URLSearchParams();
|
|
165
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
166
|
+
if (value !== undefined)
|
|
167
|
+
formBody.append(key, String(value));
|
|
168
|
+
});
|
|
169
|
+
const response = await fetch(`${this.#baseUrl}/${this.#prefixUrl}/1.0.0/thirdparty/merchant/v2/requestQRPayment`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: config.headers,
|
|
172
|
+
body: formBody
|
|
173
|
+
});
|
|
174
|
+
return (await response.json());
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error(error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* paymentStatusQR
|
|
183
|
+
* ...
|
|
184
|
+
*/
|
|
185
|
+
async paymentStatusQR(options) {
|
|
186
|
+
try {
|
|
187
|
+
await this.authenticate();
|
|
188
|
+
const config = {
|
|
189
|
+
headers: {
|
|
190
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
191
|
+
'Token': `Bearer ${this.#keyToken}`,
|
|
192
|
+
'Authorization': `Bearer ${this.#apiToken}`,
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const body = {
|
|
196
|
+
externalTransactionId: options.externalTransactionId,
|
|
197
|
+
referenceNumber: options.referenceNumber,
|
|
198
|
+
};
|
|
199
|
+
const formBody = new URLSearchParams();
|
|
200
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
201
|
+
if (value !== undefined)
|
|
202
|
+
formBody.append(key, String(value));
|
|
203
|
+
});
|
|
204
|
+
const response = await fetch(`${this.#baseUrl}/${this.#prefixUrl}/1.0.0/thirdparty/merchant/checkQRPayment`, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: config.headers,
|
|
207
|
+
body: formBody
|
|
208
|
+
});
|
|
209
|
+
return (await response.json());
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error(error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* requestPush
|
|
218
|
+
* ...
|
|
219
|
+
*/
|
|
220
|
+
async requestPush(options) {
|
|
221
|
+
try {
|
|
222
|
+
await this.authenticate();
|
|
223
|
+
const config = {
|
|
224
|
+
headers: {
|
|
225
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
226
|
+
'Token': `Bearer ${this.#keyToken}`,
|
|
227
|
+
'Authorization': `Bearer ${this.#apiToken}`,
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const body = {
|
|
231
|
+
amount: options.amount,
|
|
232
|
+
currency: options.currency,
|
|
233
|
+
externalTransactionId: options.externalTransactionId,
|
|
234
|
+
externalAdditionalData: options.externalAdditionalData,
|
|
235
|
+
serviceCode: options.serviceCode,
|
|
236
|
+
};
|
|
237
|
+
const formBody = new URLSearchParams();
|
|
238
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
239
|
+
if (value !== undefined)
|
|
240
|
+
formBody.append(key, String(value));
|
|
241
|
+
});
|
|
242
|
+
const response = await fetch(`${this.#baseUrl}/${this.#prefixUrl}/1.0.0/thirdparty/merchant/v2/requestPushPayment`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: config.headers,
|
|
245
|
+
body: formBody
|
|
246
|
+
});
|
|
247
|
+
return (await response.json());
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
console.error(error);
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* paymentStatusPush
|
|
256
|
+
* ...
|
|
257
|
+
*/
|
|
258
|
+
async paymentStatusPush(options) {
|
|
259
|
+
try {
|
|
260
|
+
await this.authenticate();
|
|
261
|
+
const config = {
|
|
262
|
+
headers: {
|
|
263
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
264
|
+
'Token': `Bearer ${this.#keyToken}`,
|
|
265
|
+
'Authorization': `Bearer ${this.#apiToken}`,
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const body = {
|
|
269
|
+
externalTransactionId: options.externalTransactionId,
|
|
270
|
+
referenceNumber: options.referenceNumber,
|
|
271
|
+
};
|
|
272
|
+
const formBody = new URLSearchParams();
|
|
273
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
274
|
+
if (value !== undefined)
|
|
275
|
+
formBody.append(key, String(value));
|
|
276
|
+
});
|
|
277
|
+
const response = await fetch(`${this.#baseUrl}/${this.#prefixUrl}/1.0.0/thirdparty/merchant/checkRequestPayment`, {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: config.headers,
|
|
280
|
+
body: formBody
|
|
281
|
+
});
|
|
282
|
+
return (await response.json());
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
console.error(error);
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* verifyCallback
|
|
291
|
+
* ...
|
|
292
|
+
*/
|
|
293
|
+
async verifyCallback(options) {
|
|
294
|
+
try {
|
|
295
|
+
if (!options.paymentResult)
|
|
296
|
+
throw new Error("paymentResult is missing or undefined");
|
|
297
|
+
const cipherRaw = node_buffer_1.Buffer.from(options.paymentResult, 'base64');
|
|
298
|
+
const key = node_buffer_1.Buffer.from(this.#decryptionKey);
|
|
299
|
+
const decipher = node_crypto_1.default.createDecipheriv('aes-256-ecb', key, null);
|
|
300
|
+
let decrypted = decipher.update(cipherRaw, undefined, 'utf8');
|
|
301
|
+
decrypted += decipher.final('utf8');
|
|
302
|
+
const result = JSON.parse(decrypted);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error(error);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* verifyCallbackRefund
|
|
312
|
+
* ...
|
|
313
|
+
*/
|
|
314
|
+
async verifyCallbackRefund(options) {
|
|
315
|
+
try {
|
|
316
|
+
if (!options.refundResult)
|
|
317
|
+
throw new Error("refundResult is missing or undefined");
|
|
318
|
+
const cipherRaw = node_buffer_1.Buffer.from(options.refundResult, 'base64');
|
|
319
|
+
const key = node_buffer_1.Buffer.from(this.#decryptionKey);
|
|
320
|
+
const decipher = node_crypto_1.default.createDecipheriv('aes-256-ecb', key, null);
|
|
321
|
+
let decrypted = decipher.update(cipherRaw, undefined, 'utf8');
|
|
322
|
+
decrypted += decipher.final('utf8');
|
|
323
|
+
const result = JSON.parse(decrypted);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error(error);
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|