paybridge 0.5.0 → 0.7.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/README.md +61 -0
- package/dist/cli/commands/providers.d.ts +1 -0
- package/dist/cli/commands/providers.js +135 -0
- package/dist/cli/commands/quote.d.ts +1 -0
- package/dist/cli/commands/quote.js +91 -0
- package/dist/cli/commands/test.d.ts +1 -0
- package/dist/cli/commands/test.js +92 -0
- package/dist/cli/commands/webhook.d.ts +1 -0
- package/dist/cli/commands/webhook.js +197 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +45 -0
- package/dist/cli/runners.d.ts +10 -0
- package/dist/cli/runners.js +335 -0
- package/dist/cli/utils.d.ts +16 -0
- package/dist/cli/utils.js +98 -0
- package/dist/crypto/index.d.ts +3 -1
- package/dist/crypto/index.js +23 -0
- package/dist/crypto/ramp.d.ts +36 -0
- package/dist/crypto/ramp.js +179 -0
- package/dist/crypto/transak.d.ts +32 -0
- package/dist/crypto/transak.js +212 -0
- package/dist/index.js +35 -0
- package/dist/providers/mollie.d.ts +39 -0
- package/dist/providers/mollie.js +178 -0
- package/dist/providers/pesapal.d.ts +47 -0
- package/dist/providers/pesapal.js +236 -0
- package/dist/providers/square.d.ts +41 -0
- package/dist/providers/square.js +278 -0
- package/dist/types.d.ts +1 -1
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -197,6 +197,9 @@ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
|
197
197
|
| **Adyen** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
198
198
|
| **Mercado Pago** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
199
199
|
| **Razorpay** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
200
|
+
| **Mollie** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
201
|
+
| **Square** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
202
|
+
| **Pesapal** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
200
203
|
|
|
201
204
|
### Crypto on/off-ramp providers
|
|
202
205
|
|
|
@@ -204,6 +207,8 @@ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
|
204
207
|
|----------|---------|----------|-------|----------|--------|
|
|
205
208
|
| **MoonPay** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
206
209
|
| **Yellow Card** | ⚠️ | ⚠️ | ⚠️ | ⚠️ | **Experimental** |
|
|
210
|
+
| **Transak** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
211
|
+
| **Ramp Network** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
207
212
|
|
|
208
213
|
**Legend:** ✅ Supported | ⛔ Not supported by upstream API | ⚠️ Experimental (spec unverified)
|
|
209
214
|
|
|
@@ -313,6 +318,62 @@ const pay = new PayBridge({
|
|
|
313
318
|
|
|
314
319
|
**Note:** Razorpay webhooks do not include timestamp-based replay protection.
|
|
315
320
|
|
|
321
|
+
### Mollie
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const pay = new PayBridge({
|
|
325
|
+
provider: 'mollie',
|
|
326
|
+
credentials: {
|
|
327
|
+
apiKey: 'test_...' // Or live_... for production
|
|
328
|
+
},
|
|
329
|
+
sandbox: true,
|
|
330
|
+
webhookSecret: 'optional_webhook_secret'
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Docs:** [Mollie API Reference](https://docs.mollie.com/reference)
|
|
335
|
+
|
|
336
|
+
**Note:** Mollie subscriptions require Customer + Mandate setup (not yet supported by paybridge). Mollie webhooks have no signature scheme — security relies on getPayment() round-trip + source IP validation.
|
|
337
|
+
|
|
338
|
+
### Square
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const pay = new PayBridge({
|
|
342
|
+
provider: 'square',
|
|
343
|
+
credentials: {
|
|
344
|
+
apiKey: 'EAAAEOuL...', // Access token
|
|
345
|
+
locationId: 'LOCATION123',
|
|
346
|
+
notificationUrl: 'https://example.com/webhook' // Required for signature verification
|
|
347
|
+
},
|
|
348
|
+
sandbox: true,
|
|
349
|
+
webhookSecret: 'your_webhook_secret'
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Docs:** [Square API Reference](https://developer.squareup.com/reference/square)
|
|
354
|
+
|
|
355
|
+
**Note:** Square subscriptions require multi-step Catalog + Customer + Plan setup (not yet supported by paybridge). Webhook signature uses notificationUrl + raw body.
|
|
356
|
+
|
|
357
|
+
### Pesapal
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const pay = new PayBridge({
|
|
361
|
+
provider: 'pesapal',
|
|
362
|
+
credentials: {
|
|
363
|
+
apiKey: 'qkio1BGG...', // consumer_key
|
|
364
|
+
secretKey: 'osGQ364R...', // consumer_secret
|
|
365
|
+
notificationId: 'IPN123', // Register IPN URL with Pesapal first
|
|
366
|
+
username: 'merchant@example.com' // Required for refunds
|
|
367
|
+
},
|
|
368
|
+
sandbox: true,
|
|
369
|
+
webhookSecret: 'optional_webhook_secret'
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Docs:** [Pesapal API Reference](https://developer.pesapal.com/)
|
|
374
|
+
|
|
375
|
+
**Note:** Pesapal subscriptions not yet supported. Pesapal IPN has no signature scheme — security relies on getPayment() round-trip + source IP validation. OAuth-style token caching (5min expiry).
|
|
376
|
+
|
|
316
377
|
## Switch Providers in 1 Line
|
|
317
378
|
|
|
318
379
|
```typescript
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runProviders(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runProviders = runProviders;
|
|
4
|
+
const index_1 = require("../../index");
|
|
5
|
+
const crypto_1 = require("../../crypto");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const FIAT_PROVIDERS = [
|
|
8
|
+
'softycomp',
|
|
9
|
+
'yoco',
|
|
10
|
+
'ozow',
|
|
11
|
+
'payfast',
|
|
12
|
+
'paystack',
|
|
13
|
+
'stripe',
|
|
14
|
+
'peach',
|
|
15
|
+
'flutterwave',
|
|
16
|
+
'adyen',
|
|
17
|
+
'mercadopago',
|
|
18
|
+
'razorpay',
|
|
19
|
+
'mollie',
|
|
20
|
+
'square',
|
|
21
|
+
'pesapal',
|
|
22
|
+
];
|
|
23
|
+
const CRYPTO_PROVIDERS = ['moonpay', 'yellowcard', 'transak', 'ramp'];
|
|
24
|
+
function createDummyProvider(name) {
|
|
25
|
+
const dummyCreds = {
|
|
26
|
+
softycomp: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
27
|
+
yoco: { apiKey: 'dummy' },
|
|
28
|
+
ozow: { apiKey: 'dummy', siteCode: 'dummy', privateKey: 'dummy' },
|
|
29
|
+
payfast: { merchantId: 'dummy', merchantKey: 'dummy' },
|
|
30
|
+
paystack: { apiKey: 'dummy' },
|
|
31
|
+
stripe: { apiKey: 'dummy' },
|
|
32
|
+
peach: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
33
|
+
flutterwave: { apiKey: 'dummy' },
|
|
34
|
+
adyen: { apiKey: 'dummy', merchantAccount: 'dummy' },
|
|
35
|
+
mercadopago: { apiKey: 'dummy' },
|
|
36
|
+
razorpay: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
37
|
+
mollie: { apiKey: 'dummy' },
|
|
38
|
+
square: { apiKey: 'dummy', locationId: 'dummy' },
|
|
39
|
+
pesapal: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
40
|
+
};
|
|
41
|
+
return new index_1.PayBridge({
|
|
42
|
+
provider: name,
|
|
43
|
+
credentials: dummyCreds[name],
|
|
44
|
+
sandbox: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createDummyCryptoProvider(name) {
|
|
48
|
+
const dummyCreds = {
|
|
49
|
+
moonpay: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
50
|
+
yellowcard: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
51
|
+
transak: { apiKey: 'dummy', secretKey: 'dummy' },
|
|
52
|
+
ramp: { apiKey: 'dummy' },
|
|
53
|
+
mock: { apiKey: 'dummy' },
|
|
54
|
+
};
|
|
55
|
+
return new crypto_1.CryptoRamp({
|
|
56
|
+
provider: name,
|
|
57
|
+
credentials: dummyCreds[name],
|
|
58
|
+
sandbox: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function runProviders(args) {
|
|
62
|
+
const isJson = args.includes('--json');
|
|
63
|
+
if (isJson) {
|
|
64
|
+
const fiat = FIAT_PROVIDERS.map((name) => {
|
|
65
|
+
const provider = createDummyProvider(name);
|
|
66
|
+
const caps = provider.provider.getCapabilities();
|
|
67
|
+
return {
|
|
68
|
+
provider: name,
|
|
69
|
+
type: 'fiat',
|
|
70
|
+
region: caps.country || 'N/A',
|
|
71
|
+
currencies: caps.currencies,
|
|
72
|
+
fee: `${caps.fees.percent ?? 0}% + ${caps.fees.fixed ?? 0}`,
|
|
73
|
+
minAmount: caps.minAmount,
|
|
74
|
+
maxAmount: caps.maxAmount,
|
|
75
|
+
avgLatencyMs: caps.avgLatencyMs,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const crypto = CRYPTO_PROVIDERS.map((name) => {
|
|
79
|
+
const ramp = createDummyCryptoProvider(name);
|
|
80
|
+
const caps = ramp.getCapabilities();
|
|
81
|
+
return {
|
|
82
|
+
provider: name,
|
|
83
|
+
type: 'crypto',
|
|
84
|
+
onRampSupported: !!caps.onRampLimits,
|
|
85
|
+
offRampSupported: !!caps.offRampLimits,
|
|
86
|
+
assets: caps.supportedAssets,
|
|
87
|
+
onRampFeePercent: caps.fees.onRampPercent,
|
|
88
|
+
offRampFeePercent: caps.fees.offRampPercent,
|
|
89
|
+
experimental: caps.experimental,
|
|
90
|
+
avgLatencyMs: caps.avgLatencyMs,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
console.log(JSON.stringify({ fiat, crypto }, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log((0, utils_1.colorize)('\nFIAT PROVIDERS', 'bright'));
|
|
97
|
+
console.log('==============\n');
|
|
98
|
+
const fiatRows = [
|
|
99
|
+
['PROVIDER', 'REGION', 'CURRENCIES', 'FEE'],
|
|
100
|
+
];
|
|
101
|
+
for (const name of FIAT_PROVIDERS) {
|
|
102
|
+
const provider = createDummyProvider(name);
|
|
103
|
+
const caps = provider.provider.getCapabilities();
|
|
104
|
+
fiatRows.push([
|
|
105
|
+
name,
|
|
106
|
+
caps.country || 'N/A',
|
|
107
|
+
caps.currencies.join('/'),
|
|
108
|
+
`${caps.fees.percent ?? 0}% + ${caps.fees.fixed ?? 0}`,
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
console.log((0, utils_1.formatTable)(fiatRows));
|
|
112
|
+
console.log((0, utils_1.colorize)('\n\nCRYPTO PROVIDERS', 'bright'));
|
|
113
|
+
console.log('================\n');
|
|
114
|
+
const cryptoRows = [
|
|
115
|
+
['PROVIDER', 'ON-RAMP', 'OFF-RAMP', 'ASSETS', 'FEE (ON/OFF)'],
|
|
116
|
+
];
|
|
117
|
+
for (const name of CRYPTO_PROVIDERS) {
|
|
118
|
+
const ramp = createDummyCryptoProvider(name);
|
|
119
|
+
const caps = ramp.getCapabilities();
|
|
120
|
+
const onRampIcon = caps.experimental ? '⚠' : caps.onRampLimits ? '✓' : '✗';
|
|
121
|
+
const offRampIcon = caps.experimental ? '⚠' : caps.offRampLimits ? '✓' : '✗';
|
|
122
|
+
const feeText = caps.experimental
|
|
123
|
+
? '⚠ experimental'
|
|
124
|
+
: `${caps.fees.onRampPercent ?? 0}% / ${caps.fees.offRampPercent ?? 0}%`;
|
|
125
|
+
cryptoRows.push([
|
|
126
|
+
name,
|
|
127
|
+
onRampIcon,
|
|
128
|
+
offRampIcon,
|
|
129
|
+
caps.supportedAssets.slice(0, 4).join('/'),
|
|
130
|
+
feeText,
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
console.log((0, utils_1.formatTable)(cryptoRows));
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runQuote(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runQuote = runQuote;
|
|
4
|
+
const crypto_1 = require("../../crypto");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const CRYPTO_PROVIDERS = ['moonpay', 'yellowcard', 'transak', 'ramp'];
|
|
7
|
+
function createCryptoProviderFromEnv(providerName) {
|
|
8
|
+
const envMap = {
|
|
9
|
+
moonpay: {
|
|
10
|
+
keys: ['MOONPAY_API_KEY', 'MOONPAY_SECRET_KEY'],
|
|
11
|
+
mapper: (env) => ({ apiKey: env.MOONPAY_API_KEY, secretKey: env.MOONPAY_SECRET_KEY }),
|
|
12
|
+
},
|
|
13
|
+
yellowcard: {
|
|
14
|
+
keys: ['YELLOWCARD_API_KEY', 'YELLOWCARD_SECRET_KEY'],
|
|
15
|
+
mapper: (env) => ({ apiKey: env.YELLOWCARD_API_KEY, secretKey: env.YELLOWCARD_SECRET_KEY }),
|
|
16
|
+
},
|
|
17
|
+
transak: {
|
|
18
|
+
keys: ['TRANSAK_API_KEY', 'TRANSAK_SECRET_KEY'],
|
|
19
|
+
mapper: (env) => ({ apiKey: env.TRANSAK_API_KEY, secretKey: env.TRANSAK_SECRET_KEY }),
|
|
20
|
+
},
|
|
21
|
+
ramp: {
|
|
22
|
+
keys: ['RAMP_API_KEY'],
|
|
23
|
+
mapper: (env) => ({ apiKey: env.RAMP_API_KEY }),
|
|
24
|
+
},
|
|
25
|
+
mock: {
|
|
26
|
+
keys: [],
|
|
27
|
+
mapper: () => ({ apiKey: 'mock' }),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const config = envMap[providerName];
|
|
31
|
+
if (!config) {
|
|
32
|
+
throw new Error(`Unknown crypto provider: ${providerName}`);
|
|
33
|
+
}
|
|
34
|
+
const missing = config.keys.filter((k) => !process.env[k]);
|
|
35
|
+
if (missing.length > 0) {
|
|
36
|
+
throw new Error(`Missing required env vars: ${missing.join(', ')}`);
|
|
37
|
+
}
|
|
38
|
+
const credentials = config.mapper(process.env);
|
|
39
|
+
return new crypto_1.CryptoRamp({
|
|
40
|
+
provider: providerName,
|
|
41
|
+
credentials,
|
|
42
|
+
sandbox: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function runQuote(args) {
|
|
46
|
+
const providerName = args[0];
|
|
47
|
+
if (!providerName) {
|
|
48
|
+
console.error('Usage: paybridge quote <provider> --direction on|off --fiat-amount N --fiat-currency CUR --asset ASSET --network NET');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
if (!CRYPTO_PROVIDERS.includes(providerName)) {
|
|
52
|
+
console.error(`Unknown crypto provider: ${providerName}`);
|
|
53
|
+
console.error(`Available: ${CRYPTO_PROVIDERS.join(', ')}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const getArg = (flag) => {
|
|
57
|
+
const idx = args.indexOf(flag);
|
|
58
|
+
return idx >= 0 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
59
|
+
};
|
|
60
|
+
const direction = getArg('--direction');
|
|
61
|
+
const fiatAmountStr = getArg('--fiat-amount');
|
|
62
|
+
const fiatCurrency = getArg('--fiat-currency');
|
|
63
|
+
const asset = getArg('--asset');
|
|
64
|
+
const network = getArg('--network');
|
|
65
|
+
if (!direction || !fiatAmountStr || !fiatCurrency || !asset) {
|
|
66
|
+
console.error('Missing required flags: --direction, --fiat-amount, --fiat-currency, --asset');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const fiatAmount = parseFloat(fiatAmountStr);
|
|
70
|
+
if (!Number.isFinite(fiatAmount) || fiatAmount <= 0) {
|
|
71
|
+
console.error('Invalid fiat amount (must be positive number)');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
let provider;
|
|
75
|
+
try {
|
|
76
|
+
provider = createCryptoProviderFromEnv(providerName);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error((0, utils_1.colorize)(error.message, 'red'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const quote = await provider.getQuote(direction, fiatAmount, fiatCurrency, asset, network || asset);
|
|
84
|
+
console.log(JSON.stringify(quote, null, 2));
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error((0, utils_1.colorize)(`Quote error: ${error.message}`, 'red'));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runTest(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runTest = runTest;
|
|
4
|
+
const runners_1 = require("../runners");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
async function runProviderTest(runner) {
|
|
7
|
+
const missing = runner.envRequired.filter((key) => !process.env[key]);
|
|
8
|
+
if (missing.length > 0) {
|
|
9
|
+
return {
|
|
10
|
+
provider: runner.name,
|
|
11
|
+
status: 'skipped',
|
|
12
|
+
message: `Missing: ${missing.join(', ')}`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
console.log(`${(0, utils_1.colorize)('[…]', 'cyan')} ${runner.name} - validating...`);
|
|
16
|
+
try {
|
|
17
|
+
const result = await runner.run();
|
|
18
|
+
if (!result.id) {
|
|
19
|
+
throw new Error('No payment ID returned');
|
|
20
|
+
}
|
|
21
|
+
console.log(`${(0, utils_1.colorize)('[✓]', 'green')} ${runner.name} → id=${result.id}${result.checkoutUrl ? `, url=${result.checkoutUrl.substring(0, 60)}...` : ''}, status=${result.status}`);
|
|
22
|
+
return {
|
|
23
|
+
provider: runner.name,
|
|
24
|
+
status: 'success',
|
|
25
|
+
data: {
|
|
26
|
+
id: result.id,
|
|
27
|
+
checkoutUrl: result.checkoutUrl,
|
|
28
|
+
status: result.status,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.log(`${(0, utils_1.colorize)('[✗]', 'red')} ${runner.name} ERROR: ${error.message || String(error)}`);
|
|
34
|
+
return {
|
|
35
|
+
provider: runner.name,
|
|
36
|
+
status: 'failed',
|
|
37
|
+
message: error.message || String(error),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function runTest(args) {
|
|
42
|
+
const isAll = args.includes('--all');
|
|
43
|
+
const providerName = args.find((a) => !a.startsWith('--'));
|
|
44
|
+
if (!isAll && !providerName) {
|
|
45
|
+
console.error('Usage: paybridge test <provider> | paybridge test --all');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
console.log('\n=== PayBridge Test ===\n');
|
|
49
|
+
const results = [];
|
|
50
|
+
if (isAll) {
|
|
51
|
+
for (const runner of runners_1.runners) {
|
|
52
|
+
const result = await runProviderTest(runner);
|
|
53
|
+
results.push(result);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const runner = runners_1.runners.find((r) => r.name === providerName);
|
|
58
|
+
if (!runner) {
|
|
59
|
+
console.error(`Unknown provider: ${providerName}`);
|
|
60
|
+
console.error(`Available: ${runners_1.runners.map((r) => r.name).join(', ')}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const result = await runProviderTest(runner);
|
|
64
|
+
results.push(result);
|
|
65
|
+
}
|
|
66
|
+
console.log('\n=== Summary ===\n');
|
|
67
|
+
const succeeded = results.filter((r) => r.status === 'success').length;
|
|
68
|
+
const skipped = results.filter((r) => r.status === 'skipped').length;
|
|
69
|
+
const failed = results.filter((r) => r.status === 'failed').length;
|
|
70
|
+
results.forEach((r) => {
|
|
71
|
+
const icon = r.status === 'success'
|
|
72
|
+
? (0, utils_1.colorize)('[✓]', 'green')
|
|
73
|
+
: r.status === 'skipped'
|
|
74
|
+
? (0, utils_1.colorize)('[ ]', 'dim')
|
|
75
|
+
: (0, utils_1.colorize)('[✗]', 'red');
|
|
76
|
+
const msg = r.message ? ` (${r.message})` : '';
|
|
77
|
+
console.log(`${icon} ${r.provider}${msg}`);
|
|
78
|
+
});
|
|
79
|
+
console.log(`\nSucceeded: ${succeeded}, Skipped: ${skipped}, Failed: ${failed}`);
|
|
80
|
+
if (failed > 0) {
|
|
81
|
+
console.log('\nSome providers failed validation.');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
else if (succeeded === 0) {
|
|
85
|
+
console.log('\nNo providers validated (all skipped). Set env vars to validate.');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log('\nAll enabled providers validated successfully!');
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runWebhook(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runWebhook = runWebhook;
|
|
4
|
+
const index_1 = require("../../index");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const PROVIDERS = [
|
|
7
|
+
'softycomp',
|
|
8
|
+
'yoco',
|
|
9
|
+
'ozow',
|
|
10
|
+
'payfast',
|
|
11
|
+
'paystack',
|
|
12
|
+
'stripe',
|
|
13
|
+
'peach',
|
|
14
|
+
'flutterwave',
|
|
15
|
+
'adyen',
|
|
16
|
+
'mercadopago',
|
|
17
|
+
'razorpay',
|
|
18
|
+
'mollie',
|
|
19
|
+
'square',
|
|
20
|
+
'pesapal',
|
|
21
|
+
];
|
|
22
|
+
function createProviderFromEnv(providerName) {
|
|
23
|
+
const envMap = {
|
|
24
|
+
softycomp: {
|
|
25
|
+
keys: ['SOFTYCOMP_API_KEY', 'SOFTYCOMP_SECRET_KEY', 'SOFTYCOMP_WEBHOOK_SECRET'],
|
|
26
|
+
mapper: (env) => ({
|
|
27
|
+
apiKey: env.SOFTYCOMP_API_KEY,
|
|
28
|
+
secretKey: env.SOFTYCOMP_SECRET_KEY,
|
|
29
|
+
webhookSecret: env.SOFTYCOMP_WEBHOOK_SECRET,
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
yoco: {
|
|
33
|
+
keys: ['YOCO_API_KEY', 'YOCO_WEBHOOK_SECRET'],
|
|
34
|
+
mapper: (env) => ({ apiKey: env.YOCO_API_KEY, webhookSecret: env.YOCO_WEBHOOK_SECRET }),
|
|
35
|
+
},
|
|
36
|
+
ozow: {
|
|
37
|
+
keys: ['OZOW_API_KEY', 'OZOW_SITE_CODE', 'OZOW_PRIVATE_KEY'],
|
|
38
|
+
mapper: (env) => ({
|
|
39
|
+
apiKey: env.OZOW_API_KEY,
|
|
40
|
+
siteCode: env.OZOW_SITE_CODE,
|
|
41
|
+
privateKey: env.OZOW_PRIVATE_KEY,
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
payfast: {
|
|
45
|
+
keys: ['PAYFAST_MERCHANT_ID', 'PAYFAST_MERCHANT_KEY', 'PAYFAST_PASSPHRASE'],
|
|
46
|
+
mapper: (env) => ({
|
|
47
|
+
merchantId: env.PAYFAST_MERCHANT_ID,
|
|
48
|
+
merchantKey: env.PAYFAST_MERCHANT_KEY,
|
|
49
|
+
passphrase: env.PAYFAST_PASSPHRASE,
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
paystack: {
|
|
53
|
+
keys: ['PAYSTACK_API_KEY', 'PAYSTACK_WEBHOOK_SECRET'],
|
|
54
|
+
mapper: (env) => ({ apiKey: env.PAYSTACK_API_KEY, webhookSecret: env.PAYSTACK_WEBHOOK_SECRET }),
|
|
55
|
+
},
|
|
56
|
+
stripe: {
|
|
57
|
+
keys: ['STRIPE_API_KEY', 'STRIPE_WEBHOOK_SECRET'],
|
|
58
|
+
mapper: (env) => ({ apiKey: env.STRIPE_API_KEY, webhookSecret: env.STRIPE_WEBHOOK_SECRET }),
|
|
59
|
+
},
|
|
60
|
+
peach: {
|
|
61
|
+
keys: ['PEACH_ACCESS_TOKEN', 'PEACH_ENTITY_ID', 'PEACH_WEBHOOK_SECRET'],
|
|
62
|
+
mapper: (env) => ({
|
|
63
|
+
apiKey: env.PEACH_ACCESS_TOKEN,
|
|
64
|
+
secretKey: env.PEACH_ENTITY_ID,
|
|
65
|
+
webhookSecret: env.PEACH_WEBHOOK_SECRET,
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
flutterwave: {
|
|
69
|
+
keys: ['FLUTTERWAVE_API_KEY', 'FLUTTERWAVE_WEBHOOK_SECRET'],
|
|
70
|
+
mapper: (env) => ({ apiKey: env.FLUTTERWAVE_API_KEY, webhookSecret: env.FLUTTERWAVE_WEBHOOK_SECRET }),
|
|
71
|
+
},
|
|
72
|
+
adyen: {
|
|
73
|
+
keys: ['ADYEN_API_KEY', 'ADYEN_MERCHANT_ACCOUNT', 'ADYEN_WEBHOOK_SECRET'],
|
|
74
|
+
mapper: (env) => ({
|
|
75
|
+
apiKey: env.ADYEN_API_KEY,
|
|
76
|
+
merchantAccount: env.ADYEN_MERCHANT_ACCOUNT,
|
|
77
|
+
webhookSecret: env.ADYEN_WEBHOOK_SECRET,
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
mercadopago: {
|
|
81
|
+
keys: ['MERCADOPAGO_ACCESS_TOKEN', 'MERCADOPAGO_WEBHOOK_SECRET'],
|
|
82
|
+
mapper: (env) => ({ apiKey: env.MERCADOPAGO_ACCESS_TOKEN, webhookSecret: env.MERCADOPAGO_WEBHOOK_SECRET }),
|
|
83
|
+
},
|
|
84
|
+
razorpay: {
|
|
85
|
+
keys: ['RAZORPAY_KEY_ID', 'RAZORPAY_KEY_SECRET', 'RAZORPAY_WEBHOOK_SECRET'],
|
|
86
|
+
mapper: (env) => ({
|
|
87
|
+
apiKey: env.RAZORPAY_KEY_ID,
|
|
88
|
+
secretKey: env.RAZORPAY_KEY_SECRET,
|
|
89
|
+
webhookSecret: env.RAZORPAY_WEBHOOK_SECRET,
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
mollie: {
|
|
93
|
+
keys: ['MOLLIE_API_KEY'],
|
|
94
|
+
mapper: (env) => ({ apiKey: env.MOLLIE_API_KEY }),
|
|
95
|
+
},
|
|
96
|
+
square: {
|
|
97
|
+
keys: ['SQUARE_ACCESS_TOKEN', 'SQUARE_LOCATION_ID', 'SQUARE_WEBHOOK_SECRET'],
|
|
98
|
+
mapper: (env) => ({
|
|
99
|
+
apiKey: env.SQUARE_ACCESS_TOKEN,
|
|
100
|
+
locationId: env.SQUARE_LOCATION_ID,
|
|
101
|
+
webhookSecret: env.SQUARE_WEBHOOK_SECRET,
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
pesapal: {
|
|
105
|
+
keys: ['PESAPAL_CONSUMER_KEY', 'PESAPAL_CONSUMER_SECRET'],
|
|
106
|
+
mapper: (env) => ({ apiKey: env.PESAPAL_CONSUMER_KEY, secretKey: env.PESAPAL_CONSUMER_SECRET }),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const config = envMap[providerName];
|
|
110
|
+
if (!config) {
|
|
111
|
+
throw new Error(`Unknown provider: ${providerName}`);
|
|
112
|
+
}
|
|
113
|
+
const required = config.keys.filter((k) => !k.includes('WEBHOOK_SECRET'));
|
|
114
|
+
const missing = required.filter((k) => !process.env[k]);
|
|
115
|
+
if (missing.length > 0) {
|
|
116
|
+
throw new Error(`Missing required env vars: ${missing.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
const credentials = config.mapper(process.env);
|
|
119
|
+
return new index_1.PayBridge({
|
|
120
|
+
provider: providerName,
|
|
121
|
+
credentials,
|
|
122
|
+
sandbox: true,
|
|
123
|
+
webhookSecret: credentials.webhookSecret,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function runWebhook(args) {
|
|
127
|
+
const subcommand = args[0];
|
|
128
|
+
const providerName = args[1];
|
|
129
|
+
if (!subcommand || !providerName) {
|
|
130
|
+
console.error('Usage: paybridge webhook verify|parse <provider> [--header key=value]');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (!PROVIDERS.includes(providerName)) {
|
|
134
|
+
console.error(`Unknown provider: ${providerName}`);
|
|
135
|
+
console.error(`Available: ${PROVIDERS.join(', ')}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const headerFlags = args.slice(2).filter((a) => a.startsWith('--header')).map((a) => a.replace('--header', '').trim());
|
|
139
|
+
const headersJsonFlag = args.find((a) => a.startsWith('--headers-json='));
|
|
140
|
+
let headers = {};
|
|
141
|
+
if (headersJsonFlag) {
|
|
142
|
+
const json = headersJsonFlag.replace('--headers-json=', '');
|
|
143
|
+
try {
|
|
144
|
+
headers = JSON.parse(json);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
console.error('Invalid JSON in --headers-json');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
headers = (0, utils_1.parseHeaders)(headerFlags);
|
|
153
|
+
}
|
|
154
|
+
const rawBody = await (0, utils_1.readStdin)();
|
|
155
|
+
let body;
|
|
156
|
+
try {
|
|
157
|
+
body = JSON.parse(rawBody);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
body = rawBody;
|
|
161
|
+
}
|
|
162
|
+
let provider;
|
|
163
|
+
try {
|
|
164
|
+
provider = createProviderFromEnv(providerName);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error((0, utils_1.colorize)(error.message, 'red'));
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
if (subcommand === 'verify') {
|
|
171
|
+
const valid = provider.verifyWebhook(body, headers);
|
|
172
|
+
if (valid) {
|
|
173
|
+
console.log((0, utils_1.colorize)('OK', 'green'));
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log((0, utils_1.colorize)('INVALID', 'red'));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (subcommand === 'parse') {
|
|
182
|
+
try {
|
|
183
|
+
const event = provider.parseWebhook(body, headers);
|
|
184
|
+
console.log(JSON.stringify(event, null, 2));
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error((0, utils_1.colorize)(`Parse error: ${error.message}`, 'red'));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.error(`Unknown webhook subcommand: ${subcommand}`);
|
|
194
|
+
console.error('Use "verify" or "parse"');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const test_1 = require("./commands/test");
|
|
5
|
+
const providers_1 = require("./commands/providers");
|
|
6
|
+
const webhook_1 = require("./commands/webhook");
|
|
7
|
+
const quote_1 = require("./commands/quote");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
async function main() {
|
|
10
|
+
const [, , command, ...args] = process.argv;
|
|
11
|
+
switch (command) {
|
|
12
|
+
case 'test':
|
|
13
|
+
await (0, test_1.runTest)(args);
|
|
14
|
+
break;
|
|
15
|
+
case 'providers':
|
|
16
|
+
await (0, providers_1.runProviders)(args);
|
|
17
|
+
break;
|
|
18
|
+
case 'webhook':
|
|
19
|
+
await (0, webhook_1.runWebhook)(args);
|
|
20
|
+
break;
|
|
21
|
+
case 'quote':
|
|
22
|
+
await (0, quote_1.runQuote)(args);
|
|
23
|
+
break;
|
|
24
|
+
case '-h':
|
|
25
|
+
case '--help':
|
|
26
|
+
case 'help':
|
|
27
|
+
case undefined:
|
|
28
|
+
(0, utils_1.printHelp)();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
break;
|
|
31
|
+
case '-v':
|
|
32
|
+
case '--version':
|
|
33
|
+
(0, utils_1.printVersion)();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
console.error(`Unknown command: ${command}`);
|
|
38
|
+
(0, utils_1.printHelp)(true);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
main().catch((err) => {
|
|
43
|
+
console.error(err.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|