@katorymnd/pawapay-node-sdk 2.6.2 → 2.8.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 +30 -16
- package/LICENSE +13 -7
- package/README.md +74 -1455
- package/package.json +24 -9
- package/scripts/install-sdk.js +123 -1
- package/src/api/ApiClient.js +373 -1
- package/src/config/Config.js +30 -72
- package/src/core/index.js +100 -0
- package/src/core/katorymnd_pawapay_core.darwin-arm64.node +0 -0
- package/src/core/katorymnd_pawapay_core.darwin-x64.node +0 -0
- package/src/core/katorymnd_pawapay_core.linux-arm64-gnu.node +0 -0
- package/src/core/katorymnd_pawapay_core.linux-x64-gnu.node +0 -0
- package/src/core/katorymnd_pawapay_core.linux-x64-musl.node +0 -0
- package/src/core/katorymnd_pawapay_core.node +0 -0
- package/src/core/katorymnd_pawapay_core.win32-arm64-msvc.node +0 -0
- package/src/core/katorymnd_pawapay_core.win32-x64-msvc.node +0 -0
- package/src/utils/license/integrity.js +170 -1
- package/src/utils/license/protection.js +275 -1
- package/src/utils/license/server-check.js +326 -1
- package/src/utils/license/validator.js +42 -1
- package/src/utils/validator.js +2 -25
- package/src/utils/vm/bytecode-encoder.js +205 -1
- package/src/utils/vm/degradation-manager.js +146 -1
- package/src/utils/vm/interpreter.js +179 -1
package/README.md
CHANGED
|
@@ -1,1517 +1,136 @@
|
|
|
1
|
-
# pawaPay Node.js SDK
|
|
2
|
-
|
|
3
|
-
A professional, commercial Node.js SDK for integrating with the **pawaPay API**.
|
|
4
|
-
This SDK enables seamless payment processing with **mobile money deposits, refunds, payouts**, and **real-time transaction verification**, designed for production-grade systems.
|
|
5
|
-
|
|
6
|
-
> **Commercial Software**
|
|
7
|
-
> This SDK is a premium product. A valid license key is required to operate it in production environments.
|
|
8
|
-
|
|
9
|
-
> **Versioning Note**
|
|
10
|
-
> V1 is the default main codebase. V2 has been surgically integrated and can run alongside V1, selectable through configuration without breaking existing implementations.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Folder Structure
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
example/
|
|
18
|
-
data/
|
|
19
|
-
src/
|
|
20
|
-
tests/
|
|
21
|
-
logs/
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
* **example**
|
|
25
|
-
Contains live and demo samples for each major workflow, including deposits, refunds, payouts, and configuration fetching. Both frontend and backend usage patterns are demonstrated.
|
|
26
|
-
|
|
27
|
-
* **data**
|
|
28
|
-
Stores generated and cached configuration files such as `mno_availability` and `active_conf` JSON files for both V1 and V2.
|
|
29
|
-

|
|
30
|
-
* **src**
|
|
31
|
-
Core SDK source code and internal architecture.
|
|
32
|
-
|
|
33
|
-
* **logs**
|
|
34
|
-
Cotains logs files for any request - failed or successful.
|
|
35
|
-
|
|
36
|
-
* **tests**
|
|
37
|
-
Automated test suites using Mocha or Jest, covering SDK logic and transaction workflows.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Available Features
|
|
42
|
-
|
|
43
|
-
The pawaPay Node.js SDK provides a comprehensive, production-ready feature set with real-time validation at every stage.
|
|
44
|
-
|
|
45
|
-
### Mobile Money Deposit Request
|
|
46
|
-
|
|
47
|
-

|
|
48
|
-
|
|
49
|
-
Initiate deposit requests to mobile money accounts with built-in real-time transaction verification. Each deposit is validated immediately, ensuring accurate and current status reporting.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
### Mobile Money Refund Request
|
|
54
|
-
|
|
55
|
-

|
|
56
|
-
|
|
57
|
-
Process refunds for completed deposits using the original `depositId`. Refund availability depends on your pawaPay merchant configuration, and all refund requests are verified in real time.
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
### Mobile Money Payout Request
|
|
62
|
-
|
|
63
|
-

|
|
64
|
-
|
|
65
|
-
Execute payouts to single or multiple recipients within one request. This feature is optimized for bulk payments and includes real-time payout status tracking.
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
### Real-Time Transaction Verification
|
|
70
|
-
|
|
71
|
-
All operations, deposits, refunds, and payouts, are verified in real time. This ensures reliable, up-to-date transaction states without polling delays or stale responses.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
### Country-Specific Payment Configuration
|
|
76
|
-
|
|
77
|
-
The SDK dynamically fetches supported Mobile Network Operators (MNOs) based on the country associated with your merchant account. This prevents attempts against inactive or unsupported operators.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
### Mobile Network Operator (MNO) Status Checks
|
|
82
|
-
|
|
83
|
-
Before initiating a transaction, the SDK verifies MNO availability in real time. This minimizes failed payments caused by inactive networks.
|
|
84
|
-
|
|
85
|
-
---
|
|
86
1
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-

|
|
2
|
+
# pawaPay Node.js SDK
|
|
90
3
|
|
|
91
|
-
|
|
4
|
+
A professional, high-performance Node.js SDK for integrating with the **pawaPay API**.
|
|
5
|
+
This SDK provides a streamlined interface for **mobile money deposits, refunds, payouts**, and **real-time transaction verification**, built for production-grade fintech applications.
|
|
92
6
|
|
|
93
7
|
---
|
|
94
8
|
|
|
95
|
-
|
|
96
|
-

|
|
97
|
-
Use the hosted payment widget to collect payments through a secure redirect flow.
|
|
9
|
+
## 🏗️ Architecture
|
|
98
10
|
|
|
99
|
-
|
|
100
|
-
2. pawaPay returns a `redirectUrl`.
|
|
101
|
-
3. The customer completes payment on the hosted page.
|
|
102
|
-
4. Upon success, the customer is redirected back to your application:
|
|
11
|
+
The SDK is designed for maximum reliability and speed. It utilizes a **Native Core Integration** to handle complex cryptographic operations and data normalization, ensuring that your payment processing is both fast and secure across all supported environments.
|
|
103
12
|
|
|
13
|
+
### Folder Structure
|
|
14
|
+
```text
|
|
15
|
+
example/ # Implementation samples and workflows
|
|
16
|
+
data/ # Localized MNO & API configurations
|
|
17
|
+
src/ # SDK Source code
|
|
18
|
+
├── api/ # API Request wrappers
|
|
19
|
+
├── core/ # Native runtime binaries
|
|
20
|
+
└── utils/ # Internal helper logic
|
|
21
|
+
tests/ # Automated test suites
|
|
22
|
+
scripts/ # SDK management utilities
|
|
104
23
|
```
|
|
105
|
-
deposit-page-success?depositId=951e084a-005c-4976-ad4e-205ddedb914e
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
You may then activate services, store customer details, or trigger post-payment workflows as required.
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
### Sandbox and Live Environments
|
|
113
|
-
|
|
114
|
-
Easily switch between sandbox and production environments using environment variables. No code changes are required.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Table of Contents
|
|
119
|
-
|
|
120
|
-
* [Overview](#overview)
|
|
121
|
-
* [Licensing & Pricing](#licensing--pricing)
|
|
122
|
-
* [Installation](#installation)
|
|
123
|
-
* [Configuration (.env)](#configuration-env)
|
|
124
|
-
* [Usage](#usage)
|
|
125
|
-
|
|
126
|
-
* [Initializing the SDK(The brain)](#initializing-the-sdk)
|
|
127
|
-
* [The SDK heart](#the-sdk-heart)
|
|
128
|
-
* [Deposit Senario(MNO)](#deposit-senariomno)
|
|
129
|
-
* [Deposit Senario (Hosted Page)](#deposit-senario-hosted-page)
|
|
130
|
-
* [Payout Senario](#payout-senario)
|
|
131
|
-
* [Refund Senario](#refund-senario)
|
|
132
|
-
* [MNO Configuration & Version Switching](#mno-configuration--version-switching)
|
|
133
|
-
|
|
134
|
-
* [Support](#support)
|
|
135
24
|
|
|
136
25
|
---
|
|
137
26
|
|
|
138
|
-
##
|
|
27
|
+
## 🚀 Key Features
|
|
139
28
|
|
|
140
|
-
|
|
141
|
-
|
|
29
|
+
| Feature | Description |
|
|
30
|
+
| :--- | :--- |
|
|
31
|
+
| **Native Performance** | Core logic is executed via optimized native bindings. |
|
|
32
|
+
| **Hybrid V1/V2 Support** | Toggle between pawaPay API V1 and V2 within the same project. |
|
|
33
|
+
| **MNO Intelligence** | Automatic network availability checks before payment initiation. |
|
|
34
|
+
| **Enterprise Logging** | Structured logging for successful and failed transaction flows. |
|
|
35
|
+
| **Platform Optimized** | Pre-compiled for Windows, Linux, and macOS (Intel & Apple Silicon). |
|
|
142
36
|
|
|
143
37
|
---
|
|
144
38
|
|
|
145
|
-
##
|
|
146
|
-
|
|
147
|
-
This is a **paid commercial SDK**.
|
|
39
|
+
## 📦 Installation
|
|
148
40
|
|
|
149
|
-
|
|
150
|
-
* **Validity:** Lifetime license
|
|
151
|
-
* **Scope:** One licensed domain per key
|
|
152
|
-
|
|
153
|
-
To purchase a license and obtain your credentials:
|
|
154
|
-
|
|
155
|
-
👉 **[https://katorymnd.com/pawapay-payment-sdk/nodejs](https://katorymnd.com/pawapay-payment-sdk/nodejs)**
|
|
156
|
-
|
|
157
|
-
You will receive:
|
|
158
|
-
|
|
159
|
-
* `KATORYMND_PAWAPAY_SDK_LICENSE_KEY`
|
|
160
|
-
* `PAWAPAY_SDK_LICENSE_SECRET`
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Installation
|
|
41
|
+
The SDK is cross-platform and will automatically utilize the correct native components for your specific server architecture.
|
|
165
42
|
|
|
166
43
|
```bash
|
|
167
44
|
npm install @katorymnd/pawapay-node-sdk
|
|
168
45
|
# or
|
|
169
|
-
|
|
46
|
+
pnpm add @katorymnd/pawapay-node-sdk
|
|
170
47
|
```
|
|
171
48
|
|
|
172
49
|
---
|
|
173
50
|
|
|
174
|
-
## Configuration (.env)
|
|
51
|
+
## 🛠️ Configuration (.env)
|
|
175
52
|
|
|
176
|
-
|
|
53
|
+
Set up your project environment with your pawaPay credentials and SDK access keys.
|
|
177
54
|
|
|
178
55
|
```bash
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# ===============================
|
|
182
|
-
|
|
56
|
+
# .env.example
|
|
57
|
+
# Sandbox API token (for testing environment)
|
|
183
58
|
PAWAPAY_SANDBOX_API_TOKEN=your_sandbox_api_token_here
|
|
184
|
-
PAWAPAY_PRODUCTION_API_TOKEN=your_production_api_token_here
|
|
185
|
-
|
|
186
|
-
# ===============================
|
|
187
|
-
# Katorymnd pawaPay SDK Licensing
|
|
188
|
-
# ===============================
|
|
189
59
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
PAWAPAY_SDK_LICENSE_SECRET=your_sdk_license_secret_here
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## Usage
|
|
60
|
+
# Production API token (for live environment)
|
|
61
|
+
PAWAPAY_PRODUCTION_API_TOKEN=your_production_api_token_here
|
|
198
62
|
|
|
199
|
-
|
|
63
|
+
# License key used by the Katorymnd PawaPay SDK
|
|
64
|
+
KATORYMND_PAWAPAY_SDK_LICENSE_KEY=your_license_key_here
|
|
200
65
|
|
|
201
|
-
|
|
66
|
+
# License domain (used for license validation)
|
|
67
|
+
PAWAPAY_SDK_LICENSE_DOMAIN=your_license_domain_here
|
|
202
68
|
|
|
203
|
-
|
|
204
|
-
|
|
69
|
+
# License secret (used for license validation)
|
|
70
|
+
PAWAPAY_SDK_LICENSE_SECRET=your_license_secret_here
|
|
205
71
|
```
|
|
206
72
|
|
|
207
|
-
On first initialization, the SDK securely binds:
|
|
208
|
-
|
|
209
|
-
* Your license key
|
|
210
|
-
* Your license secret
|
|
211
|
-
* Your domain
|
|
212
|
-
|
|
213
|
-
This binding is permanent for that license and prevents unauthorized reuse across domains.
|
|
214
|
-
|
|
215
73
|
---
|
|
216
|
-
### The SDK heart
|
|
217
|
-
|
|
218
|
-
At this stage i assume that the user has arleady purchased the premium packge and also installed the SDK to there work space.
|
|
219
74
|
|
|
220
|
-
|
|
75
|
+
## 💻 Usage
|
|
221
76
|
|
|
222
|
-
|
|
77
|
+
To begin, initialize the `ApiClient`. The SDK will perform a quick environmental check and prepare the native core for processing.
|
|
223
78
|
|
|
224
|
-
```
|
|
225
|
-
|
|
79
|
+
```javascript
|
|
80
|
+
const { ApiClient } = require('@katorymnd/pawapay-node-sdk');
|
|
226
81
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const { ApiClient, Helpers, FailureCodeHelper } = require('@katorymnd/pawapay-node-sdk');
|
|
234
|
-
|
|
235
|
-
// ========== LOGGING SETUP ==========
|
|
236
|
-
const logsDir = path.resolve(__dirname, '../logs');
|
|
237
|
-
|
|
238
|
-
const logger = winston.createLogger({
|
|
239
|
-
format: winston.format.combine(
|
|
240
|
-
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
241
|
-
winston.format.errors({ stack: true }),
|
|
242
|
-
winston.format.json()
|
|
243
|
-
),
|
|
244
|
-
transports: [
|
|
245
|
-
new winston.transports.File({
|
|
246
|
-
filename: path.join(logsDir, 'payment_success.log'),
|
|
247
|
-
level: 'info',
|
|
248
|
-
format: winston.format.combine(
|
|
249
|
-
winston.format.timestamp(),
|
|
250
|
-
winston.format.json()
|
|
251
|
-
)
|
|
252
|
-
}),
|
|
253
|
-
new winston.transports.File({
|
|
254
|
-
filename: path.join(logsDir, 'payment_failed.log'),
|
|
255
|
-
level: 'error',
|
|
256
|
-
format: winston.format.combine(
|
|
257
|
-
winston.format.timestamp(),
|
|
258
|
-
winston.format.json()
|
|
259
|
-
)
|
|
260
|
-
})
|
|
261
|
-
]
|
|
82
|
+
const client = new ApiClient({
|
|
83
|
+
apiToken: process.env.PAWAPAY_SANDBOX_API_TOKEN,
|
|
84
|
+
environment: 'sandbox',
|
|
85
|
+
licenseKey: process.env.KATORYMND_PAWAPAY_SDK_LICENSE_KEY,
|
|
86
|
+
apiVersion: 'v2',
|
|
87
|
+
sslVerify: false
|
|
262
88
|
});
|
|
89
|
+
```
|
|
263
90
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
winston.format.colorize(),
|
|
268
|
-
winston.format.simple()
|
|
269
|
-
)
|
|
270
|
-
}));
|
|
271
|
-
}
|
|
272
|
-
// ========== END LOGGING SETUP ==========
|
|
273
|
-
|
|
274
|
-
class PawaPayService {
|
|
275
|
-
/**
|
|
276
|
-
* @param {Object} config - Optional configuration
|
|
277
|
-
* @param {string} config.token - Custom API Token to override .env
|
|
278
|
-
*/
|
|
279
|
-
constructor(config = {}) {
|
|
280
|
-
// Prioritize ENV
|
|
281
|
-
const activeToken = process.env.PAWAPAY_SANDBOX_API_TOKEN;
|
|
282
|
-
|
|
283
|
-
// Debug log to confirm token is being used (masked)
|
|
284
|
-
const maskedToken = activeToken ? `${activeToken.substring(0, 5)}...` : 'NONE';
|
|
285
|
-
console.log(`[PawaPayService] Initializing with token: ${maskedToken}`);
|
|
286
|
-
|
|
287
|
-
this.pawapay = new ApiClient({
|
|
288
|
-
apiToken: activeToken,
|
|
289
|
-
environment: 'sandbox', //production/sandbox
|
|
290
|
-
licenseKey: process.env.KATORYMND_PAWAPAY_SDK_LICENSE_KEY, //required
|
|
291
|
-
sslVerify: false // true -> production
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Deposit money to a mobile money account
|
|
296
|
-
* @param {Object} depositData - Deposit details
|
|
297
|
-
* @param {string} apiVersion - 'v1' or 'v2'
|
|
298
|
-
*/
|
|
299
|
-
async deposit(depositData, apiVersion = 'v1') {
|
|
300
|
-
// Use the Helper directly from the SDK
|
|
301
|
-
const depositId = Helpers.generateUniqueId();
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
const {
|
|
305
|
-
amount,
|
|
306
|
-
currency,
|
|
307
|
-
mno,
|
|
308
|
-
payerMsisdn,
|
|
309
|
-
description,
|
|
310
|
-
metadata = []
|
|
311
|
-
} = depositData;
|
|
312
|
-
|
|
313
|
-
// 1. STRICT VALIDATION
|
|
314
|
-
if (!amount || !mno || !payerMsisdn || !description || !currency) {
|
|
315
|
-
const missingMsg = 'Validation failed - Missing required fields';
|
|
316
|
-
logger.error(missingMsg, depositData);
|
|
317
|
-
return { success: false, error: missingMsg };
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Validate Amount
|
|
321
|
-
const amountRegex = /^\d+(\.\d{1,2})?$/;
|
|
322
|
-
if (!amountRegex.test(amount) || parseFloat(amount) <= 0) {
|
|
323
|
-
const msg = 'Invalid amount. Must be positive with max 2 decimals.';
|
|
324
|
-
logger.error(msg, { amount });
|
|
325
|
-
return { success: false, error: msg };
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Validate Description
|
|
329
|
-
const descriptionRegex = /^[A-Za-z0-9 ]{1,22}$/;
|
|
330
|
-
if (!descriptionRegex.test(description)) {
|
|
331
|
-
const msg = 'Invalid description. Max 22 chars, alphanumeric only.';
|
|
332
|
-
logger.error(msg, { description });
|
|
333
|
-
return { success: false, error: msg };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
logger.info('Initiating deposit', {
|
|
337
|
-
depositId,
|
|
338
|
-
amount,
|
|
339
|
-
currency,
|
|
340
|
-
mno,
|
|
341
|
-
apiVersion
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// 2. PROCESS DEPOSIT
|
|
345
|
-
let response;
|
|
346
|
-
|
|
347
|
-
if (apiVersion === 'v2') {
|
|
348
|
-
response = await this.pawapay.initiateDepositV2(
|
|
349
|
-
depositId,
|
|
350
|
-
amount,
|
|
351
|
-
currency,
|
|
352
|
-
payerMsisdn,
|
|
353
|
-
mno, // provider
|
|
354
|
-
description, // customerMessage
|
|
355
|
-
null, // clientReferenceId
|
|
356
|
-
null, // preAuthorisationCode
|
|
357
|
-
metadata
|
|
358
|
-
);
|
|
359
|
-
} else {
|
|
360
|
-
response = await this.pawapay.initiateDeposit(
|
|
361
|
-
depositId,
|
|
362
|
-
amount,
|
|
363
|
-
currency,
|
|
364
|
-
mno, // correspondent
|
|
365
|
-
payerMsisdn,
|
|
366
|
-
description, // statementDescription
|
|
367
|
-
metadata
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// 3. HANDLE RESPONSE
|
|
372
|
-
if (response.status === 200 || response.status === 201) {
|
|
373
|
-
logger.info('Deposit initiated successfully', { depositId, status: response.status });
|
|
374
|
-
|
|
375
|
-
const statusCheck = await this.checkTransactionStatus(depositId, apiVersion);
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
success: true,
|
|
379
|
-
depositId,
|
|
380
|
-
transactionId: depositId,
|
|
381
|
-
reference: depositId,
|
|
382
|
-
status: statusCheck.status || 'SUBMITTED',
|
|
383
|
-
message: 'Deposit initiated successfully',
|
|
384
|
-
rawResponse: response,
|
|
385
|
-
statusCheck: statusCheck
|
|
386
|
-
};
|
|
387
|
-
} else {
|
|
388
|
-
// 4. HANDLE ERRORS
|
|
389
|
-
let errorMessage = 'Deposit initiation failed';
|
|
390
|
-
let failureCode = 'UNKNOWN';
|
|
391
|
-
|
|
392
|
-
if (response.response?.rejectionReason?.rejectionMessage) {
|
|
393
|
-
errorMessage = response.response.rejectionReason.rejectionMessage;
|
|
394
|
-
} else if (response.response?.failureReason?.failureCode) {
|
|
395
|
-
failureCode = response.response.failureReason.failureCode;
|
|
396
|
-
// Use the SDK's built-in error helper
|
|
397
|
-
errorMessage = FailureCodeHelper.getFailureMessage(failureCode);
|
|
398
|
-
} else if (response.response?.message) {
|
|
399
|
-
errorMessage = response.response.message;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
logger.error('Deposit initiation failed', {
|
|
403
|
-
depositId,
|
|
404
|
-
error: errorMessage,
|
|
405
|
-
failureCode,
|
|
406
|
-
response: response.response
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
success: false,
|
|
411
|
-
error: errorMessage,
|
|
412
|
-
depositId,
|
|
413
|
-
statusCode: response.status,
|
|
414
|
-
rawResponse: response
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
} catch (error) {
|
|
419
|
-
logger.error('System Error during deposit', {
|
|
420
|
-
depositId,
|
|
421
|
-
error: error.message,
|
|
422
|
-
stack: error.stack
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
return {
|
|
426
|
-
success: false,
|
|
427
|
-
error: error.message || 'Internal processing error',
|
|
428
|
-
depositId
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Check Status of ANY transaction (Deposit, Payout, Refund)
|
|
435
|
-
* @param {string} transactionId - The ID to check
|
|
436
|
-
* @param {string} apiVersion - 'v1' or 'v2'
|
|
437
|
-
* @param {string} type - 'deposit', 'payout', 'refund', 'remittance'
|
|
438
|
-
*/
|
|
439
|
-
async checkTransactionStatus(transactionId, apiVersion = 'v1', type = 'deposit') {
|
|
440
|
-
try {
|
|
441
|
-
let response;
|
|
442
|
-
|
|
443
|
-
// Pass the 'type' to the SDK so it hits the correct endpoint (e.g., /payouts vs /deposits)
|
|
444
|
-
if (apiVersion === 'v2') {
|
|
445
|
-
response = await this.pawapay.checkTransactionStatusV2(transactionId, type);
|
|
446
|
-
} else {
|
|
447
|
-
response = await this.pawapay.checkTransactionStatus(transactionId, type);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
logger.info(`Checking ${type} status`, { transactionId, status: response.status });
|
|
451
|
-
|
|
452
|
-
if (response.status === 200) {
|
|
453
|
-
let data;
|
|
454
|
-
let status;
|
|
455
|
-
|
|
456
|
-
// Normalize V1 (Array/Object) vs V2 (Object wrapper)
|
|
457
|
-
if (apiVersion === 'v2') {
|
|
458
|
-
if (response.response?.status !== 'FOUND') {
|
|
459
|
-
return {
|
|
460
|
-
success: true,
|
|
461
|
-
status: 'PROCESSING',
|
|
462
|
-
transactionId,
|
|
463
|
-
message: 'Transaction processing'
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
data = response.response.data;
|
|
467
|
-
status = data?.status || 'UNKNOWN';
|
|
468
|
-
} else {
|
|
469
|
-
// V1 legacy can be array [ { ... } ] or object
|
|
470
|
-
const raw = response.response;
|
|
471
|
-
data = Array.isArray(raw) ? raw[0] : raw;
|
|
472
|
-
status = data?.status || 'UNKNOWN';
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
success: true,
|
|
477
|
-
status: status,
|
|
478
|
-
transactionId: transactionId,
|
|
479
|
-
data: data,
|
|
480
|
-
rawResponse: response
|
|
481
|
-
};
|
|
482
|
-
} else {
|
|
483
|
-
return {
|
|
484
|
-
success: false,
|
|
485
|
-
error: `Status check failed with code ${response.status}`,
|
|
486
|
-
statusCode: response.status
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
} catch (error) {
|
|
490
|
-
logger.error('Status check error', { error: error.message });
|
|
491
|
-
return {
|
|
492
|
-
success: false,
|
|
493
|
-
error: error.message || 'Status check failed'
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
validateToken(token) {
|
|
499
|
-
if (!token || token.trim() === '') return { isValid: false, error: 'Token is required' };
|
|
500
|
-
return { isValid: true, type: 'JWT', message: 'Valid token format' };
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Create a Payment Page Session
|
|
505
|
-
* @param {Object} pageData - Payment details
|
|
506
|
-
* @param {string} apiVersion - 'v1' or 'v2'
|
|
507
|
-
*/
|
|
508
|
-
async initiatePaymentPage(pageData, apiVersion = 'v1') {
|
|
509
|
-
const depositId = Helpers.generateUniqueId();
|
|
510
|
-
|
|
511
|
-
try {
|
|
512
|
-
const {
|
|
513
|
-
amount,
|
|
514
|
-
currency,
|
|
515
|
-
payerMsisdn,
|
|
516
|
-
description,
|
|
517
|
-
returnUrl,
|
|
518
|
-
metadata = [],
|
|
519
|
-
country = 'UGA', // Default to Uganda for testing
|
|
520
|
-
reason = 'Payment'
|
|
521
|
-
} = pageData;
|
|
522
|
-
|
|
523
|
-
// 1. STRICT VALIDATION
|
|
524
|
-
if (!amount || !description || !currency || !returnUrl) {
|
|
525
|
-
const missingMsg = 'Validation failed - Missing required fields (returnUrl is mandatory)';
|
|
526
|
-
logger.error(missingMsg, pageData);
|
|
527
|
-
return { success: false, error: missingMsg };
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
logger.info('Initiating Payment Page', {
|
|
531
|
-
depositId,
|
|
532
|
-
amount,
|
|
533
|
-
apiVersion
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// 2. PREPARE PAYLOAD & CALL SDK
|
|
537
|
-
let response;
|
|
538
|
-
|
|
539
|
-
// Normalize phone (remove +)
|
|
540
|
-
const cleanMsisdn = payerMsisdn ? payerMsisdn.replace(/\D/g, '') : null;
|
|
541
|
-
|
|
542
|
-
if (apiVersion === 'v2') {
|
|
543
|
-
// V2 Payload Construction
|
|
544
|
-
const v2Params = {
|
|
545
|
-
depositId,
|
|
546
|
-
returnUrl,
|
|
547
|
-
customerMessage: description,
|
|
548
|
-
amountDetails: {
|
|
549
|
-
amount: String(amount),
|
|
550
|
-
currency: currency
|
|
551
|
-
},
|
|
552
|
-
phoneNumber: cleanMsisdn,
|
|
553
|
-
country,
|
|
554
|
-
reason,
|
|
555
|
-
metadata
|
|
556
|
-
};
|
|
557
|
-
response = await this.pawapay.createPaymentPageSessionV2(v2Params);
|
|
558
|
-
} else {
|
|
559
|
-
// V1 Payload Construction
|
|
560
|
-
const v1Params = {
|
|
561
|
-
depositId,
|
|
562
|
-
returnUrl,
|
|
563
|
-
amount: String(amount),
|
|
564
|
-
currency,
|
|
565
|
-
msisdn: cleanMsisdn,
|
|
566
|
-
statementDescription: description,
|
|
567
|
-
country,
|
|
568
|
-
reason,
|
|
569
|
-
metadata
|
|
570
|
-
};
|
|
571
|
-
response = await this.pawapay.createPaymentPageSession(v1Params);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// 3. HANDLE RESPONSE
|
|
575
|
-
// Note: API returns 200/201 for success
|
|
576
|
-
if (response.status >= 200 && response.status < 300) {
|
|
577
|
-
const redirectUrl = response.response?.redirectUrl || response.response?.url;
|
|
578
|
-
|
|
579
|
-
logger.info('Payment Page created', { depositId, redirectUrl });
|
|
580
|
-
|
|
581
|
-
return {
|
|
582
|
-
success: true,
|
|
583
|
-
depositId,
|
|
584
|
-
redirectUrl,
|
|
585
|
-
message: 'Session created successfully',
|
|
586
|
-
rawResponse: response
|
|
587
|
-
};
|
|
588
|
-
} else {
|
|
589
|
-
// 4. HANDLE ERRORS
|
|
590
|
-
const errorMsg = response.response?.message || 'Failed to create payment session';
|
|
591
|
-
|
|
592
|
-
logger.error('Payment Page creation failed', {
|
|
593
|
-
depositId,
|
|
594
|
-
error: errorMsg,
|
|
595
|
-
response: response.response
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
return {
|
|
599
|
-
success: false,
|
|
600
|
-
error: errorMsg,
|
|
601
|
-
depositId,
|
|
602
|
-
statusCode: response.status
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
} catch (error) {
|
|
607
|
-
logger.error('System Error during payment page creation', {
|
|
608
|
-
depositId,
|
|
609
|
-
error: error.message,
|
|
610
|
-
stack: error.stack
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
return {
|
|
614
|
-
success: false,
|
|
615
|
-
error: error.message || 'Internal processing error',
|
|
616
|
-
depositId
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* Payout money to a mobile money account (Disbursement)
|
|
624
|
-
* @param {Object} payoutData - Payout details
|
|
625
|
-
* @param {string} apiVersion - 'v1' or 'v2'
|
|
626
|
-
*/
|
|
627
|
-
async payout(payoutData, apiVersion = 'v1') {
|
|
628
|
-
const payoutId = Helpers.generateUniqueId();
|
|
629
|
-
|
|
630
|
-
try {
|
|
631
|
-
// 1. EXTRACT DATA WITH FALLBACKS
|
|
632
|
-
let {
|
|
633
|
-
amount,
|
|
634
|
-
currency,
|
|
635
|
-
mno, // Logic might send this
|
|
636
|
-
provider, // V2 logic might send this
|
|
637
|
-
correspondent, // V1 logic might send this
|
|
638
|
-
recipientMsisdn,
|
|
639
|
-
description, // Direct description
|
|
640
|
-
statementDescription, // V1 alternative
|
|
641
|
-
customerMessage, // V2 alternative
|
|
642
|
-
reason, // Another possible field
|
|
643
|
-
metadata = []
|
|
644
|
-
} = payoutData;
|
|
645
|
-
|
|
646
|
-
// 🛠️ FIX: Normalize the operator code
|
|
647
|
-
// If 'mno' is undefined, use 'provider' (V2) or 'correspondent' (V1)
|
|
648
|
-
const resolvedMno = mno || provider || correspondent;
|
|
649
|
-
|
|
650
|
-
// ============================================================
|
|
651
|
-
// Ensure 'description' is never missing
|
|
652
|
-
// PawaPay API requires this field for both V1 and V2.
|
|
653
|
-
// ============================================================
|
|
654
|
-
const resolvedDescription = description
|
|
655
|
-
|| statementDescription
|
|
656
|
-
|| customerMessage
|
|
657
|
-
|| reason
|
|
658
|
-
|| 'Transaction Processing'; // Ultimate fallback
|
|
659
|
-
|
|
660
|
-
// 2. STRICT VALIDATION & DEBUG LOGGING
|
|
661
|
-
// We check specific fields to give a precise error message
|
|
662
|
-
const missingFields = [];
|
|
663
|
-
if (!amount) missingFields.push('amount');
|
|
664
|
-
if (!resolvedMno) missingFields.push(`mno (looked for: mno, provider, correspondent)`);
|
|
665
|
-
if (!recipientMsisdn) missingFields.push('recipientMsisdn');
|
|
666
|
-
if (!resolvedDescription) missingFields.push('description');
|
|
667
|
-
if (!currency) missingFields.push('currency');
|
|
668
|
-
|
|
669
|
-
if (missingFields.length > 0) {
|
|
670
|
-
const missingMsg = `Validation failed - Missing fields: [${missingFields.join(', ')}]`;
|
|
671
|
-
|
|
672
|
-
// 🔍 DEBUG: Construct a detailed log entry for the error.log
|
|
673
|
-
const debugPayload = {
|
|
674
|
-
ERROR_TYPE: 'PAYOUT_VALIDATION_ERROR',
|
|
675
|
-
PAYOUT_ID: payoutId,
|
|
676
|
-
API_VERSION: apiVersion,
|
|
677
|
-
MISSING: missingFields,
|
|
678
|
-
RESOLVED_MNO: resolvedMno || 'UNDEFINED (This is likely the issue)',
|
|
679
|
-
RESOLVED_DESCRIPTION: resolvedDescription || 'UNDEFINED',
|
|
680
|
-
RAW_RECEIVED: JSON.stringify(payoutData, null, 2) // Pretty print the full object
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
// Log to your system logger
|
|
684
|
-
logger.error(missingMsg, debugPayload);
|
|
685
|
-
|
|
686
|
-
// Return failure with details
|
|
687
|
-
return {
|
|
688
|
-
success: false,
|
|
689
|
-
error: missingMsg,
|
|
690
|
-
debug: debugPayload // Return this so the frontend/controller can see it too
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Assign the resolved values back to variables used in logic
|
|
695
|
-
mno = resolvedMno;
|
|
696
|
-
description = resolvedDescription; // CRITICAL: Update the description variable
|
|
697
|
-
|
|
698
|
-
// Validate Amount Format
|
|
699
|
-
const amountRegex = /^\d+(\.\d{1,2})?$/;
|
|
700
|
-
if (!amountRegex.test(amount) || parseFloat(amount) <= 0) {
|
|
701
|
-
const msg = 'Invalid amount. Must be positive with max 2 decimals.';
|
|
702
|
-
logger.error(msg, { amount, payoutId });
|
|
703
|
-
return { success: false, error: msg };
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
logger.info('Initiating payout', {
|
|
707
|
-
payoutId,
|
|
708
|
-
amount,
|
|
709
|
-
currency,
|
|
710
|
-
mno,
|
|
711
|
-
description, // Log the resolved description
|
|
712
|
-
apiVersion
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
// 3. PROCESS PAYOUT
|
|
716
|
-
let response;
|
|
717
|
-
|
|
718
|
-
if (apiVersion === 'v2') {
|
|
719
|
-
// V2 Payout
|
|
720
|
-
response = await this.pawapay.initiatePayoutV2(
|
|
721
|
-
payoutId,
|
|
722
|
-
amount,
|
|
723
|
-
currency,
|
|
724
|
-
recipientMsisdn,
|
|
725
|
-
mno, // provider
|
|
726
|
-
description, // customerMessage - using the resolved description
|
|
727
|
-
metadata
|
|
728
|
-
);
|
|
729
|
-
} else {
|
|
730
|
-
// V1 Payout
|
|
731
|
-
response = await this.pawapay.initiatePayout(
|
|
732
|
-
payoutId,
|
|
733
|
-
amount,
|
|
734
|
-
currency,
|
|
735
|
-
mno, // correspondent
|
|
736
|
-
recipientMsisdn, // recipient address
|
|
737
|
-
description, // statementDescription - using the resolved description
|
|
738
|
-
metadata
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// 4. HANDLE RESPONSE
|
|
743
|
-
if (response.status === 200 || response.status === 201 || response.status === 202) {
|
|
744
|
-
logger.info('Payout initiated successfully', {
|
|
745
|
-
payoutId,
|
|
746
|
-
status: response.status,
|
|
747
|
-
description // Log successful description
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
const statusCheck = await this.checkTransactionStatus(payoutId, apiVersion);
|
|
751
|
-
|
|
752
|
-
return {
|
|
753
|
-
success: true,
|
|
754
|
-
payoutId,
|
|
755
|
-
transactionId: payoutId,
|
|
756
|
-
status: statusCheck.status || 'SUBMITTED',
|
|
757
|
-
message: 'Payout initiated successfully',
|
|
758
|
-
rawResponse: response,
|
|
759
|
-
statusCheck: statusCheck
|
|
760
|
-
};
|
|
761
|
-
} else {
|
|
762
|
-
// 5. HANDLE ERRORS
|
|
763
|
-
let errorMessage = 'Payout initiation failed';
|
|
764
|
-
let failureCode = 'UNKNOWN';
|
|
765
|
-
|
|
766
|
-
if (response.response?.rejectionReason?.rejectionMessage) {
|
|
767
|
-
errorMessage = response.response.rejectionReason.rejectionMessage;
|
|
768
|
-
} else if (response.response?.failureReason?.failureCode) {
|
|
769
|
-
failureCode = response.response.failureReason.failureCode;
|
|
770
|
-
errorMessage = FailureCodeHelper.getFailureMessage(failureCode);
|
|
771
|
-
} else if (response.response?.message) {
|
|
772
|
-
errorMessage = response.response.message;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
logger.error('Payout initiation failed', {
|
|
776
|
-
payoutId,
|
|
777
|
-
error: errorMessage,
|
|
778
|
-
failureCode,
|
|
779
|
-
response: response.response,
|
|
780
|
-
description // Log description even on failure
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
return {
|
|
784
|
-
success: false,
|
|
785
|
-
error: errorMessage,
|
|
786
|
-
payoutId,
|
|
787
|
-
statusCode: response.status,
|
|
788
|
-
rawResponse: response
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
} catch (error) {
|
|
793
|
-
logger.error('System Error during payout', {
|
|
794
|
-
payoutId,
|
|
795
|
-
error: error.message,
|
|
796
|
-
stack: error.stack,
|
|
797
|
-
inputData: JSON.stringify(payoutData) // Log input on crash too
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
return {
|
|
801
|
-
success: false,
|
|
802
|
-
error: error.message || 'Internal processing error',
|
|
803
|
-
payoutId
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Initiate a Refund (Partial or Full)
|
|
811
|
-
* @param {Object} refundData - Refund details
|
|
812
|
-
* @param {string} apiVersion - 'v1' or 'v2'
|
|
813
|
-
*/
|
|
814
|
-
async refund(refundData, apiVersion = 'v1') {
|
|
815
|
-
const refundId = Helpers.generateUniqueId();
|
|
816
|
-
|
|
91
|
+
### 📤 Initiating a Deposit
|
|
92
|
+
```javascript
|
|
93
|
+
async function handlePayment() {
|
|
817
94
|
try {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
// 1. STRICT VALIDATION
|
|
827
|
-
if (!depositId || !amount) {
|
|
828
|
-
const missingMsg = 'Validation failed - Missing required fields (depositId, amount)';
|
|
829
|
-
logger.error(missingMsg, refundData);
|
|
830
|
-
return { success: false, error: missingMsg };
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// V2 Specific Validation
|
|
834
|
-
if (apiVersion === 'v2' && !currency) {
|
|
835
|
-
const msg = 'Validation failed - V2 Refunds require a currency code';
|
|
836
|
-
logger.error(msg, refundData);
|
|
837
|
-
return { success: false, error: msg };
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Validate Amount
|
|
841
|
-
const amountRegex = /^\d+(\.\d{1,2})?$/;
|
|
842
|
-
if (!amountRegex.test(amount) || parseFloat(amount) <= 0) {
|
|
843
|
-
const msg = 'Invalid amount. Must be positive with max 2 decimals.';
|
|
844
|
-
logger.error(msg, { amount });
|
|
845
|
-
return { success: false, error: msg };
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
logger.info('Initiating refund', {
|
|
849
|
-
refundId,
|
|
850
|
-
depositId,
|
|
851
|
-
amount,
|
|
852
|
-
currency,
|
|
853
|
-
apiVersion
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
// 2. PROCESS REFUND
|
|
857
|
-
let response;
|
|
858
|
-
|
|
859
|
-
if (apiVersion === 'v2') {
|
|
860
|
-
// V2 Refund
|
|
861
|
-
response = await this.pawapay.initiateRefundV2(
|
|
862
|
-
refundId,
|
|
863
|
-
depositId,
|
|
864
|
-
amount,
|
|
865
|
-
currency,
|
|
866
|
-
metadata
|
|
867
|
-
);
|
|
868
|
-
} else {
|
|
869
|
-
// V1 Refund
|
|
870
|
-
response = await this.pawapay.initiateRefund(
|
|
871
|
-
refundId,
|
|
872
|
-
depositId,
|
|
873
|
-
amount,
|
|
874
|
-
metadata
|
|
95
|
+
const response = await client.initiateDepositV2(
|
|
96
|
+
"unique-order-id-123",
|
|
97
|
+
"5000",
|
|
98
|
+
"UGX",
|
|
99
|
+
"256783456789",
|
|
100
|
+
"MTN_MOMO_UGA",
|
|
101
|
+
"Payment for Order #101"
|
|
875
102
|
);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
// 3. HANDLE RESPONSE
|
|
879
|
-
// Refunds typically return 200/201/202
|
|
880
|
-
if (response.status >= 200 && response.status < 300) {
|
|
881
|
-
logger.info('Refund initiated successfully', { refundId, status: response.status });
|
|
882
|
-
|
|
883
|
-
// Check status immediately (passing 'refund' as type is critical)
|
|
884
|
-
const statusCheck = await this.checkTransactionStatus(refundId, apiVersion, 'refund');
|
|
885
|
-
|
|
886
|
-
return {
|
|
887
|
-
success: true,
|
|
888
|
-
refundId,
|
|
889
|
-
transactionId: refundId,
|
|
890
|
-
depositId: depositId,
|
|
891
|
-
status: statusCheck.status || 'SUBMITTED',
|
|
892
|
-
message: 'Refund initiated successfully',
|
|
893
|
-
rawResponse: response,
|
|
894
|
-
statusCheck: statusCheck
|
|
895
|
-
};
|
|
896
|
-
} else {
|
|
897
|
-
// 4. HANDLE ERRORS
|
|
898
|
-
let errorMessage = 'Refund initiation failed';
|
|
899
|
-
let failureCode = 'UNKNOWN';
|
|
900
|
-
|
|
901
|
-
if (response.response?.rejectionReason?.rejectionMessage) {
|
|
902
|
-
errorMessage = response.response.rejectionReason.rejectionMessage;
|
|
903
|
-
} else if (response.response?.failureReason?.failureCode) {
|
|
904
|
-
failureCode = response.response.failureReason.failureCode;
|
|
905
|
-
errorMessage = FailureCodeHelper.getFailureMessage(failureCode);
|
|
906
|
-
} else if (response.response?.message) {
|
|
907
|
-
errorMessage = response.response.message;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
logger.error('Refund initiation failed', {
|
|
911
|
-
refundId,
|
|
912
|
-
depositId,
|
|
913
|
-
error: errorMessage,
|
|
914
|
-
failureCode,
|
|
915
|
-
response: response.response
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
return {
|
|
919
|
-
success: false,
|
|
920
|
-
error: errorMessage,
|
|
921
|
-
refundId,
|
|
922
|
-
statusCode: response.status,
|
|
923
|
-
rawResponse: response
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
|
|
103
|
+
|
|
104
|
+
console.log("Response:", response.response);
|
|
927
105
|
} catch (error) {
|
|
928
|
-
|
|
929
|
-
refundId,
|
|
930
|
-
depositId: refundData.depositId,
|
|
931
|
-
error: error.message,
|
|
932
|
-
stack: error.stack
|
|
933
|
-
});
|
|
934
|
-
|
|
935
|
-
return {
|
|
936
|
-
success: false,
|
|
937
|
-
error: error.message || 'Internal processing error',
|
|
938
|
-
refundId
|
|
939
|
-
};
|
|
106
|
+
console.error("API Error:", error.message);
|
|
940
107
|
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
108
|
}
|
|
944
|
-
|
|
945
|
-
module.exports = PawaPayService;
|
|
946
109
|
```
|
|
947
|
-
what our `pawapayService.js` has is all what the SDK needs to make any process possible.
|
|
948
|
-
|
|
949
|
-
**Lets demostrate how to use the heart with examples**
|
|
950
110
|
|
|
951
111
|
---
|
|
952
|
-
### Deposit Senario(MNO)
|
|
953
|
-
|
|
954
|
-
```typescript
|
|
955
|
-
//test-deposit.js
|
|
956
|
-
const path = require('path');
|
|
957
|
-
// Ensure strict loading of the root .env file
|
|
958
|
-
require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
|
|
959
|
-
|
|
960
|
-
const PawaPayService = require('./pawapayService');
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
* Run a full test of the PawaPay SDK integration (Deposits)
|
|
964
|
-
*/
|
|
965
|
-
async function testSDKConnection() {
|
|
966
|
-
console.log('\n========================================');
|
|
967
|
-
console.log('🧪 PAWAPAY SDK DEPOSIT TEST (With Status Check)');
|
|
968
|
-
console.log('========================================\n');
|
|
969
|
-
|
|
970
|
-
const service = new PawaPayService();
|
|
971
|
-
|
|
972
|
-
// 1. Validate Token Format
|
|
973
|
-
console.log('🔹 Step 1: Validating API Token...');
|
|
974
|
-
const testToken = process.env.PAWAPAY_SANDBOX_API_TOKEN;
|
|
975
|
-
const validation = service.validateToken(testToken);
|
|
976
|
-
|
|
977
|
-
if (!validation.isValid) {
|
|
978
|
-
console.error('❌ Token Validation Failed:', validation);
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
console.log('✅ Token looks valid:', validation.type);
|
|
982
112
|
|
|
983
|
-
|
|
984
|
-
const commonData = {
|
|
985
|
-
amount: '1000',
|
|
986
|
-
currency: 'UGX',
|
|
987
|
-
mno: 'MTN_MOMO_UGA',
|
|
988
|
-
payerMsisdn: '256783456789', // Valid Sandbox Payer
|
|
989
|
-
description: 'SDK Integration Test'
|
|
990
|
-
};
|
|
991
|
-
|
|
992
|
-
// --- 2. Test V1 Deposit ---
|
|
993
|
-
console.log('\n🔹 Step 2: Testing V1 Deposit...');
|
|
994
|
-
try {
|
|
995
|
-
const v1Result = await service.deposit({
|
|
996
|
-
...commonData,
|
|
997
|
-
description: 'V1 Test Payment'
|
|
998
|
-
}, 'v1');
|
|
999
|
-
|
|
1000
|
-
if (v1Result.success) {
|
|
1001
|
-
console.log('✅ V1 Initiation Success:', {
|
|
1002
|
-
depositId: v1Result.depositId,
|
|
1003
|
-
status: v1Result.status
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
// WAIT AND CHECK
|
|
1007
|
-
console.log('⏳ Waiting 5 seconds for V1 propagation...');
|
|
1008
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
1009
|
-
|
|
1010
|
-
console.log('🔍 Checking V1 Status...');
|
|
1011
|
-
const statusCheck = await service.checkTransactionStatus(
|
|
1012
|
-
v1Result.depositId,
|
|
1013
|
-
'v1',
|
|
1014
|
-
'deposit'
|
|
1015
|
-
);
|
|
1016
|
-
|
|
1017
|
-
console.log(`📊 Final V1 Status: [ ${statusCheck.status} ]`);
|
|
1018
|
-
if (statusCheck.status === 'FAILED') {
|
|
1019
|
-
console.warn(` Reason: ${statusCheck.data?.failureReason?.failureMessage || 'Unknown'}`);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
} else {
|
|
1023
|
-
console.error('❌ V1 Failed:', v1Result.error);
|
|
1024
|
-
}
|
|
1025
|
-
} catch (error) {
|
|
1026
|
-
console.error('❌ V1 Exception:', error.message);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// --- 3. Test V2 Deposit ---
|
|
1030
|
-
console.log('\n🔹 Step 3: Testing V2 Deposit...');
|
|
1031
|
-
try {
|
|
1032
|
-
const v2Result = await service.deposit({
|
|
1033
|
-
...commonData,
|
|
1034
|
-
description: 'V2 Test Payment',
|
|
1035
|
-
metadata: [
|
|
1036
|
-
{ orderId: "ORD-SDK-TEST" },
|
|
1037
|
-
{ customerId: "test-user@example.com", isPII: true }
|
|
1038
|
-
]
|
|
1039
|
-
}, 'v2');
|
|
1040
|
-
|
|
1041
|
-
if (v2Result.success) {
|
|
1042
|
-
console.log('✅ V2 Initiation Success:', {
|
|
1043
|
-
depositId: v2Result.depositId,
|
|
1044
|
-
status: v2Result.status
|
|
1045
|
-
});
|
|
1046
|
-
|
|
1047
|
-
// WAIT AND CHECK
|
|
1048
|
-
console.log('⏳ Waiting 5 seconds for V2 propagation...');
|
|
1049
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
1050
|
-
|
|
1051
|
-
console.log('🔍 Checking V2 Status...');
|
|
1052
|
-
const statusCheck = await service.checkTransactionStatus(
|
|
1053
|
-
v2Result.depositId,
|
|
1054
|
-
'v2',
|
|
1055
|
-
'deposit'
|
|
1056
|
-
);
|
|
1057
|
-
|
|
1058
|
-
console.log(`📊 Final V2 Status: [ ${statusCheck.status} ]`);
|
|
1059
|
-
if (statusCheck.status === 'FAILED') {
|
|
1060
|
-
// V2 failure messages are nested in 'data' usually
|
|
1061
|
-
const msg = statusCheck.data?.failureReason?.failureMessage || 'Unknown';
|
|
1062
|
-
console.warn(` Reason: ${msg}`);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
} else {
|
|
1066
|
-
console.error('❌ V2 Failed:', v2Result.error);
|
|
1067
|
-
}
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
console.error('❌ V2 Exception:', error.message);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
console.log('\n🏁 SDK Testing Complete');
|
|
1073
|
-
process.exit(0);
|
|
1074
|
-
}
|
|
113
|
+
## 🌍 Platform Support
|
|
1075
114
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
115
|
+
The SDK includes high-performance native core support for:
|
|
116
|
+
* **Windows:** x64 (msvc)
|
|
117
|
+
* **Linux:** x64, arm64 (including `musl` for Docker/Alpine)
|
|
118
|
+
* **macOS:** x64 (Intel), arm64 (Apple Silicon)
|
|
1080
119
|
|
|
1081
|
-
module.exports = { testSDKConnection };
|
|
1082
|
-
```
|
|
1083
120
|
---
|
|
1084
121
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
```typescript
|
|
1088
|
-
|
|
1089
|
-
// test-payment-page.js
|
|
1090
|
-
require('dotenv').config();
|
|
1091
|
-
const PawaPayService = require('./pawapayService');
|
|
1092
|
-
|
|
1093
|
-
/**
|
|
1094
|
-
* Run a full test of the PawaPay Payment Page Integration
|
|
1095
|
-
*/
|
|
1096
|
-
async function testPaymentPage() {
|
|
1097
|
-
console.log('🔗 Starting PawaPay Payment Page Test...\n');
|
|
1098
|
-
|
|
1099
|
-
const service = new PawaPayService();
|
|
1100
|
-
|
|
1101
|
-
// 1. Validate Token Format
|
|
1102
|
-
console.log('🔹 Step 1: Validating API Token...');
|
|
1103
|
-
const testToken = process.env.PAWAPAY_SANDBOX_API_TOKEN;
|
|
1104
|
-
const validation = service.validateToken(testToken);
|
|
1105
|
-
|
|
1106
|
-
if (!validation.isValid) {
|
|
1107
|
-
console.error('❌ Token Validation Failed:', validation);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
console.log('✅ Token looks valid:', validation.type);
|
|
1111
|
-
|
|
1112
|
-
// Common Test Data
|
|
1113
|
-
const commonData = {
|
|
1114
|
-
amount: '500',
|
|
1115
|
-
currency: '51345789',
|
|
1116
|
-
payerMsisdn: '22951345789', // Optional for V2, but good for V1
|
|
1117
|
-
description: 'Page Test',
|
|
1118
|
-
// IMPORTANT: You need a return URL
|
|
1119
|
-
returnUrl: 'https://example.com/payment-success',
|
|
1120
|
-
country: 'UGA'
|
|
1121
|
-
};
|
|
1122
|
-
|
|
1123
|
-
// 2. Test V1 Payment Page
|
|
1124
|
-
console.log('\n🔹 Step 2: Testing V1 Payment Page Generation...');
|
|
1125
|
-
try {
|
|
1126
|
-
const v1Result = await service.initiatePaymentPage({
|
|
1127
|
-
...commonData,
|
|
1128
|
-
description: 'V1 Page Test'
|
|
1129
|
-
}, 'v1');
|
|
1130
|
-
|
|
1131
|
-
if (v1Result.success) {
|
|
1132
|
-
console.log('✅ V1 Success!');
|
|
1133
|
-
console.log(' Deposit ID:', v1Result.depositId);
|
|
1134
|
-
console.log(' 👉 CLICK TO PAY:', v1Result.redirectUrl);
|
|
1135
|
-
} else {
|
|
1136
|
-
console.error('❌ V1 Failed:', v1Result.error);
|
|
1137
|
-
}
|
|
1138
|
-
} catch (error) {
|
|
1139
|
-
console.error('❌ V1 Exception:', error.message);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// 3. Test V2 Payment Page
|
|
1143
|
-
console.log('\n🔹 Step 3: Testing V2 Payment Page Generation...');
|
|
1144
|
-
try {
|
|
1145
|
-
const v2Result = await service.initiatePaymentPage({
|
|
1146
|
-
...commonData,
|
|
1147
|
-
description: 'V2 Page Test',
|
|
1148
|
-
// V2 specific metadata
|
|
1149
|
-
metadata: [
|
|
1150
|
-
{ fieldName: "product_id", fieldValue: "PROD-999" },
|
|
1151
|
-
{ fieldName: "email", fieldValue: "user@test.com", isPII: true }
|
|
1152
|
-
]
|
|
1153
|
-
}, 'v2');
|
|
1154
|
-
|
|
1155
|
-
if (v2Result.success) {
|
|
1156
|
-
console.log('✅ V2 Success!');
|
|
1157
|
-
console.log(' Deposit ID:', v2Result.depositId);
|
|
1158
|
-
console.log(' 👉 CLICK TO PAY:', v2Result.redirectUrl);
|
|
1159
|
-
} else {
|
|
1160
|
-
console.error('❌ V2 Failed:', v2Result.error);
|
|
1161
|
-
}
|
|
1162
|
-
} catch (error) {
|
|
1163
|
-
console.error('❌ V2 Exception:', error.message);
|
|
1164
|
-
}
|
|
122
|
+
## 🔐 Licensing
|
|
1165
123
|
|
|
1166
|
-
|
|
1167
|
-
}
|
|
124
|
+
This is a commercial product. The SDK is licensed per domain and requires a valid key for operation.
|
|
1168
125
|
|
|
1169
|
-
|
|
1170
|
-
if (require.main === module) {
|
|
1171
|
-
testPaymentPage().catch(console.error);
|
|
1172
|
-
}
|
|
126
|
+
👉 **Get a License:** [katorymnd.com/pawapay-payment-sdk](https://katorymnd.com/pawapay-payment-sdk/nodejs)
|
|
1173
127
|
|
|
1174
|
-
module.exports = { testPaymentPage };
|
|
1175
|
-
```
|
|
1176
128
|
---
|
|
1177
129
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
```typescript
|
|
1181
|
-
|
|
1182
|
-
// test-payout.js
|
|
1183
|
-
const path = require('path');
|
|
1184
|
-
require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
|
|
1185
|
-
|
|
1186
|
-
const PawaPayService = require('./pawapayService');
|
|
1187
|
-
|
|
1188
|
-
async function runPayoutTest() {
|
|
1189
|
-
console.log('\n========================================');
|
|
1190
|
-
console.log('🚀 PAWAPAY SDK PAYOUT TEST (With Status Check)');
|
|
1191
|
-
console.log('========================================\n');
|
|
1192
|
-
|
|
1193
|
-
const service = new PawaPayService({});
|
|
1194
|
-
|
|
1195
|
-
const testData = {
|
|
1196
|
-
amount: "1000",
|
|
1197
|
-
currency: "UGX",
|
|
1198
|
-
recipientMsisdn: "256783456789",
|
|
1199
|
-
mno: "MTN_MOMO_UGA",
|
|
1200
|
-
description: "SDK Payout Test",
|
|
1201
|
-
metadata: [
|
|
1202
|
-
{ fieldName: "test_run", fieldValue: "true" }
|
|
1203
|
-
]
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
const API_VERSION = 'v1';
|
|
1207
|
-
|
|
1208
|
-
console.log(`Initiating Payout [${API_VERSION}]...`);
|
|
1209
|
-
|
|
1210
|
-
try {
|
|
1211
|
-
// --- STEP 1: INITIATE ---
|
|
1212
|
-
const result = await service.payout(testData, API_VERSION);
|
|
1213
|
-
|
|
1214
|
-
if (result.success) {
|
|
1215
|
-
console.log('\n Payout Submitted Successfully!');
|
|
1216
|
-
console.log(` ID: ${result.payoutId}`);
|
|
1217
|
-
|
|
1218
|
-
// --- STEP 2: THE WAIT (Crucial for logical testing) ---
|
|
1219
|
-
console.log('\n⏳ Waiting 5 seconds for Sandbox propagation...');
|
|
1220
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1221
|
-
|
|
1222
|
-
// --- STEP 3: CHECK STATUS ---
|
|
1223
|
-
console.log('🔍 Checking Payout Status...');
|
|
1224
|
-
|
|
1225
|
-
// We specifically pass 'payout' as the 3rd argument here
|
|
1226
|
-
const statusCheck = await service.checkTransactionStatus(
|
|
1227
|
-
result.payoutId,
|
|
1228
|
-
API_VERSION,
|
|
1229
|
-
'payout'
|
|
1230
|
-
);
|
|
1231
|
-
|
|
1232
|
-
if (statusCheck.success) {
|
|
1233
|
-
const status = statusCheck.status;
|
|
1234
|
-
const failureMsg = statusCheck.data?.failureReason?.failureMessage;
|
|
1235
|
-
|
|
1236
|
-
console.log(`\n FINAL STATUS: [ ${status} ]`);
|
|
1237
|
-
|
|
1238
|
-
if (status === 'COMPLETED') {
|
|
1239
|
-
console.log(' SUCCESS: Money sent.');
|
|
1240
|
-
} else if (status === 'FAILED') {
|
|
1241
|
-
console.log(` FAILED: ${failureMsg || 'Unknown reason'}`);
|
|
1242
|
-
} else {
|
|
1243
|
-
console.log(' PENDING: Still processing.');
|
|
1244
|
-
}
|
|
130
|
+
## 🆘 Support
|
|
1245
131
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
} else {
|
|
1252
|
-
console.error('\n Payout Initiation Failed');
|
|
1253
|
-
console.error(`Error: ${result.error}`);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
} catch (e) {
|
|
1257
|
-
console.error(' Script Error:', e);
|
|
1258
|
-
} finally {
|
|
1259
|
-
process.exit(0);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
runPayoutTest();
|
|
1264
|
-
|
|
1265
|
-
```
|
|
1266
|
-
---
|
|
1267
|
-
### Refund Senario
|
|
1268
|
-
|
|
1269
|
-
```typescript
|
|
1270
|
-
|
|
1271
|
-
// test-refund.js
|
|
1272
|
-
const path = require('path');
|
|
1273
|
-
require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
|
|
1274
|
-
|
|
1275
|
-
const PawaPayService = require('./pawapayService');
|
|
1276
|
-
|
|
1277
|
-
async function runRefundTest() {
|
|
1278
|
-
console.log('\n========================================');
|
|
1279
|
-
console.log('💸 PAWAPAY SDK REFUND TEST');
|
|
1280
|
-
console.log('========================================\n');
|
|
1281
|
-
|
|
1282
|
-
const service = new PawaPayService({});
|
|
1283
|
-
|
|
1284
|
-
// DATA FROM YOUR LOGS
|
|
1285
|
-
const EXISTING_DEPOSIT_ID = "e6abef2a-7b54-4d5b-9afb-8ccc831a228b";//test deposit id - example
|
|
1286
|
-
|
|
1287
|
-
// We refund a partial amount to be safe/realistic
|
|
1288
|
-
const testData = {
|
|
1289
|
-
depositId: EXISTING_DEPOSIT_ID,
|
|
1290
|
-
amount: "500", // Refund half of the original 1000
|
|
1291
|
-
currency: "UGX",
|
|
1292
|
-
reason: "Customer requested partial refund",
|
|
1293
|
-
metadata: [
|
|
1294
|
-
{ fieldName: "reason", fieldValue: "sdk_test_script" },
|
|
1295
|
-
{ fieldName: "original_order", fieldValue: "ORD-SDK-TEST" }
|
|
1296
|
-
]
|
|
1297
|
-
};
|
|
1298
|
-
|
|
1299
|
-
// Toggle this to test V1 vs V2
|
|
1300
|
-
const API_VERSION = 'v1';
|
|
1301
|
-
|
|
1302
|
-
console.log(` Initiating Refund [${API_VERSION}] for Deposit: ${EXISTING_DEPOSIT_ID}...`);
|
|
1303
|
-
|
|
1304
|
-
try {
|
|
1305
|
-
// --- STEP 1: INITIATE REFUND ---
|
|
1306
|
-
const result = await service.refund(testData, API_VERSION);
|
|
1307
|
-
|
|
1308
|
-
if (result.success) {
|
|
1309
|
-
console.log('\n Refund Submitted Successfully!');
|
|
1310
|
-
console.log(` Refund ID: ${result.refundId}`);
|
|
1311
|
-
console.log(` Status: ${result.status}`);
|
|
1312
|
-
|
|
1313
|
-
// --- STEP 2: WAIT ---
|
|
1314
|
-
console.log('\n Waiting 5 seconds for processing...');
|
|
1315
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1316
|
-
|
|
1317
|
-
// --- STEP 3: CHECK STATUS ---
|
|
1318
|
-
console.log(' Checking Final Refund Status...');
|
|
1319
|
-
|
|
1320
|
-
// Critical: Pass 'refund' as the 3rd argument
|
|
1321
|
-
const statusCheck = await service.checkTransactionStatus(
|
|
1322
|
-
result.refundId,
|
|
1323
|
-
API_VERSION,
|
|
1324
|
-
'refund'
|
|
1325
|
-
);
|
|
1326
|
-
|
|
1327
|
-
if (statusCheck.success) {
|
|
1328
|
-
console.log(`\n FINAL STATUS: [ ${statusCheck.status} ]`);
|
|
1329
|
-
console.log('Data:', JSON.stringify(statusCheck.data, null, 2));
|
|
1330
|
-
} else {
|
|
1331
|
-
console.error(' Could not fetch status:', statusCheck.error);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
} else {
|
|
1335
|
-
console.error('\n Refund Initiation Failed');
|
|
1336
|
-
console.error(`Error: ${result.error}`);
|
|
1337
|
-
if (result.rawResponse) {
|
|
1338
|
-
console.error('Raw:', JSON.stringify(result.rawResponse.response || result.rawResponse, null, 2));
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
} catch (e) {
|
|
1343
|
-
console.error(' Script Error:', e);
|
|
1344
|
-
} finally {
|
|
1345
|
-
process.exit(0);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
runRefundTest();
|
|
1350
|
-
|
|
1351
|
-
```
|
|
132
|
+
* **Documentation:** [Official Guides](https://katorymnd.com/pawapay-payment-sdk/nodejs)
|
|
133
|
+
* **Email Support:** [support@katorymnd.com](mailto:support@katorymnd.com)
|
|
134
|
+
* **Gmail Support:** [katorymnd@gmail.com](mailto:katorymnd@gmail.com)
|
|
1352
135
|
|
|
1353
136
|
---
|
|
1354
|
-
|
|
1355
|
-
### MNO Configuration & Version Switching
|
|
1356
|
-
|
|
1357
|
-
The SDK ships with default configuration templates. However, you are expected to maintain **up-to-date MNO and active configuration files**.
|
|
1358
|
-
|
|
1359
|
-
* Use the example script:
|
|
1360
|
-
|
|
1361
|
-
```
|
|
1362
|
-
example/fetchMnoConf.js
|
|
1363
|
-
```
|
|
1364
|
-
* This script fetches and generates:
|
|
1365
|
-
|
|
1366
|
-
* `active_conf_v1.json`
|
|
1367
|
-
* `mno_availability_v1.json`
|
|
1368
|
-
* `active_conf_v2.json`
|
|
1369
|
-
* `mno_availability_v2.json`
|
|
1370
|
-
|
|
1371
|
-
You may toggle **V1 or V2** behavior through these configuration files.
|
|
1372
|
-
|
|
1373
|
-
> **Important**
|
|
1374
|
-
> You should configure a cron job to periodically refresh these files to ensure MNO availability and configuration data remain current.
|
|
1375
|
-
|
|
1376
|
-
Here is a sample code too using the installed SDK
|
|
1377
|
-
|
|
1378
|
-
```typescript
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
* pawapayFetchConfig.js
|
|
1382
|
-
* * AUTOMATED CONFIGURATION UPDATER
|
|
1383
|
-
* Uses the PawaPayService to fetch the latest MNO definitions and Active Configurations
|
|
1384
|
-
* from the Sandbox environment and updates the static JSON files.
|
|
1385
|
-
*
|
|
1386
|
-
*/
|
|
1387
|
-
|
|
1388
|
-
const fs = require('fs');
|
|
1389
|
-
const path = require('path');
|
|
1390
|
-
|
|
1391
|
-
// 🔒 HARD LOCK THE WORKING DIRECTORY (CRON-PROOF)
|
|
1392
|
-
process.chdir('path/to/nodejs/dir');
|
|
1393
|
-
|
|
1394
|
-
// Load environment variables explicitly
|
|
1395
|
-
require('dotenv').config({
|
|
1396
|
-
path: 'path/to/nodejs/dir/.env'
|
|
1397
|
-
});
|
|
1398
|
-
|
|
1399
|
-
// (Optional debug – safe to remove once satisfied)
|
|
1400
|
-
// console.log({
|
|
1401
|
-
// cwd: process.cwd(),
|
|
1402
|
-
// home: process.env.HOME
|
|
1403
|
-
// });
|
|
1404
|
-
|
|
1405
|
-
// Load PawaPay service AFTER cwd + env are stable
|
|
1406
|
-
const PawaPayService = require('../pawapayService');
|
|
1407
|
-
|
|
1408
|
-
// CONFIGURATION
|
|
1409
|
-
// Use __dirname to ensure data directory is script-relative
|
|
1410
|
-
const OUTPUT_DIR = path.join(__dirname, '../data');
|
|
1411
|
-
|
|
1412
|
-
// Ensure directory exists
|
|
1413
|
-
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
1414
|
-
console.log(`[Config] Directory not found, creating: ${OUTPUT_DIR}`);
|
|
1415
|
-
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// Initialize Service (Automatically loads ENV tokens and Sandbox mode)
|
|
1419
|
-
const service = new PawaPayService();
|
|
1420
|
-
|
|
1421
|
-
/**
|
|
1422
|
-
* Helper to write JSON files surgically
|
|
1423
|
-
*/
|
|
1424
|
-
const saveJson = (filename, data) => {
|
|
1425
|
-
const filePath = path.join(OUTPUT_DIR, filename);
|
|
1426
|
-
try {
|
|
1427
|
-
const content = JSON.stringify(data, null, 2);
|
|
1428
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
1429
|
-
console.log(` [SUCCESS] Updated: ${filename} (${content.length} bytes)`);
|
|
1430
|
-
} catch (err) {
|
|
1431
|
-
console.error(` [ERROR] Failed to write ${filename}:`, err.message);
|
|
1432
|
-
}
|
|
1433
|
-
};
|
|
1434
|
-
|
|
1435
|
-
/**
|
|
1436
|
-
* Main Execution Function
|
|
1437
|
-
*/
|
|
1438
|
-
const updateConfigurations = async () => {
|
|
1439
|
-
console.log(' [Config] Starting PawaPay Configuration Update (Sandbox)...');
|
|
1440
|
-
console.log(`TB: ${OUTPUT_DIR}`);
|
|
1441
|
-
|
|
1442
|
-
try {
|
|
1443
|
-
// ==========================================
|
|
1444
|
-
// 1. FETCH V1 CONFIGURATIONS
|
|
1445
|
-
// ==========================================
|
|
1446
|
-
|
|
1447
|
-
console.log(' [V1] Fetching Active Conf...');
|
|
1448
|
-
const activeConfV1 = await service.pawapay.checkActiveConf();
|
|
1449
|
-
if (activeConfV1.status === 200) {
|
|
1450
|
-
saveJson('active_conf_v1.json', activeConfV1.response);
|
|
1451
|
-
} else {
|
|
1452
|
-
console.error(` [V1] Active Conf Failed: ${activeConfV1.status}`);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
console.log(' [V1] Fetching MNO Availability...');
|
|
1456
|
-
const availabilityV1 = await service.pawapay.checkMNOAvailability();
|
|
1457
|
-
if (availabilityV1.status === 200) {
|
|
1458
|
-
saveJson('mno_availability_v1.json', availabilityV1.response);
|
|
1459
|
-
} else {
|
|
1460
|
-
console.error(` [V1] Availability Failed: ${availabilityV1.status}`);
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
// ==========================================
|
|
1464
|
-
// 2. FETCH V2 CONFIGURATIONS
|
|
1465
|
-
// ==========================================
|
|
1466
|
-
|
|
1467
|
-
console.log(' [V2] Fetching Active Conf...');
|
|
1468
|
-
const activeConfV2 = await service.pawapay.checkActiveConfV2();
|
|
1469
|
-
if (activeConfV2.status === 200) {
|
|
1470
|
-
saveJson('active_conf_v2.json', activeConfV2.response);
|
|
1471
|
-
} else {
|
|
1472
|
-
console.error(` [V2] Active Conf Failed: ${activeConfV2.status}`);
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
console.log(' [V2] Fetching MNO Availability...');
|
|
1476
|
-
const availabilityV2 = await service.pawapay.checkMNOAvailabilityV2();
|
|
1477
|
-
if (availabilityV2.status === 200) {
|
|
1478
|
-
saveJson('mno_availability_v2.json', availabilityV2.response);
|
|
1479
|
-
} else {
|
|
1480
|
-
console.error(` [V2] Availability Failed: ${availabilityV2.status}`);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
console.log(' [DONE] All configurations updated successfully.');
|
|
1484
|
-
process.exit(0);
|
|
1485
|
-
|
|
1486
|
-
} catch (error) {
|
|
1487
|
-
console.error(' [CRITICAL] Script failed:', error.message);
|
|
1488
|
-
process.exit(1);
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
|
|
1492
|
-
// Run
|
|
1493
|
-
updateConfigurations();
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
```
|
|
1497
|
-
|
|
1498
|
-
This approach keeps runtime fast, predictable, and independent of unnecessary API calls.
|
|
1499
|
-
|
|
1500
|
-
## Support
|
|
1501
|
-
|
|
1502
|
-
For any issues, questions, or guidance:
|
|
1503
|
-
|
|
1504
|
-
* **Documentation:** Visit the [official documentation](https://katorymnd.com/pawapay-payment-sdk/nodejs)
|
|
1505
|
-
|
|
1506
|
-
* **Community Support:** Available with Starter and higher licenses
|
|
1507
|
-
* **Priority Support:** Included with Professional (6 months) and Agency (1 year) licenses
|
|
1508
|
-
* **Urgent Assistance:** Available as an add-on for production outages
|
|
1509
|
-
|
|
1510
|
-
**Commercial Support:**
|
|
1511
|
-
For licensed users experiencing production issues or needing implementation guidance.
|
|
1512
|
-
|
|
1513
|
-
**Contact:** [support@katorymnd.com](mailto:support@katorymnd.com)
|
|
1514
|
-
_Backup:_ [katorymnd@gmail.com](mailto:katorymnd@gmail.com)
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
----
|