softycomp-node 1.0.4 → 1.1.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
@@ -10,9 +10,13 @@ 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+)
@@ -185,6 +189,100 @@ const status = await client.getBillStatus('BILL-REF-123');
185
189
  }
186
190
  ```
187
191
 
192
+ ### Bill Management
193
+
194
+ #### Expire a Bill
195
+
196
+ Set a bill to expired status (e.g., for cancelling a recurring bill):
197
+
198
+ ```typescript
199
+ await client.setBillToExpiredStatus('BILL-REF-123', 'USER-REF-123');
200
+ ```
201
+
202
+ #### Update Bill Presentment
203
+
204
+ Update details of an existing bill:
205
+
206
+ ```typescript
207
+ await client.updateBillPresentment({
208
+ reference: 'BILL-REF-123',
209
+ amount: 399.00,
210
+ description: 'Updated description',
211
+ customerEmail: 'newemail@example.com'
212
+ });
213
+ ```
214
+
215
+ #### List Bill Audit Trail
216
+
217
+ Get the audit trail for a bill:
218
+
219
+ ```typescript
220
+ const audits = await client.listBillPresentmentAudits('BILL-REF-123', 'USER-REF-123');
221
+
222
+ // Returns array of:
223
+ {
224
+ auditId: number;
225
+ timestamp: string;
226
+ description: string;
227
+ user: string;
228
+ raw: any;
229
+ }
230
+ ```
231
+
232
+ ### Client Management
233
+
234
+ Create a new client profile:
235
+
236
+ ```typescript
237
+ const clientId = await client.createClient({
238
+ name: 'John',
239
+ surname: 'Doe',
240
+ email: 'john@example.com',
241
+ phone: '0825551234',
242
+ idNumber: '8001015009087' // Optional SA ID number
243
+ });
244
+
245
+ console.log(`Created client ID: ${clientId}`);
246
+ ```
247
+
248
+ ### Mobi-Mandate (Debit Orders)
249
+
250
+ 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.
251
+
252
+ ```typescript
253
+ const mandate = await client.createMobiMandate({
254
+ customerEmail: 'john@example.com',
255
+ customerPhone: '0825551234',
256
+ surname: 'Doe',
257
+ initials: 'J',
258
+ amount: 99.00, // Monthly/yearly charge amount
259
+ frequency: 'monthly', // or 'yearly'
260
+ debitDay: 1, // Day of month to charge (1-28)
261
+ description: 'Monthly subscription',
262
+ successUrl: 'https://myapp.com/success',
263
+ callbackUrl: 'https://myapp.com/webhook'
264
+ });
265
+
266
+ // Redirect customer to sign the mandate
267
+ console.log(mandate.url); // e.g., https://popay.co.za/xxx
268
+
269
+ // Returns:
270
+ {
271
+ url: string; // Mandate sign-up URL
272
+ success: boolean;
273
+ message: string;
274
+ }
275
+ ```
276
+
277
+ #### Cancel a Debit Order Collection
278
+
279
+ ```typescript
280
+ await client.updateCollectionStatus({
281
+ collectionId: 12345,
282
+ statusTypeId: 6 // 6 = Cancelled
283
+ });
284
+ ```
285
+
188
286
  ### Refund Payment
189
287
 
190
288
  Process a full or partial refund (credit transaction).
@@ -209,6 +307,50 @@ const refund = await client.refund({
209
307
  }
210
308
  ```
211
309
 
310
+ ### Credit Distribution (Payouts)
311
+
312
+ Send money to a bank account:
313
+
314
+ ```typescript
315
+ const result = await client.createCreditDistribution({
316
+ amount: 500.00,
317
+ accountNumber: '1234567890',
318
+ branchCode: '123456',
319
+ accountName: 'John Doe',
320
+ reference: 'PAYOUT-001'
321
+ });
322
+
323
+ // Returns:
324
+ {
325
+ distributionId: string;
326
+ success: boolean;
327
+ messages: string[];
328
+ }
329
+ ```
330
+
331
+ ### Re-authentication (Card Expiry)
332
+
333
+ Handle card expiry by expiring the old bill and creating a new one with a different reference:
334
+
335
+ ```typescript
336
+ const newBill = await client.createReauthBill({
337
+ oldReference: 'OLD-BILL-123',
338
+ newReference: 'NEW-BILL-456', // MUST be different
339
+ amount: 99.00,
340
+ customerName: 'John Doe',
341
+ customerEmail: 'john@example.com',
342
+ customerPhone: '0825551234',
343
+ description: 'Monthly subscription',
344
+ billingCycle: 'MONTHLY', // or 'YEARLY'
345
+ successUrl: 'https://myapp.com/success',
346
+ cancelUrl: 'https://myapp.com/cancel',
347
+ notifyUrl: 'https://myapp.com/webhook'
348
+ });
349
+
350
+ // Customer re-enters card details at newBill.paymentUrl
351
+ console.log(newBill.paymentUrl);
352
+ ```
353
+
212
354
  ### Webhook Handling
213
355
 
214
356
  SoftyComp sends real-time payment notifications to your `notifyUrl`.
@@ -366,7 +508,16 @@ import {
366
508
  PaymentStatus,
367
509
  WebhookEvent,
368
510
  CreateBillParams,
369
- RefundParams
511
+ RefundParams,
512
+ CreateClientParams,
513
+ CreateMobiMandateParams,
514
+ MobiMandateResult,
515
+ UpdateBillParams,
516
+ BillAudit,
517
+ CreditDistributionParams,
518
+ CreditDistributionResult,
519
+ CreateReauthBillParams,
520
+ UpdateCollectionStatusParams
370
521
  } from 'softycomp-node';
371
522
  ```
372
523
 
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,227 @@ 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
+ },
566
+ ],
567
+ });
568
+ return {
569
+ distributionId: result?.value?.toString() || `dist_${Date.now()}`,
570
+ success: result?.success || false,
571
+ messages: result?.messages || [],
572
+ };
573
+ }
574
+ // ==================== Re-authentication ====================
575
+ /**
576
+ * Handle card expiry / re-auth: expire old bill and create new one
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const newBill = await client.createReauthBill({
581
+ * oldReference: 'OLD-BILL-123',
582
+ * newReference: 'NEW-BILL-456',
583
+ * amount: 99.00,
584
+ * customerName: 'John Doe',
585
+ * customerEmail: 'john@example.com',
586
+ * customerPhone: '0825551234',
587
+ * description: 'Monthly subscription',
588
+ * billingCycle: 'MONTHLY',
589
+ * successUrl: 'https://myapp.com/success',
590
+ * cancelUrl: 'https://myapp.com/cancel',
591
+ * notifyUrl: 'https://myapp.com/webhook'
592
+ * });
593
+ * ```
594
+ */
595
+ async createReauthBill(params) {
596
+ // Step 1: Expire the old bill
597
+ try {
598
+ await this.setBillToExpiredStatus(params.oldReference, params.oldReference);
599
+ }
600
+ catch (err) {
601
+ console.warn(`[SoftyComp] Could not expire old bill ${params.oldReference}:`, err);
602
+ // Continue — the old bill may already be expired
603
+ }
604
+ // Step 2: Create a new bill with a different reference
605
+ const isMonthly = params.billingCycle === 'MONTHLY';
606
+ const tomorrow = new Date();
607
+ tomorrow.setDate(tomorrow.getDate() + 1);
608
+ return this.createBill({
609
+ amount: params.amount,
610
+ customerName: params.customerName,
611
+ customerEmail: params.customerEmail,
612
+ customerPhone: params.customerPhone,
613
+ reference: params.newReference,
614
+ description: params.description,
615
+ frequency: isMonthly ? 'monthly' : 'yearly',
616
+ commencementDate: tomorrow.toISOString().split('T')[0],
617
+ recurringDay: tomorrow.getDate(),
618
+ recurringMonth: isMonthly ? undefined : (tomorrow.getMonth() + 1),
619
+ returnUrl: params.successUrl,
620
+ cancelUrl: params.cancelUrl,
621
+ notifyUrl: params.notifyUrl,
622
+ });
623
+ }
333
624
  // ==================== Helpers ====================
334
625
  /**
335
626
  * Map SoftyComp status type ID to payment status
@@ -342,6 +633,8 @@ class SoftyComp {
342
633
  case 3: return 'failed'; // Failed
343
634
  case 4: return 'cancelled'; // Expired
344
635
  case 5: return 'cancelled'; // Cancelled
636
+ case 6: return 'failed'; // Arrears (card expired / payment failed)
637
+ case 7: return 'failed'; // ReAuth required
345
638
  default: return 'pending';
346
639
  }
347
640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softycomp-node",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Node.js SDK for SoftyComp — South African bill presentment and debit order platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",