paybridge 0.1.3 → 0.2.2

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.
Files changed (50) hide show
  1. package/README.md +87 -7
  2. package/dist/circuit-breaker-store.d.ts +27 -0
  3. package/dist/circuit-breaker-store.js +25 -0
  4. package/dist/circuit-breaker.d.ts +30 -0
  5. package/dist/circuit-breaker.js +86 -0
  6. package/dist/crypto/base.d.ts +15 -0
  7. package/dist/crypto/base.js +24 -0
  8. package/dist/crypto/index.d.ts +35 -0
  9. package/dist/crypto/index.js +95 -0
  10. package/dist/crypto/mock.d.ts +15 -0
  11. package/dist/crypto/mock.js +112 -0
  12. package/dist/crypto/moonpay.d.ts +33 -0
  13. package/dist/crypto/moonpay.js +261 -0
  14. package/dist/crypto/router.d.ts +36 -0
  15. package/dist/crypto/router.js +287 -0
  16. package/dist/crypto/types.d.ts +89 -0
  17. package/dist/crypto/types.js +5 -0
  18. package/dist/crypto/yellowcard.d.ts +56 -0
  19. package/dist/crypto/yellowcard.js +311 -0
  20. package/dist/index.d.ts +10 -1
  21. package/dist/index.js +60 -3
  22. package/dist/providers/base.d.ts +5 -0
  23. package/dist/providers/flutterwave.d.ts +36 -0
  24. package/dist/providers/flutterwave.js +339 -0
  25. package/dist/providers/ozow.d.ts +20 -2
  26. package/dist/providers/ozow.js +158 -114
  27. package/dist/providers/payfast.d.ts +40 -0
  28. package/dist/providers/payfast.js +352 -0
  29. package/dist/providers/paystack.d.ts +37 -0
  30. package/dist/providers/paystack.js +336 -0
  31. package/dist/providers/peach.d.ts +50 -0
  32. package/dist/providers/peach.js +302 -0
  33. package/dist/providers/softycomp.d.ts +106 -0
  34. package/dist/providers/softycomp.js +229 -10
  35. package/dist/providers/stripe.d.ts +38 -0
  36. package/dist/providers/stripe.js +367 -0
  37. package/dist/providers/yoco.d.ts +12 -0
  38. package/dist/providers/yoco.js +148 -61
  39. package/dist/router.d.ts +33 -0
  40. package/dist/router.js +282 -0
  41. package/dist/routing-types.d.ts +39 -0
  42. package/dist/routing-types.js +14 -0
  43. package/dist/stores/redis.d.ts +30 -0
  44. package/dist/stores/redis.js +42 -0
  45. package/dist/strategies.d.ts +18 -0
  46. package/dist/strategies.js +44 -0
  47. package/dist/types.d.ts +4 -2
  48. package/dist/utils/fetch.d.ts +24 -0
  49. package/dist/utils/fetch.js +74 -0
  50. package/package.json +7 -4
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { PaymentProvider } from './base';
6
6
  import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
7
+ import { ProviderCapabilities } from '../routing-types';
7
8
  interface SoftyCompConfig {
8
9
  apiKey: string;
9
10
  secretKey: string;
@@ -29,6 +30,111 @@ export declare class SoftyCompProvider extends PaymentProvider {
29
30
  refund(params: RefundParams): Promise<RefundResult>;
30
31
  parseWebhook(body: any, _headers?: any): WebhookEvent;
31
32
  verifyWebhook(body: string | Buffer, headers?: any): boolean;
33
+ /**
34
+ * Set a bill to expired status
35
+ */
36
+ setBillToExpiredStatus(reference: string, userReference: string): Promise<void>;
37
+ /**
38
+ * Update bill presentment details
39
+ */
40
+ updateBillPresentment(params: {
41
+ reference: string;
42
+ amount?: number;
43
+ description?: string;
44
+ customerName?: string;
45
+ customerEmail?: string;
46
+ customerPhone?: string;
47
+ }): Promise<void>;
48
+ /**
49
+ * List bill presentment audit trail
50
+ */
51
+ listBillPresentmentAudits(reference: string, userReference: string): Promise<Array<{
52
+ auditId: number;
53
+ timestamp: string;
54
+ description: string;
55
+ user: string;
56
+ raw: any;
57
+ }>>;
58
+ /**
59
+ * Create a new client
60
+ */
61
+ createClient(params: {
62
+ name: string;
63
+ surname: string;
64
+ email: string;
65
+ phone: string;
66
+ idNumber?: string;
67
+ }): Promise<number>;
68
+ /**
69
+ * Create a Mobi-Mandate request for debit order sign-up
70
+ */
71
+ createMobiMandate(params: {
72
+ customerEmail: string;
73
+ customerPhone: string;
74
+ surname: string;
75
+ initials?: string;
76
+ idNumber?: string;
77
+ amount: number;
78
+ frequency: 'monthly' | 'yearly';
79
+ debitDay?: number;
80
+ description?: string;
81
+ contractCode?: string;
82
+ initialAmount?: number;
83
+ accountName?: string;
84
+ accountNumber?: string;
85
+ branchCode?: string;
86
+ accountType?: number;
87
+ expiryDate?: string;
88
+ commencementDate?: string;
89
+ collectionMethodTypeId?: number;
90
+ productId?: string;
91
+ maxCollectionAmount?: number;
92
+ successUrl?: string;
93
+ callbackUrl?: string;
94
+ }): Promise<{
95
+ url: string;
96
+ success: boolean;
97
+ message: string;
98
+ }>;
99
+ /**
100
+ * Update collection status (e.g., cancel a debit order)
101
+ */
102
+ updateCollectionStatus(params: {
103
+ collectionId: number;
104
+ statusTypeId: number;
105
+ }): Promise<void>;
106
+ /**
107
+ * Create a credit distribution (payout to bank account)
108
+ */
109
+ createCreditDistribution(params: {
110
+ amount: number;
111
+ accountNumber: string;
112
+ branchCode: string;
113
+ accountName: string;
114
+ reference: string;
115
+ userReference?: string;
116
+ }): Promise<{
117
+ distributionId: string;
118
+ success: boolean;
119
+ messages: string[];
120
+ }>;
121
+ /**
122
+ * Handle card expiry / re-auth: expire old bill and create new one
123
+ */
124
+ createReauthBill(params: {
125
+ oldReference: string;
126
+ newReference: string;
127
+ amount: number;
128
+ customerName: string;
129
+ customerEmail: string;
130
+ customerPhone: string;
131
+ description: string;
132
+ billingCycle: 'MONTHLY' | 'YEARLY';
133
+ successUrl: string;
134
+ cancelUrl: string;
135
+ notifyUrl: string;
136
+ }): Promise<SubscriptionResult>;
137
+ getCapabilities(): ProviderCapabilities;
32
138
  private mapBillStatus;
33
139
  }
34
140
  export {};
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.SoftyCompProvider = void 0;
11
11
  const crypto_1 = __importDefault(require("crypto"));
12
12
  const base_1 = require("./base");
13
+ const fetch_1 = require("../utils/fetch");
13
14
  class SoftyCompProvider extends base_1.PaymentProvider {
14
15
  constructor(config) {
15
16
  super();
@@ -36,7 +37,7 @@ class SoftyCompProvider extends base_1.PaymentProvider {
36
37
  if (this.token && Date.now() < this.tokenExpiry - 60000) {
37
38
  return this.token;
38
39
  }
39
- const response = await fetch(`${this.baseUrl}/api/auth/generatetoken`, {
40
+ const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/api/auth/generatetoken`, {
40
41
  method: 'POST',
41
42
  headers: { 'Content-Type': 'application/json' },
42
43
  body: JSON.stringify({
@@ -44,10 +45,6 @@ class SoftyCompProvider extends base_1.PaymentProvider {
44
45
  apiSecret: this.secretKey,
45
46
  }),
46
47
  });
47
- if (!response.ok) {
48
- const errorText = await response.text();
49
- throw new Error(`SoftyComp authentication failed: ${response.status} - ${errorText}`);
50
- }
51
48
  const data = (await response.json());
52
49
  this.token = data.token;
53
50
  this.tokenExpiry = new Date(data.expiration).getTime();
@@ -56,7 +53,7 @@ class SoftyCompProvider extends base_1.PaymentProvider {
56
53
  async apiRequest(method, path, data) {
57
54
  const token = await this.authenticate();
58
55
  const url = `${this.baseUrl}${path}`;
59
- const response = await fetch(url, {
56
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
60
57
  method,
61
58
  headers: {
62
59
  Authorization: `Bearer ${token}`,
@@ -64,10 +61,6 @@ class SoftyCompProvider extends base_1.PaymentProvider {
64
61
  },
65
62
  body: data ? JSON.stringify(data) : undefined,
66
63
  });
67
- if (!response.ok) {
68
- const errorText = await response.text();
69
- throw new Error(`SoftyComp API error (${method} ${path}): ${response.status} - ${errorText}`);
70
- }
71
64
  const contentType = response.headers.get('content-type');
72
65
  if (contentType && contentType.includes('application/json')) {
73
66
  return (await response.json());
@@ -277,6 +270,232 @@ class SoftyCompProvider extends base_1.PaymentProvider {
277
270
  .digest('hex');
278
271
  return signature === expectedSignature || signature === `sha256=${expectedSignature}`;
279
272
  }
273
+ // ==================== Bill Management ====================
274
+ /**
275
+ * Set a bill to expired status
276
+ */
277
+ async setBillToExpiredStatus(reference, userReference) {
278
+ await this.apiRequest('POST', `/api/paygatecontroller/setBillToExpiredStatus/${encodeURIComponent(reference)}/${encodeURIComponent(userReference)}`, '' // Empty body required
279
+ );
280
+ }
281
+ /**
282
+ * Update bill presentment details
283
+ */
284
+ async updateBillPresentment(params) {
285
+ // First, get the current bill to retrieve full structure
286
+ const currentBill = await this.apiRequest('GET', `/api/paygatecontroller/listBillPresentmentDetails/${params.reference}/${params.reference}`);
287
+ const updateData = {
288
+ Reference: params.reference,
289
+ UserReference: currentBill.userReference || params.reference,
290
+ Items: currentBill.items || [],
291
+ };
292
+ if (params.customerName !== undefined) {
293
+ updateData.Name = params.customerName;
294
+ }
295
+ if (params.customerEmail !== undefined) {
296
+ updateData.Emailaddress = params.customerEmail;
297
+ }
298
+ if (params.customerPhone !== undefined) {
299
+ updateData.Cellno = params.customerPhone;
300
+ }
301
+ // Update item fields if amount or description changed
302
+ if (updateData.Items.length > 0) {
303
+ if (params.amount !== undefined) {
304
+ updateData.Items[0].Amount = parseFloat(params.amount.toFixed(2));
305
+ }
306
+ if (params.description !== undefined) {
307
+ updateData.Items[0].Description = params.description;
308
+ }
309
+ }
310
+ await this.apiRequest('POST', '/api/paygatecontroller/updateBillPresentment', updateData);
311
+ }
312
+ /**
313
+ * List bill presentment audit trail
314
+ */
315
+ async listBillPresentmentAudits(reference, userReference) {
316
+ const result = await this.apiRequest('GET', `/api/paygatecontroller/listBillPresentmentAudits/${encodeURIComponent(reference)}/${encodeURIComponent(userReference)}`);
317
+ return (result || []).map((audit) => ({
318
+ auditId: audit.auditId || 0,
319
+ timestamp: audit.timestamp || audit.date || '',
320
+ description: audit.description || audit.action || '',
321
+ user: audit.user || audit.userName || '',
322
+ raw: audit,
323
+ }));
324
+ }
325
+ // ==================== Client Management ====================
326
+ /**
327
+ * Create a new client
328
+ */
329
+ async createClient(params) {
330
+ const result = await this.apiRequest('POST', '/api/clients/createclient', {
331
+ clientId: 0,
332
+ clientTypeId: 1, // Individual
333
+ contractCode: `C${Date.now().toString().slice(-13)}`, // Max 14 chars
334
+ initials: params.name.charAt(0),
335
+ surname: params.surname,
336
+ idnumber: params.idNumber || '',
337
+ clientStatusTypeId: 1, // Active
338
+ cellphoneNumber: params.phone,
339
+ emailAddress: params.email,
340
+ sendSmsDonotifications: true,
341
+ sendSmsUnpaidsNotifications: true,
342
+ isSouthAfricanCitizen: true,
343
+ fullNames: params.name,
344
+ });
345
+ if (!result.success) {
346
+ throw new Error(`SoftyComp create client failed: ${result.messages.join(', ')}`);
347
+ }
348
+ return result.value;
349
+ }
350
+ // ==================== Mobi-Mandate (Debit Orders) ====================
351
+ /**
352
+ * Create a Mobi-Mandate request for debit order sign-up
353
+ */
354
+ async createMobiMandate(params) {
355
+ const frequencyMap = {
356
+ monthly: 2,
357
+ yearly: 4,
358
+ };
359
+ const today = new Date();
360
+ const tomorrow = new Date(today);
361
+ tomorrow.setDate(tomorrow.getDate() + 1);
362
+ const defaultCommencementDate = tomorrow.toISOString().split('T')[0];
363
+ const mandateData = {
364
+ EmailAddress: params.customerEmail,
365
+ CellphoneNumber: params.customerPhone,
366
+ ContractCode: params.contractCode || `M${Date.now().toString().slice(-5)}`, // Max 6 chars
367
+ Surname: params.surname,
368
+ Initials: params.initials || params.surname.charAt(0),
369
+ IDNumber: params.idNumber || '',
370
+ ProductID: params.productId ? parseInt(params.productId, 10) : null,
371
+ Amount: parseFloat(params.amount.toFixed(2)),
372
+ InitialAmount: params.initialAmount ? parseFloat(params.initialAmount.toFixed(2)) : parseFloat(params.amount.toFixed(2)),
373
+ AccountName: params.accountName || '',
374
+ AccountNumber: params.accountNumber || '',
375
+ BranchCode: params.branchCode || '',
376
+ AccountType: params.accountType || 1,
377
+ ExpiryDate: params.expiryDate || null,
378
+ CommencementDate: params.commencementDate || defaultCommencementDate,
379
+ CollectionFrequencyTypeID: frequencyMap[params.frequency] || 2,
380
+ CollectionMethodTypeID: params.collectionMethodTypeId || 4, // NAEDO
381
+ DebitDay: params.debitDay || 1,
382
+ Description: params.description || 'Debit Order',
383
+ DebitMonth: null,
384
+ TransactionDate1: null,
385
+ TransactionDate2: null,
386
+ TransactionDate3: null,
387
+ TransactionDate4: null,
388
+ NaedoTrackingCodeID: 12,
389
+ EntryClassCodeTypeID: 1,
390
+ AdjustmentCategoryTypeID: 2,
391
+ DebiCheckMaximumCollectionAmount: params.maxCollectionAmount || (params.amount * 1.5),
392
+ DateAdjustmentAllowed: false,
393
+ AdjustmentAmount: 0,
394
+ AdjustmentRate: 0,
395
+ DebitValueTypeID: 1,
396
+ RedirectURL: params.successUrl || '',
397
+ CallbackURL: params.callbackUrl || '',
398
+ SendCorrespondence: true,
399
+ ExternalRequest: true,
400
+ HideHomeTel: true,
401
+ HideWorkTel: true,
402
+ HideProductDetail: false,
403
+ HideExpiryDate: true,
404
+ HideAdditionalInfo: true,
405
+ HideDescription: false,
406
+ };
407
+ const result = await this.apiRequest('POST', '/api/mobimandate/generateMobiMandateRequest', mandateData);
408
+ if (!result.success) {
409
+ throw new Error(`SoftyComp Mobi-Mandate failed: ${result.message}`);
410
+ }
411
+ return {
412
+ url: result.tinyURL,
413
+ success: result.success,
414
+ message: result.message,
415
+ };
416
+ }
417
+ /**
418
+ * Update collection status (e.g., cancel a debit order)
419
+ */
420
+ async updateCollectionStatus(params) {
421
+ await this.apiRequest('POST', '/api/collections/updateCollectionStatus', {
422
+ collectionID: params.collectionId,
423
+ collectionStatusTypeID: params.statusTypeId,
424
+ });
425
+ }
426
+ // ==================== Credit Distribution (Payouts) ====================
427
+ /**
428
+ * Create a credit distribution (payout to bank account)
429
+ */
430
+ async createCreditDistribution(params) {
431
+ const result = await this.apiRequest('POST', '/api/creditdistribution/createCreditDistribution', {
432
+ creditFileTransactions: [
433
+ {
434
+ amount: parseFloat(params.amount.toFixed(2)),
435
+ accountNumber: params.accountNumber,
436
+ branchCode: params.branchCode,
437
+ accountName: params.accountName,
438
+ reference: params.reference,
439
+ userReference: params.userReference || params.reference,
440
+ },
441
+ ],
442
+ });
443
+ return {
444
+ distributionId: result?.value?.toString() || `dist_${Date.now()}`,
445
+ success: result?.success || false,
446
+ messages: result?.messages || [],
447
+ };
448
+ }
449
+ // ==================== Re-authentication ====================
450
+ /**
451
+ * Handle card expiry / re-auth: expire old bill and create new one
452
+ */
453
+ async createReauthBill(params) {
454
+ // Step 1: Expire the old bill
455
+ try {
456
+ await this.setBillToExpiredStatus(params.oldReference, params.oldReference);
457
+ }
458
+ catch (err) {
459
+ console.warn(`[SoftyComp] Could not expire old bill ${params.oldReference}:`, err);
460
+ // Continue — the old bill may already be expired
461
+ }
462
+ // Step 2: Create a new subscription with a different reference
463
+ const isMonthly = params.billingCycle === 'MONTHLY';
464
+ const tomorrow = new Date();
465
+ tomorrow.setDate(tomorrow.getDate() + 1);
466
+ return this.createSubscription({
467
+ amount: params.amount,
468
+ currency: 'ZAR',
469
+ interval: isMonthly ? 'monthly' : 'yearly',
470
+ reference: params.newReference,
471
+ description: params.description,
472
+ customer: {
473
+ name: params.customerName,
474
+ email: params.customerEmail,
475
+ phone: params.customerPhone,
476
+ },
477
+ urls: {
478
+ success: params.successUrl,
479
+ cancel: params.cancelUrl,
480
+ webhook: params.notifyUrl,
481
+ },
482
+ startDate: tomorrow.toISOString().split('T')[0],
483
+ billingDay: tomorrow.getDate(),
484
+ });
485
+ }
486
+ // ==================== Capabilities ====================
487
+ getCapabilities() {
488
+ return {
489
+ fees: {
490
+ fixed: 0,
491
+ percent: 2.5,
492
+ currency: 'ZAR',
493
+ },
494
+ currencies: this.supportedCurrencies,
495
+ country: 'ZA',
496
+ avgLatencyMs: 800,
497
+ };
498
+ }
280
499
  // ==================== Helpers ====================
281
500
  mapBillStatus(statusTypeID) {
282
501
  switch (Number(statusTypeID)) {
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Stripe payment provider
3
+ * Global payment processor supporting 135+ currencies
4
+ * @see https://stripe.com/docs/api
5
+ */
6
+ import { PaymentProvider } from './base';
7
+ import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
8
+ import { ProviderCapabilities } from '../routing-types';
9
+ interface StripeConfig {
10
+ apiKey: string;
11
+ webhookSecret?: string;
12
+ sandbox?: boolean;
13
+ }
14
+ export declare class StripeProvider extends PaymentProvider {
15
+ readonly name = "stripe";
16
+ readonly supportedCurrencies: string[];
17
+ private apiKey;
18
+ private webhookSecret?;
19
+ private sandbox;
20
+ private baseUrl;
21
+ constructor(config: StripeConfig);
22
+ private buildFormData;
23
+ private apiRequest;
24
+ createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
25
+ createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
26
+ getPayment(id: string): Promise<PaymentResult>;
27
+ refund(params: RefundParams): Promise<RefundResult>;
28
+ parseWebhook(body: any, _headers?: any): WebhookEvent;
29
+ /**
30
+ * Verify webhook signature using Stripe's scheme.
31
+ *
32
+ * CRITICAL: body must be the raw string or Buffer from the webhook request.
33
+ * Passing a parsed JSON object will cause signature verification to fail.
34
+ */
35
+ verifyWebhook(body: string | Buffer, headers?: any): boolean;
36
+ getCapabilities(): ProviderCapabilities;
37
+ }
38
+ export {};