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
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ /**
3
+ * Flutterwave payment provider
4
+ * Leading payment gateway for Africa with global reach
5
+ * @see https://developer.flutterwave.com/docs
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.FlutterwaveProvider = void 0;
42
+ const crypto = __importStar(require("crypto"));
43
+ const base_1 = require("./base");
44
+ const fetch_1 = require("../utils/fetch");
45
+ class FlutterwaveProvider extends base_1.PaymentProvider {
46
+ constructor(config) {
47
+ super();
48
+ this.name = 'flutterwave';
49
+ this.supportedCurrencies = ['NGN', 'GHS', 'KES', 'UGX', 'ZAR', 'USD', 'EUR', 'GBP'];
50
+ this.baseUrl = 'https://api.flutterwave.com/v3';
51
+ this.apiKey = config.apiKey;
52
+ this.webhookSecret = config.webhookSecret;
53
+ this.sandbox = config.sandbox ?? this.apiKey.startsWith('FLWSECK_TEST-');
54
+ }
55
+ async apiRequest(method, path, data) {
56
+ const url = `${this.baseUrl}${path}`;
57
+ const response = await (0, fetch_1.timedFetch)(url, {
58
+ method,
59
+ headers: {
60
+ Authorization: `Bearer ${this.apiKey}`,
61
+ 'Content-Type': 'application/json',
62
+ },
63
+ body: data ? JSON.stringify(data) : undefined,
64
+ });
65
+ const json = await response.json();
66
+ if (!response.ok || json.status !== 'success') {
67
+ throw new Error(json.message || `Flutterwave API error (${method} ${path}): ${response.status}`);
68
+ }
69
+ return json;
70
+ }
71
+ async createPayment(params) {
72
+ this.validateCurrency(params.currency);
73
+ const metadata = {
74
+ reference: params.reference,
75
+ };
76
+ if (params.metadata) {
77
+ for (const [key, value] of Object.entries(params.metadata)) {
78
+ metadata[key] = value;
79
+ }
80
+ }
81
+ const requestBody = {
82
+ tx_ref: params.reference,
83
+ amount: params.amount.toFixed(2),
84
+ currency: params.currency,
85
+ redirect_url: params.urls.success,
86
+ customer: {
87
+ email: params.customer.email,
88
+ phonenumber: params.customer.phone,
89
+ name: params.customer.name,
90
+ },
91
+ customizations: {
92
+ title: params.description || 'Payment',
93
+ description: params.description,
94
+ },
95
+ meta: metadata,
96
+ };
97
+ const response = await this.apiRequest('POST', '/payments', requestBody);
98
+ return {
99
+ id: params.reference,
100
+ checkoutUrl: response.data.link,
101
+ status: 'pending',
102
+ amount: params.amount,
103
+ currency: params.currency.toUpperCase(),
104
+ reference: params.reference,
105
+ provider: 'flutterwave',
106
+ createdAt: new Date().toISOString(),
107
+ raw: response,
108
+ };
109
+ }
110
+ async createSubscription(params) {
111
+ this.validateCurrency(params.currency);
112
+ const intervalMap = {
113
+ weekly: 'weekly',
114
+ monthly: 'monthly',
115
+ yearly: 'yearly',
116
+ };
117
+ const flutterwaveInterval = intervalMap[params.interval];
118
+ const planData = {
119
+ amount: Math.round(params.amount),
120
+ name: params.description || params.reference,
121
+ interval: flutterwaveInterval,
122
+ currency: params.currency,
123
+ duration: 0,
124
+ };
125
+ const planResponse = await this.apiRequest('POST', '/payment-plans', planData);
126
+ const planId = planResponse.data.id;
127
+ const metadata = {
128
+ reference: params.reference,
129
+ };
130
+ if (params.metadata) {
131
+ for (const [key, value] of Object.entries(params.metadata)) {
132
+ metadata[key] = value;
133
+ }
134
+ }
135
+ const paymentData = {
136
+ tx_ref: params.reference,
137
+ amount: params.amount.toFixed(2),
138
+ currency: params.currency,
139
+ redirect_url: params.urls.success,
140
+ payment_plan: planId,
141
+ customer: {
142
+ email: params.customer.email,
143
+ phonenumber: params.customer.phone,
144
+ name: params.customer.name,
145
+ },
146
+ customizations: {
147
+ title: params.description || params.reference,
148
+ description: params.description,
149
+ },
150
+ meta: metadata,
151
+ };
152
+ const paymentResponse = await this.apiRequest('POST', '/payments', paymentData);
153
+ return {
154
+ id: params.reference,
155
+ checkoutUrl: paymentResponse.data.link,
156
+ status: 'pending',
157
+ amount: params.amount,
158
+ currency: params.currency.toUpperCase(),
159
+ interval: params.interval,
160
+ reference: params.reference,
161
+ provider: 'flutterwave',
162
+ startsAt: params.startDate,
163
+ createdAt: new Date().toISOString(),
164
+ raw: paymentResponse,
165
+ };
166
+ }
167
+ async getPayment(id) {
168
+ const response = await this.apiRequest('GET', `/transactions/verify_by_reference?tx_ref=${id}`);
169
+ const data = response.data;
170
+ const currency = (data.currency || 'NGN').toUpperCase();
171
+ let status = 'pending';
172
+ if (data.status === 'successful') {
173
+ status = 'completed';
174
+ }
175
+ else if (data.status === 'failed') {
176
+ status = 'failed';
177
+ }
178
+ return {
179
+ id: data.tx_ref || id,
180
+ checkoutUrl: '',
181
+ status,
182
+ amount: data.amount,
183
+ currency,
184
+ reference: data.tx_ref || id,
185
+ provider: 'flutterwave',
186
+ createdAt: new Date(data.created_at || Date.now()).toISOString(),
187
+ raw: response,
188
+ };
189
+ }
190
+ async refund(params) {
191
+ let flwId;
192
+ if (/^\d+$/.test(params.paymentId)) {
193
+ flwId = parseInt(params.paymentId, 10);
194
+ }
195
+ else {
196
+ const verifyResponse = await this.apiRequest('GET', `/transactions/verify_by_reference?tx_ref=${params.paymentId}`);
197
+ flwId = verifyResponse.data.id;
198
+ if (!flwId) {
199
+ throw new Error('Transaction not found or has no Flutterwave ID');
200
+ }
201
+ }
202
+ const refundData = {};
203
+ if (params.amount !== undefined) {
204
+ refundData.amount = params.amount.toFixed(2);
205
+ }
206
+ const response = await this.apiRequest('POST', `/transactions/${flwId}/refund`, refundData);
207
+ const data = response.data;
208
+ const currency = (data.currency || 'NGN').toUpperCase();
209
+ let status = 'pending';
210
+ if (data.status === 'completed') {
211
+ status = 'completed';
212
+ }
213
+ else if (data.status === 'failed') {
214
+ status = 'failed';
215
+ }
216
+ return {
217
+ id: data.id?.toString() || flwId.toString(),
218
+ status,
219
+ amount: data.amount || params.amount || 0,
220
+ currency,
221
+ paymentId: params.paymentId,
222
+ createdAt: new Date(data.created_at || Date.now()).toISOString(),
223
+ raw: response,
224
+ };
225
+ }
226
+ parseWebhook(body, _headers) {
227
+ const event = typeof body === 'string' ? JSON.parse(body) : body;
228
+ const typeMap = {
229
+ 'charge.completed': 'payment.completed',
230
+ 'transfer.completed': 'payment.completed',
231
+ 'subscription.cancelled': 'subscription.cancelled',
232
+ 'refund.completed': 'refund.completed',
233
+ };
234
+ let eventType = 'payment.pending';
235
+ if (event.event === 'charge.completed' && event.data?.status === 'successful') {
236
+ eventType = 'payment.completed';
237
+ }
238
+ else if (event.event === 'charge.completed' && event.data?.status === 'failed') {
239
+ eventType = 'payment.failed';
240
+ }
241
+ else {
242
+ eventType = typeMap[event.event] || 'payment.pending';
243
+ }
244
+ const data = event.data || {};
245
+ let payment;
246
+ let subscription;
247
+ let refund;
248
+ if (event.event === 'charge.completed' || event.event === 'transfer.completed') {
249
+ const currency = (data.currency || 'NGN').toUpperCase();
250
+ let status = 'pending';
251
+ if (data.status === 'successful') {
252
+ status = 'completed';
253
+ }
254
+ else if (data.status === 'failed') {
255
+ status = 'failed';
256
+ }
257
+ payment = {
258
+ id: data.id?.toString() || '',
259
+ checkoutUrl: '',
260
+ status,
261
+ amount: data.amount || 0,
262
+ currency,
263
+ reference: data.tx_ref || '',
264
+ provider: 'flutterwave',
265
+ createdAt: new Date(data.created_at || Date.now()).toISOString(),
266
+ };
267
+ }
268
+ else if (event.event === 'subscription.cancelled') {
269
+ const currency = (data.currency || 'NGN').toUpperCase();
270
+ subscription = {
271
+ id: data.id?.toString() || '',
272
+ checkoutUrl: '',
273
+ status: 'cancelled',
274
+ amount: data.amount || 0,
275
+ currency,
276
+ interval: 'monthly',
277
+ reference: data.tx_ref || '',
278
+ provider: 'flutterwave',
279
+ createdAt: new Date(data.created_at || Date.now()).toISOString(),
280
+ };
281
+ }
282
+ else if (event.event === 'refund.completed') {
283
+ const currency = (data.currency || 'NGN').toUpperCase();
284
+ refund = {
285
+ id: data.id?.toString() || '',
286
+ status: 'completed',
287
+ amount: data.amount || 0,
288
+ currency,
289
+ paymentId: data.tx_ref || '',
290
+ createdAt: new Date(data.created_at || Date.now()).toISOString(),
291
+ };
292
+ }
293
+ return {
294
+ type: eventType,
295
+ payment,
296
+ subscription,
297
+ refund,
298
+ raw: event,
299
+ };
300
+ }
301
+ /**
302
+ * Verify webhook signature using Flutterwave's scheme.
303
+ * Flutterwave sends the configured webhook secret hash as the verif-hash header.
304
+ * This is a simple equality check, not HMAC.
305
+ */
306
+ verifyWebhook(body, headers) {
307
+ if (!this.webhookSecret) {
308
+ return false;
309
+ }
310
+ const receivedHash = headers?.['verif-hash'] || headers?.['Verif-Hash'];
311
+ if (!receivedHash) {
312
+ return false;
313
+ }
314
+ try {
315
+ const secretBuffer = Buffer.from(this.webhookSecret, 'utf8');
316
+ const receivedBuffer = Buffer.from(receivedHash, 'utf8');
317
+ if (secretBuffer.length !== receivedBuffer.length) {
318
+ return false;
319
+ }
320
+ return crypto.timingSafeEqual(secretBuffer, receivedBuffer);
321
+ }
322
+ catch {
323
+ return false;
324
+ }
325
+ }
326
+ getCapabilities() {
327
+ return {
328
+ fees: {
329
+ fixed: 0,
330
+ percent: 1.4,
331
+ currency: 'NGN',
332
+ },
333
+ currencies: this.supportedCurrencies,
334
+ country: 'NG',
335
+ avgLatencyMs: 700,
336
+ };
337
+ }
338
+ }
339
+ exports.FlutterwaveProvider = FlutterwaveProvider;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { PaymentProvider } from './base';
7
7
  import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
8
+ import { ProviderCapabilities } from '../routing-types';
8
9
  interface OzowConfig {
9
10
  apiKey: string;
10
11
  siteCode: string;
@@ -19,15 +20,32 @@ export declare class OzowProvider extends PaymentProvider {
19
20
  private privateKey;
20
21
  private sandbox;
21
22
  private baseUrl;
23
+ private redirectBaseUrl;
22
24
  constructor(config: OzowConfig);
23
25
  createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
24
- createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
26
+ /**
27
+ * Ozow does not support recurring subscriptions (EFT instant-payment provider)
28
+ * Use a card-based provider for subscription functionality
29
+ */
30
+ createSubscription(_params: CreateSubscriptionParams): Promise<SubscriptionResult>;
25
31
  getPayment(id: string): Promise<PaymentResult>;
26
- refund(params: RefundParams): Promise<RefundResult>;
32
+ /**
33
+ * Ozow refunds require manual processing via merchant portal
34
+ * No public API endpoint for instant-EFT refunds
35
+ */
36
+ refund(_params: RefundParams): Promise<RefundResult>;
27
37
  parseWebhook(body: any, _headers?: any): WebhookEvent;
38
+ /**
39
+ * Verify Ozow ITN webhook signature
40
+ * Note: Ozow ITN has no timestamp, so no replay protection possible
41
+ * Caller should validate idempotency by TransactionId
42
+ */
28
43
  verifyWebhook(body: any, _headers?: any): boolean;
44
+ getCapabilities(): ProviderCapabilities;
29
45
  private mapOzowStatus;
30
46
  private mapOzowEventType;
47
+ private generateHash;
31
48
  private generateWebhookHash;
49
+ private sanitizeBankReference;
32
50
  }
33
51
  export {};