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
package/dist/providers/ozow.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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 = {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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!');
|
|
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
|
-
|
|
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
|
-
// }
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
200
|
-
|
|
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 {};
|