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
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.OzowProvider = void 0;
12
12
  const crypto_1 = __importDefault(require("crypto"));
13
13
  const base_1 = require("./base");
14
+ const fetch_1 = require("../utils/fetch");
14
15
  class OzowProvider extends base_1.PaymentProvider {
15
16
  constructor(config) {
16
17
  super();
@@ -20,114 +21,109 @@ class OzowProvider extends base_1.PaymentProvider {
20
21
  this.siteCode = config.siteCode;
21
22
  this.privateKey = config.privateKey;
22
23
  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
- }
24
+ this.baseUrl = 'https://api.ozow.com';
25
+ this.redirectBaseUrl = this.sandbox
26
+ ? 'https://stagingpay.ozow.com'
27
+ : 'https://pay.ozow.com';
30
28
  }
31
- // ==================== Payment Methods ====================
32
29
  async createPayment(params) {
33
30
  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 = {
31
+ const bankReference = this.sanitizeBankReference(params.reference);
32
+ const fields = {
60
33
  SiteCode: this.siteCode,
61
34
  CountryCode: 'ZA',
62
35
  CurrencyCode: params.currency,
63
36
  Amount: params.amount.toFixed(2),
64
37
  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 || '',
38
+ BankReference: bankReference,
39
+ Customer: params.customer.name || '',
70
40
  CancelUrl: params.urls.cancel,
71
41
  ErrorUrl: params.urls.cancel,
72
42
  SuccessUrl: params.urls.success,
73
43
  NotifyUrl: params.urls.webhook,
74
- IsTest: this.sandbox,
75
- // HashCheck: this.generateHash(...) // TODO: Implement hash generation
44
+ IsTest: this.sandbox ? 'true' : 'false',
45
+ };
46
+ const fieldOrder = [
47
+ 'SiteCode',
48
+ 'CountryCode',
49
+ 'CurrencyCode',
50
+ 'Amount',
51
+ 'TransactionReference',
52
+ 'BankReference',
53
+ 'Customer',
54
+ 'CancelUrl',
55
+ 'ErrorUrl',
56
+ 'SuccessUrl',
57
+ 'NotifyUrl',
58
+ 'IsTest',
59
+ ];
60
+ const hashCheck = this.generateHash(fields, fieldOrder);
61
+ const queryParams = new URLSearchParams();
62
+ fieldOrder.forEach(key => {
63
+ queryParams.append(key, fields[key]);
64
+ });
65
+ queryParams.append('HashCheck', hashCheck);
66
+ const checkoutUrl = `${this.redirectBaseUrl}?${queryParams.toString()}`;
67
+ return {
68
+ id: params.reference,
69
+ checkoutUrl,
70
+ status: 'pending',
71
+ amount: params.amount,
72
+ currency: params.currency,
73
+ reference: params.reference,
74
+ provider: 'ozow',
75
+ createdAt: new Date().toISOString(),
76
76
  };
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
77
  }
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!');
78
+ /**
79
+ * Ozow does not support recurring subscriptions (EFT instant-payment provider)
80
+ * Use a card-based provider for subscription functionality
81
+ */
82
+ async createSubscription(_params) {
83
+ throw new Error('Ozow does not support recurring subscriptions. EFT-based provider; use card-based provider for subscriptions.');
89
84
  }
90
85
  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!');
86
+ const url = `${this.baseUrl}/GetTransactionByReference?siteCode=${this.siteCode}&transactionReference=${encodeURIComponent(id)}`;
87
+ const response = await (0, fetch_1.timedFetchOrThrow)(url, {
88
+ method: 'GET',
89
+ headers: {
90
+ 'ApiKey': this.apiKey,
91
+ },
92
+ });
93
+ const data = (await response.json());
94
+ if (!Array.isArray(data) || data.length === 0) {
95
+ throw new Error('Transaction not found');
96
+ }
97
+ const transaction = data[0];
98
+ const status = this.mapOzowStatus(transaction.status);
99
+ return {
100
+ id: transaction.transactionId || id,
101
+ checkoutUrl: '',
102
+ status,
103
+ amount: parseFloat(transaction.amount || '0'),
104
+ currency: transaction.currencyCode || 'ZAR',
105
+ reference: transaction.transactionReference || id,
106
+ provider: 'ozow',
107
+ createdAt: transaction.createdDate || new Date().toISOString(),
108
+ raw: transaction,
109
+ };
96
110
  }
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!');
111
+ /**
112
+ * Ozow refunds require manual processing via merchant portal
113
+ * No public API endpoint for instant-EFT refunds
114
+ */
115
+ async refund(_params) {
116
+ throw new Error('Ozow refunds must be processed manually via merchant.ozow.com — no API support.');
109
117
  }
110
- // ==================== Webhooks ====================
111
118
  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
- // }
119
+ let event;
120
+ if (typeof body === 'string') {
121
+ const params = new URLSearchParams(body);
122
+ event = Object.fromEntries(params.entries());
123
+ }
124
+ else {
125
+ event = body;
126
+ }
131
127
  const ozowStatus = event.Status;
132
128
  const status = this.mapOzowStatus(ozowStatus);
133
129
  const eventType = this.mapOzowEventType(ozowStatus);
@@ -146,19 +142,53 @@ class OzowProvider extends base_1.PaymentProvider {
146
142
  raw: event,
147
143
  };
148
144
  }
145
+ /**
146
+ * Verify Ozow ITN webhook signature
147
+ * Note: Ozow ITN has no timestamp, so no replay protection possible
148
+ * Caller should validate idempotency by TransactionId
149
+ */
149
150
  verifyWebhook(body, _headers) {
150
- const event = typeof body === 'string' ? JSON.parse(body) : body;
151
+ if (!this.apiKey) {
152
+ return false;
153
+ }
154
+ let event;
155
+ if (typeof body === 'string') {
156
+ const params = new URLSearchParams(body);
157
+ event = Object.fromEntries(params.entries());
158
+ }
159
+ else {
160
+ event = body;
161
+ }
151
162
  const receivedHash = event.Hash;
152
163
  if (!receivedHash) {
153
- // No hash provided
154
- return true;
164
+ return false;
155
165
  }
156
- // TODO: Verify Ozow SHA512 hash
157
- // expectedHash = SHA512(SiteCode + TransactionId + TransactionReference + Amount + Status + Optional1 + Optional2 + Optional3 + Optional4 + Optional5 + CurrencyCode + IsTest + StatusMessage + PrivateKey)
158
166
  const expectedHash = this.generateWebhookHash(event);
159
- return receivedHash === expectedHash;
167
+ try {
168
+ const receivedBuffer = Buffer.from(receivedHash);
169
+ const expectedBuffer = Buffer.from(expectedHash);
170
+ if (receivedBuffer.length !== expectedBuffer.length) {
171
+ return false;
172
+ }
173
+ return crypto_1.default.timingSafeEqual(receivedBuffer, expectedBuffer);
174
+ }
175
+ catch {
176
+ return false;
177
+ }
178
+ }
179
+ // ==================== Capabilities ====================
180
+ getCapabilities() {
181
+ return {
182
+ fees: {
183
+ fixed: 0,
184
+ percent: 1.5,
185
+ currency: 'ZAR',
186
+ },
187
+ currencies: this.supportedCurrencies,
188
+ country: 'ZA',
189
+ avgLatencyMs: 800,
190
+ };
160
191
  }
161
- // ==================== Helpers ====================
162
192
  mapOzowStatus(ozowStatus) {
163
193
  const statusMap = {
164
194
  Complete: 'completed',
@@ -166,6 +196,7 @@ class OzowProvider extends base_1.PaymentProvider {
166
196
  Error: 'failed',
167
197
  Abandoned: 'cancelled',
168
198
  PendingInvestigation: 'pending',
199
+ Pending: 'pending',
169
200
  };
170
201
  return statusMap[ozowStatus] || 'pending';
171
202
  }
@@ -178,26 +209,39 @@ class OzowProvider extends base_1.PaymentProvider {
178
209
  };
179
210
  return typeMap[ozowStatus] || 'payment.pending';
180
211
  }
212
+ generateHash(fields, fieldOrder) {
213
+ const concat = fieldOrder.map(k => fields[k] ?? '').join('') + this.apiKey;
214
+ return crypto_1.default.createHash('sha512').update(concat.toLowerCase()).digest('hex');
215
+ }
181
216
  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,
217
+ const fieldOrder = [
218
+ 'SiteCode',
219
+ 'TransactionId',
220
+ 'TransactionReference',
221
+ 'Amount',
222
+ 'Status',
223
+ 'Optional1',
224
+ 'Optional2',
225
+ 'Optional3',
226
+ 'Optional4',
227
+ 'Optional5',
228
+ 'CurrencyCode',
229
+ 'IsTest',
230
+ 'StatusMessage',
198
231
  ];
199
- const concatenated = fields.join('');
200
- return crypto_1.default.createHash('sha512').update(concatenated, 'utf8').digest('hex').toLowerCase();
232
+ const fields = {};
233
+ fieldOrder.forEach(key => {
234
+ fields[key] = event[key] || '';
235
+ });
236
+ const concat = fieldOrder.map(k => fields[k]).join('') + this.apiKey;
237
+ return crypto_1.default.createHash('sha512').update(concat.toLowerCase()).digest('hex');
238
+ }
239
+ sanitizeBankReference(reference) {
240
+ const sanitized = reference.replace(/[^A-Za-z0-9]/g, '').substring(0, 20);
241
+ if (sanitized.length === 0) {
242
+ throw new Error('Ozow BankReference invalid: must contain at least 1 alphanumeric char');
243
+ }
244
+ return sanitized;
201
245
  }
202
246
  }
203
247
  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 {};