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.
- 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 +251 -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 +310 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +59 -3
- package/dist/providers/base.d.ts +5 -0
- package/dist/providers/flutterwave.d.ts +36 -0
- package/dist/providers/flutterwave.js +338 -0
- package/dist/providers/ozow.d.ts +20 -2
- package/dist/providers/ozow.js +161 -114
- package/dist/providers/payfast.d.ts +40 -0
- package/dist/providers/payfast.js +355 -0
- package/dist/providers/paystack.d.ts +37 -0
- package/dist/providers/paystack.js +335 -0
- package/dist/providers/peach.d.ts +50 -0
- package/dist/providers/peach.js +305 -0
- package/dist/providers/softycomp.d.ts +106 -0
- package/dist/providers/softycomp.js +234 -10
- package/dist/providers/stripe.d.ts +38 -0
- package/dist/providers/stripe.js +370 -0
- package/dist/providers/yoco.d.ts +12 -0
- package/dist/providers/yoco.js +159 -61
- package/dist/router.d.ts +33 -0
- package/dist/router.js +247 -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/package.json +7 -4
package/dist/providers/ozow.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
200
|
-
|
|
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 {};
|