paybridge 0.1.0
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/LICENSE +21 -0
- package/README.md +408 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +227 -0
- package/dist/providers/base.d.ts +48 -0
- package/dist/providers/base.js +29 -0
- package/dist/providers/ozow.d.ts +33 -0
- package/dist/providers/ozow.js +203 -0
- package/dist/providers/softycomp.d.ts +34 -0
- package/dist/providers/softycomp.js +292 -0
- package/dist/providers/yoco.d.ts +30 -0
- package/dist/providers/yoco.js +158 -0
- package/dist/types.d.ts +167 -0
- package/dist/types.js +5 -0
- package/dist/utils/currency.d.ts +28 -0
- package/dist/utils/currency.js +67 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PayBridge — Unified payment SDK for Node.js
|
|
4
|
+
* One API. Every payment provider.
|
|
5
|
+
*
|
|
6
|
+
* @see https://github.com/kobie3717/paybridge
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.PayBridge = void 0;
|
|
24
|
+
const softycomp_1 = require("./providers/softycomp");
|
|
25
|
+
const yoco_1 = require("./providers/yoco");
|
|
26
|
+
const ozow_1 = require("./providers/ozow");
|
|
27
|
+
__exportStar(require("./types"), exports);
|
|
28
|
+
__exportStar(require("./utils/currency"), exports);
|
|
29
|
+
class PayBridge {
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.provider = this.createProvider(config);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create provider instance based on config
|
|
35
|
+
*/
|
|
36
|
+
createProvider(config) {
|
|
37
|
+
const { provider, credentials, sandbox = true, webhookSecret } = config;
|
|
38
|
+
switch (provider) {
|
|
39
|
+
case 'softycomp':
|
|
40
|
+
if (!credentials.apiKey || !credentials.secretKey) {
|
|
41
|
+
throw new Error('SoftyComp requires apiKey and secretKey');
|
|
42
|
+
}
|
|
43
|
+
return new softycomp_1.SoftyCompProvider({
|
|
44
|
+
apiKey: credentials.apiKey,
|
|
45
|
+
secretKey: credentials.secretKey,
|
|
46
|
+
sandbox,
|
|
47
|
+
webhookSecret,
|
|
48
|
+
});
|
|
49
|
+
case 'yoco':
|
|
50
|
+
if (!credentials.apiKey) {
|
|
51
|
+
throw new Error('Yoco requires apiKey (secret key)');
|
|
52
|
+
}
|
|
53
|
+
return new yoco_1.YocoProvider({
|
|
54
|
+
apiKey: credentials.apiKey,
|
|
55
|
+
sandbox,
|
|
56
|
+
webhookSecret,
|
|
57
|
+
});
|
|
58
|
+
case 'ozow':
|
|
59
|
+
if (!credentials.apiKey || !credentials.siteCode || !credentials.privateKey) {
|
|
60
|
+
throw new Error('Ozow requires apiKey, siteCode, and privateKey');
|
|
61
|
+
}
|
|
62
|
+
return new ozow_1.OzowProvider({
|
|
63
|
+
apiKey: credentials.apiKey,
|
|
64
|
+
siteCode: credentials.siteCode,
|
|
65
|
+
privateKey: credentials.privateKey,
|
|
66
|
+
sandbox,
|
|
67
|
+
});
|
|
68
|
+
case 'payfast':
|
|
69
|
+
case 'paystack':
|
|
70
|
+
case 'stripe':
|
|
71
|
+
case 'peach':
|
|
72
|
+
throw new Error(`Provider ${provider} not yet implemented. Coming soon!`);
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ==================== Payment Methods ====================
|
|
78
|
+
/**
|
|
79
|
+
* Create a one-time payment
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const payment = await pay.createPayment({
|
|
84
|
+
* amount: 299.00,
|
|
85
|
+
* currency: 'ZAR',
|
|
86
|
+
* reference: 'INV-001',
|
|
87
|
+
* customer: {
|
|
88
|
+
* name: 'John Doe',
|
|
89
|
+
* email: 'john@example.com',
|
|
90
|
+
* phone: '0825551234'
|
|
91
|
+
* },
|
|
92
|
+
* urls: {
|
|
93
|
+
* success: 'https://myapp.com/success',
|
|
94
|
+
* cancel: 'https://myapp.com/cancel',
|
|
95
|
+
* webhook: 'https://myapp.com/webhook'
|
|
96
|
+
* }
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* // Redirect customer to payment page
|
|
100
|
+
* res.redirect(payment.checkoutUrl);
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
async createPayment(params) {
|
|
104
|
+
return this.provider.createPayment(params);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a recurring subscription
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const subscription = await pay.createSubscription({
|
|
112
|
+
* amount: 299.00,
|
|
113
|
+
* currency: 'ZAR',
|
|
114
|
+
* interval: 'monthly',
|
|
115
|
+
* reference: 'SUB-001',
|
|
116
|
+
* customer: {
|
|
117
|
+
* name: 'John Doe',
|
|
118
|
+
* email: 'john@example.com'
|
|
119
|
+
* },
|
|
120
|
+
* urls: {
|
|
121
|
+
* success: 'https://myapp.com/success',
|
|
122
|
+
* cancel: 'https://myapp.com/cancel',
|
|
123
|
+
* webhook: 'https://myapp.com/webhook'
|
|
124
|
+
* },
|
|
125
|
+
* startDate: '2026-04-01'
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
async createSubscription(params) {
|
|
130
|
+
return this.provider.createSubscription(params);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get payment status
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const payment = await pay.getPayment('pay_123');
|
|
138
|
+
* if (payment.status === 'completed') {
|
|
139
|
+
* console.log('Payment received!');
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
async getPayment(id) {
|
|
144
|
+
return this.provider.getPayment(id);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Process a refund (full or partial)
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Full refund
|
|
152
|
+
* const refund = await pay.refund({
|
|
153
|
+
* paymentId: 'pay_123'
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* // Partial refund
|
|
157
|
+
* const refund = await pay.refund({
|
|
158
|
+
* paymentId: 'pay_123',
|
|
159
|
+
* amount: 100.00,
|
|
160
|
+
* reason: 'Customer request'
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
async refund(params) {
|
|
165
|
+
return this.provider.refund(params);
|
|
166
|
+
}
|
|
167
|
+
// ==================== Webhooks ====================
|
|
168
|
+
/**
|
|
169
|
+
* Parse webhook payload into unified event format
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* app.post('/webhook', express.json(), (req, res) => {
|
|
174
|
+
* const event = pay.parseWebhook(req.body, req.headers);
|
|
175
|
+
*
|
|
176
|
+
* switch (event.type) {
|
|
177
|
+
* case 'payment.completed':
|
|
178
|
+
* console.log('Payment completed:', event.payment);
|
|
179
|
+
* break;
|
|
180
|
+
* case 'payment.failed':
|
|
181
|
+
* console.log('Payment failed:', event.payment);
|
|
182
|
+
* break;
|
|
183
|
+
* }
|
|
184
|
+
*
|
|
185
|
+
* res.sendStatus(200);
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
parseWebhook(body, headers) {
|
|
190
|
+
return this.provider.parseWebhook(body, headers);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Verify webhook signature
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
198
|
+
* if (!pay.verifyWebhook(req.body, req.headers)) {
|
|
199
|
+
* return res.status(400).send('Invalid signature');
|
|
200
|
+
* }
|
|
201
|
+
*
|
|
202
|
+
* const event = pay.parseWebhook(req.body, req.headers);
|
|
203
|
+
* // Process event...
|
|
204
|
+
*
|
|
205
|
+
* res.sendStatus(200);
|
|
206
|
+
* });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
verifyWebhook(body, headers) {
|
|
210
|
+
return this.provider.verifyWebhook(body, headers);
|
|
211
|
+
}
|
|
212
|
+
// ==================== Helpers ====================
|
|
213
|
+
/**
|
|
214
|
+
* Get provider name
|
|
215
|
+
*/
|
|
216
|
+
getProviderName() {
|
|
217
|
+
return this.provider.name;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get supported currencies for current provider
|
|
221
|
+
*/
|
|
222
|
+
getSupportedCurrencies() {
|
|
223
|
+
return this.provider.supportedCurrencies;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
exports.PayBridge = PayBridge;
|
|
227
|
+
exports.default = PayBridge;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base payment provider abstract class
|
|
3
|
+
* All payment providers must extend this class
|
|
4
|
+
*/
|
|
5
|
+
import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
|
|
6
|
+
export declare abstract class PaymentProvider {
|
|
7
|
+
/**
|
|
8
|
+
* Provider name (e.g. 'softycomp', 'yoco', 'ozow')
|
|
9
|
+
*/
|
|
10
|
+
abstract readonly name: string;
|
|
11
|
+
/**
|
|
12
|
+
* Supported currencies
|
|
13
|
+
*/
|
|
14
|
+
abstract readonly supportedCurrencies: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Create a one-time payment
|
|
17
|
+
*/
|
|
18
|
+
abstract createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a recurring subscription
|
|
21
|
+
*/
|
|
22
|
+
abstract createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Get payment status
|
|
25
|
+
*/
|
|
26
|
+
abstract getPayment(id: string): Promise<PaymentResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Process a refund (full or partial)
|
|
29
|
+
*/
|
|
30
|
+
abstract refund(params: RefundParams): Promise<RefundResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Parse webhook payload into unified event format
|
|
33
|
+
*/
|
|
34
|
+
abstract parseWebhook(body: any, headers?: any): WebhookEvent;
|
|
35
|
+
/**
|
|
36
|
+
* Verify webhook signature
|
|
37
|
+
* @returns true if signature is valid, false otherwise
|
|
38
|
+
*/
|
|
39
|
+
abstract verifyWebhook(body: any, headers?: any): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Validate currency is supported
|
|
42
|
+
*/
|
|
43
|
+
protected validateCurrency(currency: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Validate future date
|
|
46
|
+
*/
|
|
47
|
+
protected validateFutureDate(dateStr: string, fieldName: string): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base payment provider abstract class
|
|
4
|
+
* All payment providers must extend this class
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PaymentProvider = void 0;
|
|
8
|
+
class PaymentProvider {
|
|
9
|
+
/**
|
|
10
|
+
* Validate currency is supported
|
|
11
|
+
*/
|
|
12
|
+
validateCurrency(currency) {
|
|
13
|
+
if (!this.supportedCurrencies.includes(currency)) {
|
|
14
|
+
throw new Error(`Currency ${currency} not supported by ${this.name}. Supported: ${this.supportedCurrencies.join(', ')}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate future date
|
|
19
|
+
*/
|
|
20
|
+
validateFutureDate(dateStr, fieldName) {
|
|
21
|
+
const date = new Date(dateStr);
|
|
22
|
+
const now = new Date();
|
|
23
|
+
now.setHours(0, 0, 0, 0);
|
|
24
|
+
if (date <= now) {
|
|
25
|
+
throw new Error(`${fieldName} must be a future date (minimum tomorrow)`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.PaymentProvider = PaymentProvider;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ozow payment provider
|
|
3
|
+
* South African instant EFT payment gateway
|
|
4
|
+
* @see https://hub.ozow.com
|
|
5
|
+
*/
|
|
6
|
+
import { PaymentProvider } from './base';
|
|
7
|
+
import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
|
|
8
|
+
interface OzowConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
siteCode: string;
|
|
11
|
+
privateKey: string;
|
|
12
|
+
sandbox: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class OzowProvider extends PaymentProvider {
|
|
15
|
+
readonly name = "ozow";
|
|
16
|
+
readonly supportedCurrencies: string[];
|
|
17
|
+
private apiKey;
|
|
18
|
+
private siteCode;
|
|
19
|
+
private privateKey;
|
|
20
|
+
private sandbox;
|
|
21
|
+
private baseUrl;
|
|
22
|
+
constructor(config: OzowConfig);
|
|
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
|
+
verifyWebhook(body: any, _headers?: any): boolean;
|
|
29
|
+
private mapOzowStatus;
|
|
30
|
+
private mapOzowEventType;
|
|
31
|
+
private generateWebhookHash;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ozow payment provider
|
|
4
|
+
* South African instant EFT payment gateway
|
|
5
|
+
* @see https://hub.ozow.com
|
|
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.OzowProvider = void 0;
|
|
12
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
+
const base_1 = require("./base");
|
|
14
|
+
class OzowProvider extends base_1.PaymentProvider {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super();
|
|
17
|
+
this.name = 'ozow';
|
|
18
|
+
this.supportedCurrencies = ['ZAR'];
|
|
19
|
+
this.apiKey = config.apiKey;
|
|
20
|
+
this.siteCode = config.siteCode;
|
|
21
|
+
this.privateKey = config.privateKey;
|
|
22
|
+
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
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// ==================== Payment Methods ====================
|
|
32
|
+
async createPayment(params) {
|
|
33
|
+
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 = {
|
|
60
|
+
SiteCode: this.siteCode,
|
|
61
|
+
CountryCode: 'ZA',
|
|
62
|
+
CurrencyCode: params.currency,
|
|
63
|
+
Amount: params.amount.toFixed(2),
|
|
64
|
+
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 || '',
|
|
70
|
+
CancelUrl: params.urls.cancel,
|
|
71
|
+
ErrorUrl: params.urls.cancel,
|
|
72
|
+
SuccessUrl: params.urls.success,
|
|
73
|
+
NotifyUrl: params.urls.webhook,
|
|
74
|
+
IsTest: this.sandbox,
|
|
75
|
+
// HashCheck: this.generateHash(...) // TODO: Implement hash generation
|
|
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
|
+
}
|
|
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!');
|
|
89
|
+
}
|
|
90
|
+
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!');
|
|
96
|
+
}
|
|
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!');
|
|
109
|
+
}
|
|
110
|
+
// ==================== Webhooks ====================
|
|
111
|
+
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
|
+
// }
|
|
131
|
+
const ozowStatus = event.Status;
|
|
132
|
+
const status = this.mapOzowStatus(ozowStatus);
|
|
133
|
+
const eventType = this.mapOzowEventType(ozowStatus);
|
|
134
|
+
return {
|
|
135
|
+
type: eventType,
|
|
136
|
+
payment: {
|
|
137
|
+
id: event.TransactionId || event.TransactionReference,
|
|
138
|
+
checkoutUrl: '',
|
|
139
|
+
status,
|
|
140
|
+
amount: parseFloat(event.Amount || '0'),
|
|
141
|
+
currency: event.CurrencyCode || 'ZAR',
|
|
142
|
+
reference: event.TransactionReference,
|
|
143
|
+
provider: 'ozow',
|
|
144
|
+
createdAt: new Date().toISOString(),
|
|
145
|
+
},
|
|
146
|
+
raw: event,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
verifyWebhook(body, _headers) {
|
|
150
|
+
const event = typeof body === 'string' ? JSON.parse(body) : body;
|
|
151
|
+
const receivedHash = event.Hash;
|
|
152
|
+
if (!receivedHash) {
|
|
153
|
+
// No hash provided
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// TODO: Verify Ozow SHA512 hash
|
|
157
|
+
// expectedHash = SHA512(SiteCode + TransactionId + TransactionReference + Amount + Status + Optional1 + Optional2 + Optional3 + Optional4 + Optional5 + CurrencyCode + IsTest + StatusMessage + PrivateKey)
|
|
158
|
+
const expectedHash = this.generateWebhookHash(event);
|
|
159
|
+
return receivedHash === expectedHash;
|
|
160
|
+
}
|
|
161
|
+
// ==================== Helpers ====================
|
|
162
|
+
mapOzowStatus(ozowStatus) {
|
|
163
|
+
const statusMap = {
|
|
164
|
+
Complete: 'completed',
|
|
165
|
+
Cancelled: 'cancelled',
|
|
166
|
+
Error: 'failed',
|
|
167
|
+
Abandoned: 'cancelled',
|
|
168
|
+
PendingInvestigation: 'pending',
|
|
169
|
+
};
|
|
170
|
+
return statusMap[ozowStatus] || 'pending';
|
|
171
|
+
}
|
|
172
|
+
mapOzowEventType(ozowStatus) {
|
|
173
|
+
const typeMap = {
|
|
174
|
+
Complete: 'payment.completed',
|
|
175
|
+
Cancelled: 'payment.cancelled',
|
|
176
|
+
Error: 'payment.failed',
|
|
177
|
+
Abandoned: 'payment.cancelled',
|
|
178
|
+
};
|
|
179
|
+
return typeMap[ozowStatus] || 'payment.pending';
|
|
180
|
+
}
|
|
181
|
+
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,
|
|
198
|
+
];
|
|
199
|
+
const concatenated = fields.join('');
|
|
200
|
+
return crypto_1.default.createHash('sha512').update(concatenated, 'utf8').digest('hex').toLowerCase();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.OzowProvider = OzowProvider;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SoftyComp payment provider
|
|
3
|
+
* South African bill presentment and debit order platform
|
|
4
|
+
*/
|
|
5
|
+
import { PaymentProvider } from './base';
|
|
6
|
+
import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
|
|
7
|
+
interface SoftyCompConfig {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
secretKey: string;
|
|
10
|
+
sandbox: boolean;
|
|
11
|
+
webhookSecret?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class SoftyCompProvider extends PaymentProvider {
|
|
14
|
+
readonly name = "softycomp";
|
|
15
|
+
readonly supportedCurrencies: string[];
|
|
16
|
+
private apiKey;
|
|
17
|
+
private secretKey;
|
|
18
|
+
private sandbox;
|
|
19
|
+
private baseUrl;
|
|
20
|
+
private webhookSecret?;
|
|
21
|
+
private token;
|
|
22
|
+
private tokenExpiry;
|
|
23
|
+
constructor(config: SoftyCompConfig);
|
|
24
|
+
private authenticate;
|
|
25
|
+
private apiRequest;
|
|
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: string | Buffer, headers?: any): boolean;
|
|
32
|
+
private mapBillStatus;
|
|
33
|
+
}
|
|
34
|
+
export {};
|