softycomp-node 1.0.5 → 1.1.1
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 +172 -3
- package/dist/index.d.ts +256 -0
- package/dist/index.js +294 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,14 +10,36 @@ Accept once-off and recurring payments via card, EFT, and debit order with a sim
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **Bill Presentment** — Create payment links for once-off or recurring bills
|
|
14
|
-
- **Debit Orders** — Monthly and yearly recurring collections
|
|
13
|
+
- **Bill Presentment** — Create, update, expire payment links for once-off or recurring bills
|
|
14
|
+
- **Debit Orders** — Monthly and yearly recurring collections via Mobi-Mandate
|
|
15
|
+
- **Client Management** — Create and manage customer profiles
|
|
15
16
|
- **Refunds** — Process full or partial refunds via credit transactions
|
|
17
|
+
- **Payouts** — Credit distribution to bank accounts
|
|
18
|
+
- **Re-authentication** — Handle card expiry with automated bill re-creation
|
|
19
|
+
- **Audit Trail** — List bill presentment audit logs
|
|
16
20
|
- **Webhooks** — Real-time payment notifications with signature validation
|
|
17
21
|
- **TypeScript** — Fully typed for autocomplete and type safety
|
|
18
22
|
- **Zero Dependencies** — Uses native `fetch()` (Node.js 18+)
|
|
19
23
|
- **Sandbox Support** — Test with sandbox environment before going live
|
|
20
24
|
|
|
25
|
+
## Developer Playground
|
|
26
|
+
|
|
27
|
+
Try the interactive API explorer at `playground/` — a beautiful web interface to test all SoftyComp features:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd playground
|
|
31
|
+
npm install
|
|
32
|
+
npm start
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then visit http://localhost:4021 to explore:
|
|
36
|
+
- Create bills (once-off, monthly, weekly, yearly, subscription)
|
|
37
|
+
- Manage bills (status, update, expire, audit trail, re-auth)
|
|
38
|
+
- Debit orders (Mobi-Mandate)
|
|
39
|
+
- Client management & payouts
|
|
40
|
+
- Live webhook feed
|
|
41
|
+
- Code examples & API reference
|
|
42
|
+
|
|
21
43
|
## Installation
|
|
22
44
|
|
|
23
45
|
```bash
|
|
@@ -185,6 +207,100 @@ const status = await client.getBillStatus('BILL-REF-123');
|
|
|
185
207
|
}
|
|
186
208
|
```
|
|
187
209
|
|
|
210
|
+
### Bill Management
|
|
211
|
+
|
|
212
|
+
#### Expire a Bill
|
|
213
|
+
|
|
214
|
+
Set a bill to expired status (e.g., for cancelling a recurring bill):
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
await client.setBillToExpiredStatus('BILL-REF-123', 'USER-REF-123');
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### Update Bill Presentment
|
|
221
|
+
|
|
222
|
+
Update details of an existing bill:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
await client.updateBillPresentment({
|
|
226
|
+
reference: 'BILL-REF-123',
|
|
227
|
+
amount: 399.00,
|
|
228
|
+
description: 'Updated description',
|
|
229
|
+
customerEmail: 'newemail@example.com'
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### List Bill Audit Trail
|
|
234
|
+
|
|
235
|
+
Get the audit trail for a bill:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const audits = await client.listBillPresentmentAudits('BILL-REF-123', 'USER-REF-123');
|
|
239
|
+
|
|
240
|
+
// Returns array of:
|
|
241
|
+
{
|
|
242
|
+
auditId: number;
|
|
243
|
+
timestamp: string;
|
|
244
|
+
description: string;
|
|
245
|
+
user: string;
|
|
246
|
+
raw: any;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Client Management
|
|
251
|
+
|
|
252
|
+
Create a new client profile:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const clientId = await client.createClient({
|
|
256
|
+
name: 'John',
|
|
257
|
+
surname: 'Doe',
|
|
258
|
+
email: 'john@example.com',
|
|
259
|
+
phone: '0825551234',
|
|
260
|
+
idNumber: '8001015009087' // Optional SA ID number
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
console.log(`Created client ID: ${clientId}`);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Mobi-Mandate (Debit Orders)
|
|
267
|
+
|
|
268
|
+
Create a Mobi-Mandate request for debit order sign-up. This generates a URL where customers can enter their bank details and sign a debit order mandate.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
const mandate = await client.createMobiMandate({
|
|
272
|
+
customerEmail: 'john@example.com',
|
|
273
|
+
customerPhone: '0825551234',
|
|
274
|
+
surname: 'Doe',
|
|
275
|
+
initials: 'J',
|
|
276
|
+
amount: 99.00, // Monthly/yearly charge amount
|
|
277
|
+
frequency: 'monthly', // or 'yearly'
|
|
278
|
+
debitDay: 1, // Day of month to charge (1-28)
|
|
279
|
+
description: 'Monthly subscription',
|
|
280
|
+
successUrl: 'https://myapp.com/success',
|
|
281
|
+
callbackUrl: 'https://myapp.com/webhook'
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Redirect customer to sign the mandate
|
|
285
|
+
console.log(mandate.url); // e.g., https://popay.co.za/xxx
|
|
286
|
+
|
|
287
|
+
// Returns:
|
|
288
|
+
{
|
|
289
|
+
url: string; // Mandate sign-up URL
|
|
290
|
+
success: boolean;
|
|
291
|
+
message: string;
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Cancel a Debit Order Collection
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
await client.updateCollectionStatus({
|
|
299
|
+
collectionId: 12345,
|
|
300
|
+
statusTypeId: 6 // 6 = Cancelled
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
188
304
|
### Refund Payment
|
|
189
305
|
|
|
190
306
|
Process a full or partial refund (credit transaction).
|
|
@@ -209,6 +325,50 @@ const refund = await client.refund({
|
|
|
209
325
|
}
|
|
210
326
|
```
|
|
211
327
|
|
|
328
|
+
### Credit Distribution (Payouts)
|
|
329
|
+
|
|
330
|
+
Send money to a bank account:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const result = await client.createCreditDistribution({
|
|
334
|
+
amount: 500.00,
|
|
335
|
+
accountNumber: '1234567890',
|
|
336
|
+
branchCode: '123456',
|
|
337
|
+
accountName: 'John Doe',
|
|
338
|
+
reference: 'PAYOUT-001'
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Returns:
|
|
342
|
+
{
|
|
343
|
+
distributionId: string;
|
|
344
|
+
success: boolean;
|
|
345
|
+
messages: string[];
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Re-authentication (Card Expiry)
|
|
350
|
+
|
|
351
|
+
Handle card expiry by expiring the old bill and creating a new one with a different reference:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
const newBill = await client.createReauthBill({
|
|
355
|
+
oldReference: 'OLD-BILL-123',
|
|
356
|
+
newReference: 'NEW-BILL-456', // MUST be different
|
|
357
|
+
amount: 99.00,
|
|
358
|
+
customerName: 'John Doe',
|
|
359
|
+
customerEmail: 'john@example.com',
|
|
360
|
+
customerPhone: '0825551234',
|
|
361
|
+
description: 'Monthly subscription',
|
|
362
|
+
billingCycle: 'MONTHLY', // or 'YEARLY'
|
|
363
|
+
successUrl: 'https://myapp.com/success',
|
|
364
|
+
cancelUrl: 'https://myapp.com/cancel',
|
|
365
|
+
notifyUrl: 'https://myapp.com/webhook'
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Customer re-enters card details at newBill.paymentUrl
|
|
369
|
+
console.log(newBill.paymentUrl);
|
|
370
|
+
```
|
|
371
|
+
|
|
212
372
|
### Webhook Handling
|
|
213
373
|
|
|
214
374
|
SoftyComp sends real-time payment notifications to your `notifyUrl`.
|
|
@@ -366,7 +526,16 @@ import {
|
|
|
366
526
|
PaymentStatus,
|
|
367
527
|
WebhookEvent,
|
|
368
528
|
CreateBillParams,
|
|
369
|
-
RefundParams
|
|
529
|
+
RefundParams,
|
|
530
|
+
CreateClientParams,
|
|
531
|
+
CreateMobiMandateParams,
|
|
532
|
+
MobiMandateResult,
|
|
533
|
+
UpdateBillParams,
|
|
534
|
+
BillAudit,
|
|
535
|
+
CreditDistributionParams,
|
|
536
|
+
CreditDistributionResult,
|
|
537
|
+
CreateReauthBillParams,
|
|
538
|
+
UpdateCollectionStatusParams
|
|
370
539
|
} from 'softycomp-node';
|
|
371
540
|
```
|
|
372
541
|
|
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,146 @@ export interface BillStatusResult {
|
|
|
109
109
|
/** Raw response data */
|
|
110
110
|
data: any;
|
|
111
111
|
}
|
|
112
|
+
export interface UpdateBillParams {
|
|
113
|
+
/** Bill reference to update */
|
|
114
|
+
reference: string;
|
|
115
|
+
/** New amount in Rands */
|
|
116
|
+
amount?: number;
|
|
117
|
+
/** New description */
|
|
118
|
+
description?: string;
|
|
119
|
+
/** Update customer details */
|
|
120
|
+
customerName?: string;
|
|
121
|
+
customerEmail?: string;
|
|
122
|
+
customerPhone?: string;
|
|
123
|
+
}
|
|
124
|
+
export interface BillAudit {
|
|
125
|
+
/** Audit entry ID */
|
|
126
|
+
auditId: number;
|
|
127
|
+
/** Timestamp of change */
|
|
128
|
+
timestamp: string;
|
|
129
|
+
/** Description of change */
|
|
130
|
+
description: string;
|
|
131
|
+
/** User who made the change */
|
|
132
|
+
user: string;
|
|
133
|
+
/** Raw audit data */
|
|
134
|
+
raw: any;
|
|
135
|
+
}
|
|
136
|
+
export interface CreateClientParams {
|
|
137
|
+
/** Client full name */
|
|
138
|
+
name: string;
|
|
139
|
+
/** Client surname */
|
|
140
|
+
surname: string;
|
|
141
|
+
/** Client email address */
|
|
142
|
+
email: string;
|
|
143
|
+
/** Client phone number */
|
|
144
|
+
phone: string;
|
|
145
|
+
/** Client ID number (optional) */
|
|
146
|
+
idNumber?: string;
|
|
147
|
+
}
|
|
148
|
+
export interface CreateMobiMandateParams {
|
|
149
|
+
/** Customer email */
|
|
150
|
+
customerEmail: string;
|
|
151
|
+
/** Customer phone number */
|
|
152
|
+
customerPhone: string;
|
|
153
|
+
/** Contract code / reference */
|
|
154
|
+
contractCode?: string;
|
|
155
|
+
/** Customer surname */
|
|
156
|
+
surname: string;
|
|
157
|
+
/** Customer initials */
|
|
158
|
+
initials?: string;
|
|
159
|
+
/** Customer ID number */
|
|
160
|
+
idNumber?: string;
|
|
161
|
+
/** Product ID */
|
|
162
|
+
productId?: string;
|
|
163
|
+
/** Recurring amount in Rands */
|
|
164
|
+
amount: number;
|
|
165
|
+
/** Initial amount (defaults to amount) */
|
|
166
|
+
initialAmount?: number;
|
|
167
|
+
/** Account name */
|
|
168
|
+
accountName?: string;
|
|
169
|
+
/** Account number */
|
|
170
|
+
accountNumber?: string;
|
|
171
|
+
/** Branch code */
|
|
172
|
+
branchCode?: string;
|
|
173
|
+
/** Account type (1=Savings, 2=Cheque, 3=Credit Card) */
|
|
174
|
+
accountType?: number;
|
|
175
|
+
/** Expiry date (YYYY-MM-DD) */
|
|
176
|
+
expiryDate?: string;
|
|
177
|
+
/** Commencement date (YYYY-MM-DD) */
|
|
178
|
+
commencementDate?: string;
|
|
179
|
+
/** Collection frequency: 'monthly' | 'yearly' */
|
|
180
|
+
frequency: 'monthly' | 'yearly';
|
|
181
|
+
/** Collection method type ID (4=NAEDO) */
|
|
182
|
+
collectionMethodTypeId?: number;
|
|
183
|
+
/** Debit day (1-28) */
|
|
184
|
+
debitDay?: number;
|
|
185
|
+
/** Description */
|
|
186
|
+
description?: string;
|
|
187
|
+
/** Maximum collection amount (defaults to amount * 1.5) */
|
|
188
|
+
maxCollectionAmount?: number;
|
|
189
|
+
/** Success redirect URL */
|
|
190
|
+
successUrl?: string;
|
|
191
|
+
/** Webhook callback URL */
|
|
192
|
+
callbackUrl?: string;
|
|
193
|
+
}
|
|
194
|
+
export interface MobiMandateResult {
|
|
195
|
+
/** Mandate URL for customer to sign */
|
|
196
|
+
url: string;
|
|
197
|
+
/** Success status */
|
|
198
|
+
success: boolean;
|
|
199
|
+
/** Message from SoftyComp */
|
|
200
|
+
message: string;
|
|
201
|
+
}
|
|
202
|
+
export interface UpdateCollectionStatusParams {
|
|
203
|
+
/** Collection ID to update */
|
|
204
|
+
collectionId: number;
|
|
205
|
+
/** New status type ID (6=Cancelled) */
|
|
206
|
+
statusTypeId: number;
|
|
207
|
+
}
|
|
208
|
+
export interface CreditDistributionParams {
|
|
209
|
+
/** Amount in Rands */
|
|
210
|
+
amount: number;
|
|
211
|
+
/** Recipient account number */
|
|
212
|
+
accountNumber: string;
|
|
213
|
+
/** Recipient branch code */
|
|
214
|
+
branchCode: string;
|
|
215
|
+
/** Recipient account name */
|
|
216
|
+
accountName: string;
|
|
217
|
+
/** Payment reference */
|
|
218
|
+
reference: string;
|
|
219
|
+
}
|
|
220
|
+
export interface CreditDistributionResult {
|
|
221
|
+
/** Distribution ID */
|
|
222
|
+
distributionId: string;
|
|
223
|
+
/** Success status */
|
|
224
|
+
success: boolean;
|
|
225
|
+
/** Messages from SoftyComp */
|
|
226
|
+
messages: string[];
|
|
227
|
+
}
|
|
228
|
+
export interface CreateReauthBillParams {
|
|
229
|
+
/** Old bill reference to expire */
|
|
230
|
+
oldReference: string;
|
|
231
|
+
/** New bill reference (must be different) */
|
|
232
|
+
newReference: string;
|
|
233
|
+
/** Amount in Rands */
|
|
234
|
+
amount: number;
|
|
235
|
+
/** Customer name */
|
|
236
|
+
customerName: string;
|
|
237
|
+
/** Customer email */
|
|
238
|
+
customerEmail: string;
|
|
239
|
+
/** Customer phone */
|
|
240
|
+
customerPhone: string;
|
|
241
|
+
/** Bill description */
|
|
242
|
+
description: string;
|
|
243
|
+
/** Billing cycle */
|
|
244
|
+
billingCycle: 'MONTHLY' | 'YEARLY';
|
|
245
|
+
/** Success URL */
|
|
246
|
+
successUrl: string;
|
|
247
|
+
/** Cancel URL */
|
|
248
|
+
cancelUrl: string;
|
|
249
|
+
/** Webhook URL */
|
|
250
|
+
notifyUrl: string;
|
|
251
|
+
}
|
|
112
252
|
export declare class SoftyComp {
|
|
113
253
|
private apiKey;
|
|
114
254
|
private secretKey;
|
|
@@ -165,6 +305,37 @@ export declare class SoftyComp {
|
|
|
165
305
|
* ```
|
|
166
306
|
*/
|
|
167
307
|
getBillStatus(reference: string): Promise<BillStatusResult>;
|
|
308
|
+
/**
|
|
309
|
+
* Set a bill to expired status
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* await client.setBillToExpiredStatus('BILL-REF-123', 'USER-REF-123');
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
setBillToExpiredStatus(reference: string, userReference: string): Promise<void>;
|
|
317
|
+
/**
|
|
318
|
+
* Update bill presentment details
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* ```typescript
|
|
322
|
+
* await client.updateBillPresentment({
|
|
323
|
+
* reference: 'BILL-REF-123',
|
|
324
|
+
* amount: 399.00,
|
|
325
|
+
* description: 'Updated description'
|
|
326
|
+
* });
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
updateBillPresentment(params: UpdateBillParams): Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* List bill presentment audit trail
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```typescript
|
|
335
|
+
* const audits = await client.listBillPresentmentAudits('BILL-REF-123', 'USER-REF-123');
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
listBillPresentmentAudits(reference: string, userReference: string): Promise<BillAudit[]>;
|
|
168
339
|
/**
|
|
169
340
|
* Process a refund (credit transaction)
|
|
170
341
|
*
|
|
@@ -219,6 +390,91 @@ export declare class SoftyComp {
|
|
|
219
390
|
* ```
|
|
220
391
|
*/
|
|
221
392
|
parseWebhook(body: any): WebhookEvent;
|
|
393
|
+
/**
|
|
394
|
+
* Create a new client
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```typescript
|
|
398
|
+
* const clientId = await client.createClient({
|
|
399
|
+
* name: 'John',
|
|
400
|
+
* surname: 'Doe',
|
|
401
|
+
* email: 'john@example.com',
|
|
402
|
+
* phone: '0825551234',
|
|
403
|
+
* idNumber: '8001015009087'
|
|
404
|
+
* });
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
createClient(params: CreateClientParams): Promise<number>;
|
|
408
|
+
/**
|
|
409
|
+
* Create a Mobi-Mandate request for debit order sign-up
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const mandate = await client.createMobiMandate({
|
|
414
|
+
* customerEmail: 'john@example.com',
|
|
415
|
+
* customerPhone: '0825551234',
|
|
416
|
+
* surname: 'Doe',
|
|
417
|
+
* initials: 'J',
|
|
418
|
+
* amount: 99.00,
|
|
419
|
+
* frequency: 'monthly',
|
|
420
|
+
* debitDay: 1,
|
|
421
|
+
* description: 'Monthly subscription',
|
|
422
|
+
* successUrl: 'https://myapp.com/success'
|
|
423
|
+
* });
|
|
424
|
+
*
|
|
425
|
+
* // Redirect customer to mandate.url to sign
|
|
426
|
+
* console.log(mandate.url);
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
createMobiMandate(params: CreateMobiMandateParams): Promise<MobiMandateResult>;
|
|
430
|
+
/**
|
|
431
|
+
* Update collection status (e.g., cancel a debit order)
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* await client.updateCollectionStatus({
|
|
436
|
+
* collectionId: 12345,
|
|
437
|
+
* statusTypeId: 6 // 6 = Cancelled
|
|
438
|
+
* });
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
updateCollectionStatus(params: UpdateCollectionStatusParams): Promise<void>;
|
|
442
|
+
/**
|
|
443
|
+
* Create a credit distribution (payout to bank account)
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* const result = await client.createCreditDistribution({
|
|
448
|
+
* amount: 500.00,
|
|
449
|
+
* accountNumber: '1234567890',
|
|
450
|
+
* branchCode: '123456',
|
|
451
|
+
* accountName: 'John Doe',
|
|
452
|
+
* reference: 'PAYOUT-001'
|
|
453
|
+
* });
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
createCreditDistribution(params: CreditDistributionParams): Promise<CreditDistributionResult>;
|
|
457
|
+
/**
|
|
458
|
+
* Handle card expiry / re-auth: expire old bill and create new one
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* const newBill = await client.createReauthBill({
|
|
463
|
+
* oldReference: 'OLD-BILL-123',
|
|
464
|
+
* newReference: 'NEW-BILL-456',
|
|
465
|
+
* amount: 99.00,
|
|
466
|
+
* customerName: 'John Doe',
|
|
467
|
+
* customerEmail: 'john@example.com',
|
|
468
|
+
* customerPhone: '0825551234',
|
|
469
|
+
* description: 'Monthly subscription',
|
|
470
|
+
* billingCycle: 'MONTHLY',
|
|
471
|
+
* successUrl: 'https://myapp.com/success',
|
|
472
|
+
* cancelUrl: 'https://myapp.com/cancel',
|
|
473
|
+
* notifyUrl: 'https://myapp.com/webhook'
|
|
474
|
+
* });
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
createReauthBill(params: CreateReauthBillParams): Promise<CreateBillResult>;
|
|
222
478
|
/**
|
|
223
479
|
* Map SoftyComp status type ID to payment status
|
|
224
480
|
* @internal
|
package/dist/index.js
CHANGED
|
@@ -214,6 +214,76 @@ class SoftyComp {
|
|
|
214
214
|
data: result,
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
// ==================== Bill Management ====================
|
|
218
|
+
/**
|
|
219
|
+
* Set a bill to expired status
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* await client.setBillToExpiredStatus('BILL-REF-123', 'USER-REF-123');
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
async setBillToExpiredStatus(reference, userReference) {
|
|
227
|
+
await this.apiRequest('POST', `/api/paygatecontroller/setBillToExpiredStatus/${encodeURIComponent(reference)}/${encodeURIComponent(userReference)}`, '');
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Update bill presentment details
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* await client.updateBillPresentment({
|
|
235
|
+
* reference: 'BILL-REF-123',
|
|
236
|
+
* amount: 399.00,
|
|
237
|
+
* description: 'Updated description'
|
|
238
|
+
* });
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
async updateBillPresentment(params) {
|
|
242
|
+
// First, get the current bill to retrieve full structure
|
|
243
|
+
const currentBill = await this.apiRequest('GET', `/api/paygatecontroller/listBillPresentmentDetails/${params.reference}/${params.reference}`);
|
|
244
|
+
const updateData = {
|
|
245
|
+
Reference: params.reference,
|
|
246
|
+
UserReference: currentBill.userReference || params.reference,
|
|
247
|
+
Items: currentBill.items || [],
|
|
248
|
+
};
|
|
249
|
+
if (params.customerName !== undefined) {
|
|
250
|
+
updateData.Name = params.customerName;
|
|
251
|
+
}
|
|
252
|
+
if (params.customerEmail !== undefined) {
|
|
253
|
+
updateData.Emailaddress = params.customerEmail;
|
|
254
|
+
}
|
|
255
|
+
if (params.customerPhone !== undefined) {
|
|
256
|
+
updateData.Cellno = params.customerPhone;
|
|
257
|
+
}
|
|
258
|
+
// Update item fields if amount or description changed
|
|
259
|
+
if (updateData.Items.length > 0) {
|
|
260
|
+
if (params.amount !== undefined) {
|
|
261
|
+
updateData.Items[0].Amount = parseFloat(params.amount.toFixed(2));
|
|
262
|
+
}
|
|
263
|
+
if (params.description !== undefined) {
|
|
264
|
+
updateData.Items[0].Description = params.description;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
await this.apiRequest('POST', '/api/paygatecontroller/updateBillPresentment', updateData);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* List bill presentment audit trail
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const audits = await client.listBillPresentmentAudits('BILL-REF-123', 'USER-REF-123');
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
async listBillPresentmentAudits(reference, userReference) {
|
|
278
|
+
const result = await this.apiRequest('GET', `/api/paygatecontroller/listBillPresentmentAudits/${encodeURIComponent(reference)}/${encodeURIComponent(userReference)}`);
|
|
279
|
+
return (result || []).map((audit) => ({
|
|
280
|
+
auditId: audit.auditId || 0,
|
|
281
|
+
timestamp: audit.timestamp || audit.date || '',
|
|
282
|
+
description: audit.description || audit.action || '',
|
|
283
|
+
user: audit.user || audit.userName || '',
|
|
284
|
+
raw: audit,
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
217
287
|
// ==================== Refunds ====================
|
|
218
288
|
/**
|
|
219
289
|
* Process a refund (credit transaction)
|
|
@@ -330,6 +400,228 @@ class SoftyComp {
|
|
|
330
400
|
raw: event,
|
|
331
401
|
};
|
|
332
402
|
}
|
|
403
|
+
// ==================== Client Management ====================
|
|
404
|
+
/**
|
|
405
|
+
* Create a new client
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* const clientId = await client.createClient({
|
|
410
|
+
* name: 'John',
|
|
411
|
+
* surname: 'Doe',
|
|
412
|
+
* email: 'john@example.com',
|
|
413
|
+
* phone: '0825551234',
|
|
414
|
+
* idNumber: '8001015009087'
|
|
415
|
+
* });
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
async createClient(params) {
|
|
419
|
+
const result = await this.apiRequest('POST', '/api/clients/createclient', {
|
|
420
|
+
clientId: 0,
|
|
421
|
+
clientTypeId: 1, // Individual
|
|
422
|
+
contractCode: `C${Date.now().toString().slice(-13)}`, // Max 14 chars
|
|
423
|
+
initials: params.name.charAt(0),
|
|
424
|
+
surname: params.surname,
|
|
425
|
+
idnumber: params.idNumber || '',
|
|
426
|
+
clientStatusTypeId: 1, // Active
|
|
427
|
+
cellphoneNumber: params.phone,
|
|
428
|
+
emailAddress: params.email,
|
|
429
|
+
sendSmsDonotifications: true,
|
|
430
|
+
sendSmsUnpaidsNotifications: true,
|
|
431
|
+
isSouthAfricanCitizen: true,
|
|
432
|
+
fullNames: params.name,
|
|
433
|
+
});
|
|
434
|
+
if (!result.success) {
|
|
435
|
+
throw new Error(`SoftyComp create client failed: ${result.messages.join(', ')}`);
|
|
436
|
+
}
|
|
437
|
+
return result.value;
|
|
438
|
+
}
|
|
439
|
+
// ==================== Mobi-Mandate (Debit Orders) ====================
|
|
440
|
+
/**
|
|
441
|
+
* Create a Mobi-Mandate request for debit order sign-up
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```typescript
|
|
445
|
+
* const mandate = await client.createMobiMandate({
|
|
446
|
+
* customerEmail: 'john@example.com',
|
|
447
|
+
* customerPhone: '0825551234',
|
|
448
|
+
* surname: 'Doe',
|
|
449
|
+
* initials: 'J',
|
|
450
|
+
* amount: 99.00,
|
|
451
|
+
* frequency: 'monthly',
|
|
452
|
+
* debitDay: 1,
|
|
453
|
+
* description: 'Monthly subscription',
|
|
454
|
+
* successUrl: 'https://myapp.com/success'
|
|
455
|
+
* });
|
|
456
|
+
*
|
|
457
|
+
* // Redirect customer to mandate.url to sign
|
|
458
|
+
* console.log(mandate.url);
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
async createMobiMandate(params) {
|
|
462
|
+
const frequencyMap = {
|
|
463
|
+
monthly: 2,
|
|
464
|
+
yearly: 4,
|
|
465
|
+
};
|
|
466
|
+
const today = new Date();
|
|
467
|
+
const tomorrow = new Date(today);
|
|
468
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
469
|
+
const defaultCommencementDate = tomorrow.toISOString().split('T')[0];
|
|
470
|
+
const mandateData = {
|
|
471
|
+
EmailAddress: params.customerEmail,
|
|
472
|
+
CellphoneNumber: params.customerPhone,
|
|
473
|
+
ContractCode: params.contractCode || `M${Date.now().toString().slice(-5)}`, // Max 6 chars
|
|
474
|
+
Surname: params.surname,
|
|
475
|
+
Initials: params.initials || params.surname.charAt(0),
|
|
476
|
+
IDNumber: params.idNumber || '',
|
|
477
|
+
ProductID: params.productId ? parseInt(params.productId, 10) : null,
|
|
478
|
+
Amount: parseFloat(params.amount.toFixed(2)),
|
|
479
|
+
InitialAmount: params.initialAmount ? parseFloat(params.initialAmount.toFixed(2)) : parseFloat(params.amount.toFixed(2)),
|
|
480
|
+
AccountName: params.accountName || '',
|
|
481
|
+
AccountNumber: params.accountNumber || '',
|
|
482
|
+
BranchCode: params.branchCode || '',
|
|
483
|
+
AccountType: params.accountType || 1,
|
|
484
|
+
ExpiryDate: params.expiryDate || null,
|
|
485
|
+
CommencementDate: params.commencementDate || defaultCommencementDate,
|
|
486
|
+
CollectionFrequencyTypeID: frequencyMap[params.frequency] || 2,
|
|
487
|
+
CollectionMethodTypeID: params.collectionMethodTypeId || 4, // NAEDO
|
|
488
|
+
DebitDay: params.debitDay || 1,
|
|
489
|
+
Description: params.description || 'Debit Order',
|
|
490
|
+
DebitMonth: null,
|
|
491
|
+
TransactionDate1: null,
|
|
492
|
+
TransactionDate2: null,
|
|
493
|
+
TransactionDate3: null,
|
|
494
|
+
TransactionDate4: null,
|
|
495
|
+
NaedoTrackingCodeID: 12,
|
|
496
|
+
EntryClassCodeTypeID: 1,
|
|
497
|
+
AdjustmentCategoryTypeID: 2,
|
|
498
|
+
DebiCheckMaximumCollectionAmount: params.maxCollectionAmount || (params.amount * 1.5),
|
|
499
|
+
DateAdjustmentAllowed: false,
|
|
500
|
+
AdjustmentAmount: 0,
|
|
501
|
+
AdjustmentRate: 0,
|
|
502
|
+
DebitValueTypeID: 1,
|
|
503
|
+
RedirectURL: params.successUrl || '',
|
|
504
|
+
CallbackURL: params.callbackUrl || '',
|
|
505
|
+
SendCorrespondence: true,
|
|
506
|
+
ExternalRequest: true,
|
|
507
|
+
HideHomeTel: true,
|
|
508
|
+
HideWorkTel: true,
|
|
509
|
+
HideProductDetail: false,
|
|
510
|
+
HideExpiryDate: true,
|
|
511
|
+
HideAdditionalInfo: true,
|
|
512
|
+
HideDescription: false,
|
|
513
|
+
};
|
|
514
|
+
const result = await this.apiRequest('POST', '/api/mobimandate/generateMobiMandateRequest', mandateData);
|
|
515
|
+
if (!result.success) {
|
|
516
|
+
throw new Error(`SoftyComp Mobi-Mandate failed: ${result.message}`);
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
url: result.tinyURL,
|
|
520
|
+
success: result.success,
|
|
521
|
+
message: result.message,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Update collection status (e.g., cancel a debit order)
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```typescript
|
|
529
|
+
* await client.updateCollectionStatus({
|
|
530
|
+
* collectionId: 12345,
|
|
531
|
+
* statusTypeId: 6 // 6 = Cancelled
|
|
532
|
+
* });
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
async updateCollectionStatus(params) {
|
|
536
|
+
await this.apiRequest('POST', '/api/collections/updateCollectionStatus', {
|
|
537
|
+
collectionID: params.collectionId,
|
|
538
|
+
collectionStatusTypeID: params.statusTypeId,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// ==================== Credit Distribution (Payouts) ====================
|
|
542
|
+
/**
|
|
543
|
+
* Create a credit distribution (payout to bank account)
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```typescript
|
|
547
|
+
* const result = await client.createCreditDistribution({
|
|
548
|
+
* amount: 500.00,
|
|
549
|
+
* accountNumber: '1234567890',
|
|
550
|
+
* branchCode: '123456',
|
|
551
|
+
* accountName: 'John Doe',
|
|
552
|
+
* reference: 'PAYOUT-001'
|
|
553
|
+
* });
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
async createCreditDistribution(params) {
|
|
557
|
+
const result = await this.apiRequest('POST', '/api/creditdistribution/createCreditDistribution', {
|
|
558
|
+
creditFileTransactions: [
|
|
559
|
+
{
|
|
560
|
+
amount: parseFloat(params.amount.toFixed(2)),
|
|
561
|
+
accountNumber: params.accountNumber,
|
|
562
|
+
branchCode: params.branchCode,
|
|
563
|
+
accountName: params.accountName,
|
|
564
|
+
reference: params.reference,
|
|
565
|
+
userReference: params.reference, // Required by SoftyComp API
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
distributionId: result?.value?.toString() || `dist_${Date.now()}`,
|
|
571
|
+
success: result?.success || false,
|
|
572
|
+
messages: result?.messages || [],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
// ==================== Re-authentication ====================
|
|
576
|
+
/**
|
|
577
|
+
* Handle card expiry / re-auth: expire old bill and create new one
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```typescript
|
|
581
|
+
* const newBill = await client.createReauthBill({
|
|
582
|
+
* oldReference: 'OLD-BILL-123',
|
|
583
|
+
* newReference: 'NEW-BILL-456',
|
|
584
|
+
* amount: 99.00,
|
|
585
|
+
* customerName: 'John Doe',
|
|
586
|
+
* customerEmail: 'john@example.com',
|
|
587
|
+
* customerPhone: '0825551234',
|
|
588
|
+
* description: 'Monthly subscription',
|
|
589
|
+
* billingCycle: 'MONTHLY',
|
|
590
|
+
* successUrl: 'https://myapp.com/success',
|
|
591
|
+
* cancelUrl: 'https://myapp.com/cancel',
|
|
592
|
+
* notifyUrl: 'https://myapp.com/webhook'
|
|
593
|
+
* });
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
async createReauthBill(params) {
|
|
597
|
+
// Step 1: Expire the old bill
|
|
598
|
+
try {
|
|
599
|
+
await this.setBillToExpiredStatus(params.oldReference, params.oldReference);
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
console.warn(`[SoftyComp] Could not expire old bill ${params.oldReference}:`, err);
|
|
603
|
+
// Continue — the old bill may already be expired
|
|
604
|
+
}
|
|
605
|
+
// Step 2: Create a new bill with a different reference
|
|
606
|
+
const isMonthly = params.billingCycle === 'MONTHLY';
|
|
607
|
+
const tomorrow = new Date();
|
|
608
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
609
|
+
return this.createBill({
|
|
610
|
+
amount: params.amount,
|
|
611
|
+
customerName: params.customerName,
|
|
612
|
+
customerEmail: params.customerEmail,
|
|
613
|
+
customerPhone: params.customerPhone,
|
|
614
|
+
reference: params.newReference,
|
|
615
|
+
description: params.description,
|
|
616
|
+
frequency: isMonthly ? 'monthly' : 'yearly',
|
|
617
|
+
commencementDate: tomorrow.toISOString().split('T')[0],
|
|
618
|
+
recurringDay: tomorrow.getDate(),
|
|
619
|
+
recurringMonth: isMonthly ? undefined : (tomorrow.getMonth() + 1),
|
|
620
|
+
returnUrl: params.successUrl,
|
|
621
|
+
cancelUrl: params.cancelUrl,
|
|
622
|
+
notifyUrl: params.notifyUrl,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
333
625
|
// ==================== Helpers ====================
|
|
334
626
|
/**
|
|
335
627
|
* Map SoftyComp status type ID to payment status
|
|
@@ -342,6 +634,8 @@ class SoftyComp {
|
|
|
342
634
|
case 3: return 'failed'; // Failed
|
|
343
635
|
case 4: return 'cancelled'; // Expired
|
|
344
636
|
case 5: return 'cancelled'; // Cancelled
|
|
637
|
+
case 6: return 'failed'; // Arrears (card expired / payment failed)
|
|
638
|
+
case 7: return 'failed'; // ReAuth required
|
|
345
639
|
default: return 'pending';
|
|
346
640
|
}
|
|
347
641
|
}
|