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.
- package/README.md +87 -7
- package/dist/circuit-breaker-store.d.ts +27 -0
- package/dist/circuit-breaker-store.js +25 -0
- package/dist/circuit-breaker.d.ts +30 -0
- package/dist/circuit-breaker.js +86 -0
- package/dist/crypto/base.d.ts +15 -0
- package/dist/crypto/base.js +24 -0
- package/dist/crypto/index.d.ts +35 -0
- package/dist/crypto/index.js +95 -0
- package/dist/crypto/mock.d.ts +15 -0
- package/dist/crypto/mock.js +112 -0
- package/dist/crypto/moonpay.d.ts +33 -0
- package/dist/crypto/moonpay.js +261 -0
- package/dist/crypto/router.d.ts +36 -0
- package/dist/crypto/router.js +287 -0
- package/dist/crypto/types.d.ts +89 -0
- package/dist/crypto/types.js +5 -0
- package/dist/crypto/yellowcard.d.ts +56 -0
- package/dist/crypto/yellowcard.js +311 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +60 -3
- package/dist/providers/base.d.ts +5 -0
- package/dist/providers/flutterwave.d.ts +36 -0
- package/dist/providers/flutterwave.js +339 -0
- package/dist/providers/ozow.d.ts +20 -2
- package/dist/providers/ozow.js +158 -114
- package/dist/providers/payfast.d.ts +40 -0
- package/dist/providers/payfast.js +352 -0
- package/dist/providers/paystack.d.ts +37 -0
- package/dist/providers/paystack.js +336 -0
- package/dist/providers/peach.d.ts +50 -0
- package/dist/providers/peach.js +302 -0
- package/dist/providers/softycomp.d.ts +106 -0
- package/dist/providers/softycomp.js +229 -10
- package/dist/providers/stripe.d.ts +38 -0
- package/dist/providers/stripe.js +367 -0
- package/dist/providers/yoco.d.ts +12 -0
- package/dist/providers/yoco.js +148 -61
- package/dist/router.d.ts +33 -0
- package/dist/router.js +282 -0
- package/dist/routing-types.d.ts +39 -0
- package/dist/routing-types.js +14 -0
- package/dist/stores/redis.d.ts +30 -0
- package/dist/stores/redis.js +42 -0
- package/dist/strategies.d.ts +18 -0
- package/dist/strategies.js +44 -0
- package/dist/types.d.ts +4 -2
- package/dist/utils/fetch.d.ts +24 -0
- package/dist/utils/fetch.js +74 -0
- package/package.json +7 -4
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PayFast payment provider
|
|
4
|
+
* South African payment gateway (hosted checkout + ITN webhooks)
|
|
5
|
+
* @see https://developers.payfast.co.za
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.PayFastProvider = void 0;
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
+
const base_1 = require("./base");
|
|
14
|
+
const fetch_1 = require("../utils/fetch");
|
|
15
|
+
class PayFastProvider extends base_1.PaymentProvider {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super();
|
|
18
|
+
this.name = 'payfast';
|
|
19
|
+
this.supportedCurrencies = ['ZAR'];
|
|
20
|
+
this.merchantId = config.merchantId;
|
|
21
|
+
this.merchantKey = config.merchantKey;
|
|
22
|
+
this.passphrase = config.passphrase || config.webhookSecret;
|
|
23
|
+
this.sandbox = config.sandbox;
|
|
24
|
+
if (this.sandbox) {
|
|
25
|
+
this.checkoutBaseUrl = 'https://sandbox.payfast.co.za/eng/process';
|
|
26
|
+
this.apiBaseUrl = 'https://api.payfast.co.za';
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.checkoutBaseUrl = 'https://www.payfast.co.za/eng/process';
|
|
30
|
+
this.apiBaseUrl = 'https://api.payfast.co.za';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async createPayment(params) {
|
|
34
|
+
this.validateCurrency(params.currency);
|
|
35
|
+
const nameParts = params.customer.name.split(' ');
|
|
36
|
+
const firstName = nameParts[0] || '';
|
|
37
|
+
const lastName = nameParts.slice(1).join(' ') || '';
|
|
38
|
+
const paymentParams = {
|
|
39
|
+
merchant_id: this.merchantId,
|
|
40
|
+
merchant_key: this.merchantKey,
|
|
41
|
+
return_url: params.urls.success,
|
|
42
|
+
cancel_url: params.urls.cancel,
|
|
43
|
+
notify_url: params.urls.webhook,
|
|
44
|
+
name_first: firstName,
|
|
45
|
+
name_last: lastName,
|
|
46
|
+
email_address: params.customer.email,
|
|
47
|
+
m_payment_id: params.reference,
|
|
48
|
+
amount: params.amount.toFixed(2),
|
|
49
|
+
item_name: params.description || params.reference,
|
|
50
|
+
item_description: params.description || '',
|
|
51
|
+
};
|
|
52
|
+
if (params.metadata) {
|
|
53
|
+
const metaKeys = Object.keys(params.metadata).slice(0, 5);
|
|
54
|
+
metaKeys.forEach((key, idx) => {
|
|
55
|
+
paymentParams[`custom_str${idx + 1}`] = String(params.metadata[key]);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const signature = this.generateSignature(paymentParams);
|
|
59
|
+
const queryParams = this.buildQueryString(paymentParams);
|
|
60
|
+
const checkoutUrl = `${this.checkoutBaseUrl}?${queryParams}&signature=${signature}`;
|
|
61
|
+
return {
|
|
62
|
+
id: params.reference,
|
|
63
|
+
checkoutUrl,
|
|
64
|
+
status: 'pending',
|
|
65
|
+
amount: params.amount,
|
|
66
|
+
currency: params.currency,
|
|
67
|
+
reference: params.reference,
|
|
68
|
+
provider: 'payfast',
|
|
69
|
+
createdAt: new Date().toISOString(),
|
|
70
|
+
expiresAt: new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async createSubscription(params) {
|
|
74
|
+
this.validateCurrency(params.currency);
|
|
75
|
+
const frequencyMap = {
|
|
76
|
+
monthly: 3,
|
|
77
|
+
yearly: 6,
|
|
78
|
+
};
|
|
79
|
+
const frequency = frequencyMap[params.interval];
|
|
80
|
+
if (!frequency) {
|
|
81
|
+
throw new Error(`PayFast does not support ${params.interval} subscriptions. Use monthly or yearly.`);
|
|
82
|
+
}
|
|
83
|
+
let billingDate;
|
|
84
|
+
if (params.startDate) {
|
|
85
|
+
this.validateFutureDate(params.startDate, 'startDate');
|
|
86
|
+
billingDate = new Date(params.startDate);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
billingDate = new Date();
|
|
90
|
+
billingDate.setDate(billingDate.getDate() + 1);
|
|
91
|
+
}
|
|
92
|
+
const nameParts = params.customer.name.split(' ');
|
|
93
|
+
const firstName = nameParts[0] || '';
|
|
94
|
+
const lastName = nameParts.slice(1).join(' ') || '';
|
|
95
|
+
const subscriptionParams = {
|
|
96
|
+
merchant_id: this.merchantId,
|
|
97
|
+
merchant_key: this.merchantKey,
|
|
98
|
+
return_url: params.urls.success,
|
|
99
|
+
cancel_url: params.urls.cancel,
|
|
100
|
+
notify_url: params.urls.webhook,
|
|
101
|
+
name_first: firstName,
|
|
102
|
+
name_last: lastName,
|
|
103
|
+
email_address: params.customer.email,
|
|
104
|
+
m_payment_id: params.reference,
|
|
105
|
+
amount: params.amount.toFixed(2),
|
|
106
|
+
item_name: params.description || params.reference,
|
|
107
|
+
item_description: params.description || '',
|
|
108
|
+
subscription_type: '1',
|
|
109
|
+
billing_date: billingDate.toISOString().split('T')[0],
|
|
110
|
+
recurring_amount: params.amount.toFixed(2),
|
|
111
|
+
frequency: String(frequency),
|
|
112
|
+
cycles: '0',
|
|
113
|
+
};
|
|
114
|
+
if (params.metadata) {
|
|
115
|
+
const metaKeys = Object.keys(params.metadata).slice(0, 5);
|
|
116
|
+
metaKeys.forEach((key, idx) => {
|
|
117
|
+
subscriptionParams[`custom_str${idx + 1}`] = String(params.metadata[key]);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const signature = this.generateSignature(subscriptionParams);
|
|
121
|
+
const queryParams = this.buildQueryString(subscriptionParams);
|
|
122
|
+
const checkoutUrl = `${this.checkoutBaseUrl}?${queryParams}&signature=${signature}`;
|
|
123
|
+
return {
|
|
124
|
+
id: params.reference,
|
|
125
|
+
checkoutUrl,
|
|
126
|
+
status: 'pending',
|
|
127
|
+
amount: params.amount,
|
|
128
|
+
currency: params.currency,
|
|
129
|
+
interval: params.interval,
|
|
130
|
+
reference: params.reference,
|
|
131
|
+
provider: 'payfast',
|
|
132
|
+
startsAt: billingDate.toISOString(),
|
|
133
|
+
createdAt: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async getPayment(id) {
|
|
137
|
+
// TODO: verify against latest PayFast Query API docs
|
|
138
|
+
const timestamp = new Date().toISOString();
|
|
139
|
+
const headers = {
|
|
140
|
+
'merchant-id': this.merchantId,
|
|
141
|
+
'version': 'v1',
|
|
142
|
+
'timestamp': timestamp,
|
|
143
|
+
};
|
|
144
|
+
headers['signature'] = this.generateApiSignature(headers);
|
|
145
|
+
const testingParam = this.sandbox ? '?testing=true' : '';
|
|
146
|
+
const url = `${this.apiBaseUrl}/query/fetch${testingParam}`;
|
|
147
|
+
const response = await (0, fetch_1.timedFetchOrThrow)(url, {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
...headers,
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
m_payment_id: id,
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
const data = (await response.json());
|
|
158
|
+
const status = this.mapPayFastStatus(data.status);
|
|
159
|
+
return {
|
|
160
|
+
id: data.pf_payment_id || id,
|
|
161
|
+
checkoutUrl: '',
|
|
162
|
+
status,
|
|
163
|
+
amount: parseFloat(data.amount_gross || '0'),
|
|
164
|
+
currency: 'ZAR',
|
|
165
|
+
reference: data.m_payment_id || id,
|
|
166
|
+
provider: 'payfast',
|
|
167
|
+
createdAt: data.created_at || new Date().toISOString(),
|
|
168
|
+
raw: data,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async refund(params) {
|
|
172
|
+
// TODO: verify against latest PayFast Query API docs
|
|
173
|
+
const timestamp = new Date().toISOString();
|
|
174
|
+
const headers = {
|
|
175
|
+
'merchant-id': this.merchantId,
|
|
176
|
+
'version': 'v1',
|
|
177
|
+
'timestamp': timestamp,
|
|
178
|
+
};
|
|
179
|
+
headers['signature'] = this.generateApiSignature(headers);
|
|
180
|
+
const testingParam = this.sandbox ? '?testing=true' : '';
|
|
181
|
+
const url = `${this.apiBaseUrl}/refunds/${params.paymentId}${testingParam}`;
|
|
182
|
+
const refundData = {
|
|
183
|
+
merchant_reference: params.paymentId,
|
|
184
|
+
};
|
|
185
|
+
if (params.amount !== undefined) {
|
|
186
|
+
refundData.amount = params.amount.toFixed(2);
|
|
187
|
+
}
|
|
188
|
+
if (params.reason) {
|
|
189
|
+
refundData.reason = params.reason;
|
|
190
|
+
}
|
|
191
|
+
const response = await fetch(url, {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
...headers,
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify(refundData),
|
|
198
|
+
});
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
const errorText = await response.text();
|
|
201
|
+
throw new Error(`PayFast refund failed: ${response.status} - ${errorText}`);
|
|
202
|
+
}
|
|
203
|
+
const data = (await response.json());
|
|
204
|
+
return {
|
|
205
|
+
id: data.refund_id || `refund_${params.paymentId}_${Date.now()}`,
|
|
206
|
+
status: data.status === 'success' ? 'completed' : 'pending',
|
|
207
|
+
amount: params.amount || 0,
|
|
208
|
+
currency: 'ZAR',
|
|
209
|
+
paymentId: params.paymentId,
|
|
210
|
+
createdAt: new Date().toISOString(),
|
|
211
|
+
raw: data,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
parseWebhook(body, _headers) {
|
|
215
|
+
let event;
|
|
216
|
+
if (typeof body === 'string') {
|
|
217
|
+
const params = new URLSearchParams(body);
|
|
218
|
+
event = Object.fromEntries(params.entries());
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
event = body;
|
|
222
|
+
}
|
|
223
|
+
const paymentStatus = event.payment_status || 'PENDING';
|
|
224
|
+
const status = this.mapPayFastStatus(paymentStatus);
|
|
225
|
+
const eventType = this.mapEventType(paymentStatus);
|
|
226
|
+
return {
|
|
227
|
+
type: eventType,
|
|
228
|
+
payment: {
|
|
229
|
+
id: event.pf_payment_id || event.m_payment_id,
|
|
230
|
+
checkoutUrl: '',
|
|
231
|
+
status,
|
|
232
|
+
amount: parseFloat(event.amount_gross || '0'),
|
|
233
|
+
currency: 'ZAR',
|
|
234
|
+
reference: event.m_payment_id,
|
|
235
|
+
provider: 'payfast',
|
|
236
|
+
createdAt: new Date().toISOString(),
|
|
237
|
+
},
|
|
238
|
+
raw: event,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
verifyWebhook(body, _headers) {
|
|
242
|
+
if (!this.passphrase) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
let event;
|
|
246
|
+
let params;
|
|
247
|
+
if (typeof body === 'string') {
|
|
248
|
+
const urlParams = new URLSearchParams(body);
|
|
249
|
+
event = Object.fromEntries(urlParams.entries());
|
|
250
|
+
params = Array.from(urlParams.entries());
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
event = body;
|
|
254
|
+
params = Object.entries(body);
|
|
255
|
+
}
|
|
256
|
+
const receivedSignature = event.signature;
|
|
257
|
+
if (!receivedSignature) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const filteredParams = params
|
|
261
|
+
.filter(([key]) => key !== 'signature')
|
|
262
|
+
.map(([key, value]) => `${key}=${this.pfEncode(String(value))}`)
|
|
263
|
+
.join('&');
|
|
264
|
+
const signatureString = `${filteredParams}&passphrase=${this.pfEncode(this.passphrase)}`;
|
|
265
|
+
const expectedSignature = crypto_1.default.createHash('md5').update(signatureString).digest('hex');
|
|
266
|
+
try {
|
|
267
|
+
const receivedBuffer = Buffer.from(receivedSignature);
|
|
268
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
269
|
+
if (receivedBuffer.length !== expectedBuffer.length) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return crypto_1.default.timingSafeEqual(receivedBuffer, expectedBuffer);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
getCapabilities() {
|
|
279
|
+
return {
|
|
280
|
+
fees: {
|
|
281
|
+
fixed: 2.0,
|
|
282
|
+
percent: 2.9,
|
|
283
|
+
currency: 'ZAR',
|
|
284
|
+
},
|
|
285
|
+
currencies: this.supportedCurrencies,
|
|
286
|
+
country: 'ZA',
|
|
287
|
+
avgLatencyMs: 600,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
pfEncode(value) {
|
|
291
|
+
return encodeURIComponent(value).replace(/%20/g, '+');
|
|
292
|
+
}
|
|
293
|
+
generateSignature(params) {
|
|
294
|
+
const filtered = Object.entries(params)
|
|
295
|
+
.filter(([_, v]) => v !== '' && v !== undefined && v !== null)
|
|
296
|
+
.map(([k, v]) => `${k}=${this.pfEncode(String(v))}`)
|
|
297
|
+
.join('&');
|
|
298
|
+
const signatureString = this.passphrase
|
|
299
|
+
? `${filtered}&passphrase=${this.pfEncode(this.passphrase)}`
|
|
300
|
+
: filtered;
|
|
301
|
+
return crypto_1.default.createHash('md5').update(signatureString).digest('hex');
|
|
302
|
+
}
|
|
303
|
+
buildQueryString(params) {
|
|
304
|
+
return Object.entries(params)
|
|
305
|
+
.filter(([_, v]) => v !== '' && v !== undefined && v !== null)
|
|
306
|
+
.map(([k, v]) => `${k}=${this.pfEncode(String(v))}`)
|
|
307
|
+
.join('&');
|
|
308
|
+
}
|
|
309
|
+
generateApiSignature(headers) {
|
|
310
|
+
const sortedKeys = Object.keys(headers)
|
|
311
|
+
.filter(k => k !== 'signature')
|
|
312
|
+
.sort();
|
|
313
|
+
const paramString = sortedKeys
|
|
314
|
+
.map(k => `${k}=${encodeURIComponent(headers[k])}`)
|
|
315
|
+
.join('&');
|
|
316
|
+
const signatureString = this.passphrase
|
|
317
|
+
? `${paramString}&passphrase=${encodeURIComponent(this.passphrase)}`
|
|
318
|
+
: paramString;
|
|
319
|
+
return crypto_1.default.createHash('md5').update(signatureString).digest('hex');
|
|
320
|
+
}
|
|
321
|
+
mapPayFastStatus(status) {
|
|
322
|
+
const upperStatus = String(status).toUpperCase();
|
|
323
|
+
switch (upperStatus) {
|
|
324
|
+
case 'COMPLETE':
|
|
325
|
+
return 'completed';
|
|
326
|
+
case 'FAILED':
|
|
327
|
+
return 'failed';
|
|
328
|
+
case 'CANCELLED':
|
|
329
|
+
return 'cancelled';
|
|
330
|
+
case 'PENDING':
|
|
331
|
+
return 'pending';
|
|
332
|
+
default:
|
|
333
|
+
return 'pending';
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
mapEventType(status) {
|
|
337
|
+
const upperStatus = String(status).toUpperCase();
|
|
338
|
+
switch (upperStatus) {
|
|
339
|
+
case 'COMPLETE':
|
|
340
|
+
return 'payment.completed';
|
|
341
|
+
case 'FAILED':
|
|
342
|
+
return 'payment.failed';
|
|
343
|
+
case 'CANCELLED':
|
|
344
|
+
return 'payment.cancelled';
|
|
345
|
+
case 'PENDING':
|
|
346
|
+
return 'payment.pending';
|
|
347
|
+
default:
|
|
348
|
+
return 'payment.pending';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
exports.PayFastProvider = PayFastProvider;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayStack payment provider
|
|
3
|
+
* Leading payment gateway for Africa (Nigeria, Ghana, South Africa, Kenya)
|
|
4
|
+
* @see https://paystack.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 PayStackConfig {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
webhookSecret?: string;
|
|
12
|
+
sandbox?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class PayStackProvider extends PaymentProvider {
|
|
15
|
+
readonly name = "paystack";
|
|
16
|
+
readonly supportedCurrencies: string[];
|
|
17
|
+
private apiKey;
|
|
18
|
+
private webhookSecret?;
|
|
19
|
+
private sandbox;
|
|
20
|
+
private baseUrl;
|
|
21
|
+
constructor(config: PayStackConfig);
|
|
22
|
+
private apiRequest;
|
|
23
|
+
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
24
|
+
createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
25
|
+
getPayment(id: string): Promise<PaymentResult>;
|
|
26
|
+
refund(params: RefundParams): Promise<RefundResult>;
|
|
27
|
+
parseWebhook(body: any, _headers?: any): WebhookEvent;
|
|
28
|
+
/**
|
|
29
|
+
* Verify webhook signature using PayStack's HMAC-SHA512 scheme.
|
|
30
|
+
*
|
|
31
|
+
* CRITICAL: body must be the raw string or Buffer from the webhook request.
|
|
32
|
+
* Passing a parsed JSON object will cause signature verification to fail.
|
|
33
|
+
*/
|
|
34
|
+
verifyWebhook(body: string | Buffer, headers?: any): boolean;
|
|
35
|
+
getCapabilities(): ProviderCapabilities;
|
|
36
|
+
}
|
|
37
|
+
export {};
|