paybridge 0.1.2 → 0.2.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.
Files changed (48) 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 +251 -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 +310 -0
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.js +59 -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 +338 -0
  25. package/dist/providers/ozow.d.ts +20 -2
  26. package/dist/providers/ozow.js +161 -114
  27. package/dist/providers/payfast.d.ts +40 -0
  28. package/dist/providers/payfast.js +355 -0
  29. package/dist/providers/paystack.d.ts +37 -0
  30. package/dist/providers/paystack.js +335 -0
  31. package/dist/providers/peach.d.ts +50 -0
  32. package/dist/providers/peach.js +305 -0
  33. package/dist/providers/softycomp.d.ts +106 -0
  34. package/dist/providers/softycomp.js +234 -10
  35. package/dist/providers/stripe.d.ts +38 -0
  36. package/dist/providers/stripe.js +370 -0
  37. package/dist/providers/yoco.d.ts +12 -0
  38. package/dist/providers/yoco.js +159 -61
  39. package/dist/router.d.ts +33 -0
  40. package/dist/router.js +247 -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/package.json +7 -4
@@ -20,114 +20,113 @@ class OzowProvider extends base_1.PaymentProvider {
20
20
  this.siteCode = config.siteCode;
21
21
  this.privateKey = config.privateKey;
22
22
  this.sandbox = config.sandbox;
23
- // Ozow API endpoints
24
- if (this.sandbox) {
25
- this.baseUrl = 'https://stagingapi.ozow.com';
26
- }
27
- else {
28
- this.baseUrl = 'https://api.ozow.com';
29
- }
23
+ this.baseUrl = 'https://api.ozow.com';
24
+ this.redirectBaseUrl = this.sandbox
25
+ ? 'https://stagingpay.ozow.com'
26
+ : 'https://pay.ozow.com';
30
27
  }
31
- // ==================== Payment Methods ====================
32
28
  async createPayment(params) {
33
29
  this.validateCurrency(params.currency);
34
- // TODO: Implement Ozow payment creation
35
- // POST /api/payments
36
- // Amount in RANDS (not cents)
37
- // Requires SHA512 hash of concatenated fields
38
- // Auth: API key in header
39
- // Body: {
40
- // SiteCode: "...",
41
- // CountryCode: "ZA",
42
- // CurrencyCode: "ZAR",
43
- // Amount: "299.00",
44
- // TransactionReference: "...",
45
- // BankReference: "...",
46
- // Customer: "John Doe",
47
- // Optional1: "email@example.com",
48
- // Optional2: "0825551234",
49
- // Optional3: "...",
50
- // Optional4: "...",
51
- // Optional5: "...",
52
- // CancelUrl: "...",
53
- // ErrorUrl: "...",
54
- // SuccessUrl: "...",
55
- // NotifyUrl: "...",
56
- // IsTest: true/false,
57
- // HashCheck: "sha512_hash"
58
- // }
59
- const requestData = {
30
+ const bankReference = this.sanitizeBankReference(params.reference);
31
+ const fields = {
60
32
  SiteCode: this.siteCode,
61
33
  CountryCode: 'ZA',
62
34
  CurrencyCode: params.currency,
63
35
  Amount: params.amount.toFixed(2),
64
36
  TransactionReference: params.reference,
65
- BankReference: params.reference,
66
- Customer: params.customer.name,
67
- Optional1: params.customer.email,
68
- Optional2: params.customer.phone || '',
69
- Optional3: params.description || '',
37
+ BankReference: bankReference,
38
+ Customer: params.customer.name || '',
70
39
  CancelUrl: params.urls.cancel,
71
40
  ErrorUrl: params.urls.cancel,
72
41
  SuccessUrl: params.urls.success,
73
42
  NotifyUrl: params.urls.webhook,
74
- IsTest: this.sandbox,
75
- // HashCheck: this.generateHash(...) // TODO: Implement hash generation
43
+ IsTest: this.sandbox ? 'true' : 'false',
44
+ };
45
+ const fieldOrder = [
46
+ 'SiteCode',
47
+ 'CountryCode',
48
+ 'CurrencyCode',
49
+ 'Amount',
50
+ 'TransactionReference',
51
+ 'BankReference',
52
+ 'Customer',
53
+ 'CancelUrl',
54
+ 'ErrorUrl',
55
+ 'SuccessUrl',
56
+ 'NotifyUrl',
57
+ 'IsTest',
58
+ ];
59
+ const hashCheck = this.generateHash(fields, fieldOrder);
60
+ const queryParams = new URLSearchParams();
61
+ fieldOrder.forEach(key => {
62
+ queryParams.append(key, fields[key]);
63
+ });
64
+ queryParams.append('HashCheck', hashCheck);
65
+ const checkoutUrl = `${this.redirectBaseUrl}?${queryParams.toString()}`;
66
+ return {
67
+ id: params.reference,
68
+ checkoutUrl,
69
+ status: 'pending',
70
+ amount: params.amount,
71
+ currency: params.currency,
72
+ reference: params.reference,
73
+ provider: 'ozow',
74
+ createdAt: new Date().toISOString(),
76
75
  };
77
- // TODO: Generate SHA512 hash
78
- // Hash = SHA512(SiteCode + CountryCode + CurrencyCode + Amount + TransactionReference + BankReference + Optional1 + Optional2 + Optional3 + Optional4 + Optional5 + CancelUrl + ErrorUrl + SuccessUrl + NotifyUrl + IsTest + PrivateKey)
79
- console.warn('[PayBridge:Ozow] createPayment not yet implemented:', requestData);
80
- throw new Error('Ozow provider not yet fully implemented. Coming soon!');
81
76
  }
82
- async createSubscription(params) {
83
- this.validateCurrency(params.currency);
84
- // TODO: Implement Ozow recurring payments
85
- // Ozow supports recurring payments via their recurring payment API
86
- // Different endpoint and structure from one-time payments
87
- console.warn('[PayBridge:Ozow] createSubscription not yet implemented');
88
- throw new Error('Ozow subscriptions not yet implemented. Coming soon!');
77
+ /**
78
+ * Ozow does not support recurring subscriptions (EFT instant-payment provider)
79
+ * Use a card-based provider for subscription functionality
80
+ */
81
+ async createSubscription(_params) {
82
+ throw new Error('Ozow does not support recurring subscriptions. EFT-based provider; use card-based provider for subscriptions.');
89
83
  }
90
84
  async getPayment(id) {
91
- // TODO: Implement Ozow payment status check
92
- // GET /api/payments/{transactionReference}
93
- // Auth: API key in header
94
- console.warn('[PayBridge:Ozow] getPayment not yet implemented:', id);
95
- throw new Error('Ozow getPayment not yet implemented. Coming soon!');
85
+ const url = `${this.baseUrl}/GetTransactionByReference?siteCode=${this.siteCode}&transactionReference=${encodeURIComponent(id)}`;
86
+ const response = await fetch(url, {
87
+ method: 'GET',
88
+ headers: {
89
+ 'ApiKey': this.apiKey,
90
+ },
91
+ });
92
+ if (!response.ok) {
93
+ const errorText = await response.text();
94
+ throw new Error(`Ozow getPayment failed: ${response.status} - ${errorText}`);
95
+ }
96
+ const data = (await response.json());
97
+ if (!Array.isArray(data) || data.length === 0) {
98
+ throw new Error('Transaction not found');
99
+ }
100
+ const transaction = data[0];
101
+ const status = this.mapOzowStatus(transaction.status);
102
+ return {
103
+ id: transaction.transactionId || id,
104
+ checkoutUrl: '',
105
+ status,
106
+ amount: parseFloat(transaction.amount || '0'),
107
+ currency: transaction.currencyCode || 'ZAR',
108
+ reference: transaction.transactionReference || id,
109
+ provider: 'ozow',
110
+ createdAt: transaction.createdDate || new Date().toISOString(),
111
+ raw: transaction,
112
+ };
96
113
  }
97
- async refund(params) {
98
- // TODO: Implement Ozow refund
99
- // POST /api/refunds
100
- // Auth: API key in header
101
- // Body: {
102
- // SiteCode: "...",
103
- // TransactionReference: "...",
104
- // Amount: "299.00", // optional for partial
105
- // HashCheck: "..."
106
- // }
107
- console.warn('[PayBridge:Ozow] refund not yet implemented:', params);
108
- throw new Error('Ozow refunds not yet implemented. Coming soon!');
114
+ /**
115
+ * Ozow refunds require manual processing via merchant portal
116
+ * No public API endpoint for instant-EFT refunds
117
+ */
118
+ async refund(_params) {
119
+ throw new Error('Ozow refunds must be processed manually via merchant.ozow.com — no API support.');
109
120
  }
110
- // ==================== Webhooks ====================
111
121
  parseWebhook(body, _headers) {
112
- const event = typeof body === 'string' ? JSON.parse(body) : body;
113
- // TODO: Map Ozow webhook structure to PayBridge WebhookEvent
114
- // Ozow webhook payload structure (form data):
115
- // {
116
- // SiteCode: "...",
117
- // TransactionId: "...",
118
- // TransactionReference: "...",
119
- // Amount: "299.00",
120
- // Status: "Complete" | "Cancelled" | "Error" | "Abandoned",
121
- // Optional1: "...",
122
- // Optional2: "...",
123
- // Optional3: "...",
124
- // Optional4: "...",
125
- // Optional5: "...",
126
- // CurrencyCode: "ZAR",
127
- // IsTest: "true" | "false",
128
- // StatusMessage: "...",
129
- // Hash: "sha512_hash"
130
- // }
122
+ let event;
123
+ if (typeof body === 'string') {
124
+ const params = new URLSearchParams(body);
125
+ event = Object.fromEntries(params.entries());
126
+ }
127
+ else {
128
+ event = body;
129
+ }
131
130
  const ozowStatus = event.Status;
132
131
  const status = this.mapOzowStatus(ozowStatus);
133
132
  const eventType = this.mapOzowEventType(ozowStatus);
@@ -146,19 +145,53 @@ class OzowProvider extends base_1.PaymentProvider {
146
145
  raw: event,
147
146
  };
148
147
  }
148
+ /**
149
+ * Verify Ozow ITN webhook signature
150
+ * Note: Ozow ITN has no timestamp, so no replay protection possible
151
+ * Caller should validate idempotency by TransactionId
152
+ */
149
153
  verifyWebhook(body, _headers) {
150
- const event = typeof body === 'string' ? JSON.parse(body) : body;
154
+ if (!this.apiKey) {
155
+ return false;
156
+ }
157
+ let event;
158
+ if (typeof body === 'string') {
159
+ const params = new URLSearchParams(body);
160
+ event = Object.fromEntries(params.entries());
161
+ }
162
+ else {
163
+ event = body;
164
+ }
151
165
  const receivedHash = event.Hash;
152
166
  if (!receivedHash) {
153
- // No hash provided
154
- return true;
167
+ return false;
155
168
  }
156
- // TODO: Verify Ozow SHA512 hash
157
- // expectedHash = SHA512(SiteCode + TransactionId + TransactionReference + Amount + Status + Optional1 + Optional2 + Optional3 + Optional4 + Optional5 + CurrencyCode + IsTest + StatusMessage + PrivateKey)
158
169
  const expectedHash = this.generateWebhookHash(event);
159
- return receivedHash === expectedHash;
170
+ try {
171
+ const receivedBuffer = Buffer.from(receivedHash);
172
+ const expectedBuffer = Buffer.from(expectedHash);
173
+ if (receivedBuffer.length !== expectedBuffer.length) {
174
+ return false;
175
+ }
176
+ return crypto_1.default.timingSafeEqual(receivedBuffer, expectedBuffer);
177
+ }
178
+ catch {
179
+ return false;
180
+ }
181
+ }
182
+ // ==================== Capabilities ====================
183
+ getCapabilities() {
184
+ return {
185
+ fees: {
186
+ fixed: 0,
187
+ percent: 1.5,
188
+ currency: 'ZAR',
189
+ },
190
+ currencies: this.supportedCurrencies,
191
+ country: 'ZA',
192
+ avgLatencyMs: 800,
193
+ };
160
194
  }
161
- // ==================== Helpers ====================
162
195
  mapOzowStatus(ozowStatus) {
163
196
  const statusMap = {
164
197
  Complete: 'completed',
@@ -166,6 +199,7 @@ class OzowProvider extends base_1.PaymentProvider {
166
199
  Error: 'failed',
167
200
  Abandoned: 'cancelled',
168
201
  PendingInvestigation: 'pending',
202
+ Pending: 'pending',
169
203
  };
170
204
  return statusMap[ozowStatus] || 'pending';
171
205
  }
@@ -178,26 +212,39 @@ class OzowProvider extends base_1.PaymentProvider {
178
212
  };
179
213
  return typeMap[ozowStatus] || 'payment.pending';
180
214
  }
215
+ generateHash(fields, fieldOrder) {
216
+ const concat = fieldOrder.map(k => fields[k] ?? '').join('') + this.apiKey;
217
+ return crypto_1.default.createHash('sha512').update(concat.toLowerCase()).digest('hex');
218
+ }
181
219
  generateWebhookHash(event) {
182
- // TODO: Implement SHA512 hash generation for webhook verification
183
- const fields = [
184
- event.SiteCode,
185
- event.TransactionId,
186
- event.TransactionReference,
187
- event.Amount,
188
- event.Status,
189
- event.Optional1 || '',
190
- event.Optional2 || '',
191
- event.Optional3 || '',
192
- event.Optional4 || '',
193
- event.Optional5 || '',
194
- event.CurrencyCode,
195
- event.IsTest,
196
- event.StatusMessage || '',
197
- this.privateKey,
220
+ const fieldOrder = [
221
+ 'SiteCode',
222
+ 'TransactionId',
223
+ 'TransactionReference',
224
+ 'Amount',
225
+ 'Status',
226
+ 'Optional1',
227
+ 'Optional2',
228
+ 'Optional3',
229
+ 'Optional4',
230
+ 'Optional5',
231
+ 'CurrencyCode',
232
+ 'IsTest',
233
+ 'StatusMessage',
198
234
  ];
199
- const concatenated = fields.join('');
200
- return crypto_1.default.createHash('sha512').update(concatenated, 'utf8').digest('hex').toLowerCase();
235
+ const fields = {};
236
+ fieldOrder.forEach(key => {
237
+ fields[key] = event[key] || '';
238
+ });
239
+ const concat = fieldOrder.map(k => fields[k]).join('') + this.apiKey;
240
+ return crypto_1.default.createHash('sha512').update(concat.toLowerCase()).digest('hex');
241
+ }
242
+ sanitizeBankReference(reference) {
243
+ const sanitized = reference.replace(/[^A-Za-z0-9]/g, '').substring(0, 20);
244
+ if (sanitized.length === 0) {
245
+ throw new Error('Ozow BankReference invalid: must contain at least 1 alphanumeric char');
246
+ }
247
+ return sanitized;
201
248
  }
202
249
  }
203
250
  exports.OzowProvider = OzowProvider;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * PayFast payment provider
3
+ * South African payment gateway (hosted checkout + ITN webhooks)
4
+ * @see https://developers.payfast.co.za
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 PayFastConfig {
10
+ merchantId: string;
11
+ merchantKey: string;
12
+ passphrase?: string;
13
+ sandbox: boolean;
14
+ webhookSecret?: string;
15
+ }
16
+ export declare class PayFastProvider extends PaymentProvider {
17
+ readonly name = "payfast";
18
+ readonly supportedCurrencies: string[];
19
+ private merchantId;
20
+ private merchantKey;
21
+ private passphrase?;
22
+ private sandbox;
23
+ private checkoutBaseUrl;
24
+ private apiBaseUrl;
25
+ constructor(config: PayFastConfig);
26
+ createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
27
+ createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
28
+ getPayment(id: string): Promise<PaymentResult>;
29
+ refund(params: RefundParams): Promise<RefundResult>;
30
+ parseWebhook(body: any, _headers?: any): WebhookEvent;
31
+ verifyWebhook(body: any, _headers?: any): boolean;
32
+ getCapabilities(): ProviderCapabilities;
33
+ private pfEncode;
34
+ private generateSignature;
35
+ private buildQueryString;
36
+ private generateApiSignature;
37
+ private mapPayFastStatus;
38
+ private mapEventType;
39
+ }
40
+ export {};