@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/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
- ![configuration](https://katorymnd.com/tqc_images/pawapay-node-config.png)
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
- ![Mobile Money Deposit](https://katorymnd.com/tqc_images/pawapay-node-deposit.png)
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
- ![Mobile Money Refund](https://katorymnd.com/tqc_images/pawapay-node-refund.png)
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
- ![Mobile Money Payout](https://katorymnd.com/tqc_images/pawapay-node-payout.png)
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
- ### Owner Name Notification
88
-
89
- ![Owner Name Notification](https://katorymnd.com/tqc_images/pay-request-from.png)
2
+ # pawaPay Node.js SDK
90
3
 
91
- Supports displaying the organization or owner name in payment notifications, helping end users easily identify payment requests.
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
- ### Deposit via Hosted Payment Page
96
- ![Deposit via Hosted Payment Page](https://katorymnd.com/tqc_images/pawapay-node-payPage.png)
97
- Use the hosted payment widget to collect payments through a secure redirect flow.
9
+ ## 🏗️ Architecture
98
10
 
99
- 1. Your application creates a payment session.
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
- ## Overview
27
+ ## 🚀 Key Features
139
28
 
140
- The pawaPay Node.js SDK integrates seamlessly with Node.js frameworks such as **Express**, **NestJS**, and **Fastify**.
141
- It abstracts pawaPay’s payment APIs into a clean, predictable interface focused on correctness, security, and operational clarity.
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
- ## Licensing & Pricing
146
-
147
- This is a **paid commercial SDK**.
39
+ ## 📦 Installation
148
40
 
149
- * **License Model:** One-time payment
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
- yarn add @katorymnd/pawapay-node-sdk
46
+ pnpm add @katorymnd/pawapay-node-sdk
170
47
  ```
171
48
 
172
49
  ---
173
50
 
174
- ## Configuration (.env)
51
+ ## 🛠️ Configuration (.env)
175
52
 
176
- Create a `.env` file in your project root and configure both pawaPay API credentials and SDK licensing keys.
53
+ Set up your project environment with your pawaPay credentials and SDK access keys.
177
54
 
178
55
  ```bash
179
- # ===============================
180
- # pawaPay API Tokens
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
- KATORYMND_PAWAPAY_SDK_LICENSE_KEY=your_sdk_license_key_here
191
- PAWAPAY_SDK_LICENSE_DOMAIN=your-licensed-domain.com
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
- ### Initializing the SDK
63
+ # License key used by the Katorymnd PawaPay SDK
64
+ KATORYMND_PAWAPAY_SDK_LICENSE_KEY=your_license_key_here
200
65
 
201
- Ensure the licensed domain is present in your `.env` file:
66
+ # License domain (used for license validation)
67
+ PAWAPAY_SDK_LICENSE_DOMAIN=your_license_domain_here
202
68
 
203
- ```bash
204
- PAWAPAY_SDK_LICENSE_DOMAIN=your-licensed-domain.com
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
- You need to call the sdk (`@katorymnd/pawapay-node-sdk`) to your project so that you can use it for `deposit`,`hosted deposit`,`refund`,`payout`,`MNO Config` and also to confirm transactions.
75
+ ## 💻 Usage
221
76
 
222
- Create a page `pawapayService.js` for example and add this code
77
+ To begin, initialize the `ApiClient`. The SDK will perform a quick environmental check and prepare the native core for processing.
223
78
 
224
- ```typescript
225
- //pawapayService.js
79
+ ```javascript
80
+ const { ApiClient } = require('@katorymnd/pawapay-node-sdk');
226
81
 
227
- const path = require('path');
228
- const winston = require('winston');
229
- require('dotenv').config();
230
-
231
- // Load the SDK and destructure the public exports directly
232
- // We use 'ApiClient' because that's the class name export
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
- if (process.env.NODE_ENV !== 'production') {
265
- logger.add(new winston.transports.Console({
266
- format: winston.format.combine(
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
- const {
819
- depositId,
820
- amount,
821
- currency, // Required for V2
822
- reason,
823
- metadata = []
824
- } = refundData;
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
- logger.error('System Error during refund', {
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
- // Common Test Data (Uganda MTN Sandbox)
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
- // Run test if called directly
1077
- if (require.main === module) {
1078
- testSDKConnection().catch(console.error);
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
- ### Deposit Senario (Hosted Page)
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
- console.log('\n🏁 Payment Page Testing Complete');
1167
- }
124
+ This is a commercial product. The SDK is licensed per domain and requires a valid key for operation.
1168
125
 
1169
- // Run test if called directly
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
- ### Payout Senario
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
- // console.log('Debug Data:', JSON.stringify(statusCheck.data, null, 2));
1247
- } else {
1248
- console.error(' Could not fetch status:', statusCheck.error);
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
- ----