paybridge 0.6.0 → 0.8.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 CHANGED
@@ -48,6 +48,17 @@ The playground lets you:
48
48
 
49
49
  Perfect for demos, learning, and rapid prototyping. See [playground/README.md](playground/README.md) for details.
50
50
 
51
+ ## Framework Examples
52
+
53
+ Runnable integrations for common Node.js frameworks:
54
+
55
+ - [Express](examples/frameworks/express/) — classic, raw body parsing for webhooks
56
+ - [Fastify](examples/frameworks/fastify/) — Fastify plugin pattern, custom content type parser
57
+ - [Next.js](examples/frameworks/nextjs/) — App Router API routes, multi-provider router
58
+ - [Hono](examples/frameworks/hono/) — edge-runtime ready (Cloudflare Workers, Bun, Deno, Node)
59
+
60
+ Each example uses `PayBridgeRouter` with Stripe + PayStack and demonstrates webhook signature verification, idempotency, and provider-specific routing.
61
+
51
62
  ## Quick Start
52
63
 
53
64
  > **Upgrading from 0.1 or 0.2?** See [docs/migration.md](docs/migration.md).
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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
+ });
@@ -0,0 +1,10 @@
1
+ export interface ProviderRunner {
2
+ name: string;
3
+ envRequired: string[];
4
+ run: () => Promise<{
5
+ id: string;
6
+ checkoutUrl?: string;
7
+ status: string;
8
+ }>;
9
+ }
10
+ export declare const runners: ProviderRunner[];
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runners = void 0;
4
+ const index_1 = require("../index");
5
+ const crypto_1 = require("../crypto");
6
+ const timestamp = Date.now();
7
+ const testCustomer = {
8
+ name: 'Test User',
9
+ email: 'test@example.com',
10
+ phone: '0825551234',
11
+ };
12
+ const testUrls = {
13
+ success: 'https://example.com/success',
14
+ cancel: 'https://example.com/cancel',
15
+ webhook: 'https://example.com/webhook',
16
+ };
17
+ exports.runners = [
18
+ {
19
+ name: 'softycomp',
20
+ envRequired: ['SOFTYCOMP_API_KEY', 'SOFTYCOMP_SECRET_KEY'],
21
+ run: async () => {
22
+ const pay = new index_1.PayBridge({
23
+ provider: 'softycomp',
24
+ credentials: {
25
+ apiKey: process.env.SOFTYCOMP_API_KEY,
26
+ secretKey: process.env.SOFTYCOMP_SECRET_KEY,
27
+ },
28
+ sandbox: true,
29
+ });
30
+ const payment = await pay.createPayment({
31
+ amount: 1.0,
32
+ currency: 'ZAR',
33
+ reference: `cli-test-${timestamp}`,
34
+ customer: testCustomer,
35
+ urls: testUrls,
36
+ });
37
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
38
+ },
39
+ },
40
+ {
41
+ name: 'yoco',
42
+ envRequired: ['YOCO_API_KEY'],
43
+ run: async () => {
44
+ const pay = new index_1.PayBridge({
45
+ provider: 'yoco',
46
+ credentials: { apiKey: process.env.YOCO_API_KEY },
47
+ sandbox: true,
48
+ });
49
+ const payment = await pay.createPayment({
50
+ amount: 1.0,
51
+ currency: 'ZAR',
52
+ reference: `cli-test-${timestamp}`,
53
+ customer: testCustomer,
54
+ urls: testUrls,
55
+ });
56
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
57
+ },
58
+ },
59
+ {
60
+ name: 'ozow',
61
+ envRequired: ['OZOW_API_KEY', 'OZOW_SITE_CODE', 'OZOW_PRIVATE_KEY'],
62
+ run: async () => {
63
+ const pay = new index_1.PayBridge({
64
+ provider: 'ozow',
65
+ credentials: {
66
+ apiKey: process.env.OZOW_API_KEY,
67
+ siteCode: process.env.OZOW_SITE_CODE,
68
+ privateKey: process.env.OZOW_PRIVATE_KEY,
69
+ },
70
+ sandbox: true,
71
+ });
72
+ const payment = await pay.createPayment({
73
+ amount: 1.0,
74
+ currency: 'ZAR',
75
+ reference: `cli-test-${timestamp}`,
76
+ customer: testCustomer,
77
+ urls: testUrls,
78
+ });
79
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
80
+ },
81
+ },
82
+ {
83
+ name: 'payfast',
84
+ envRequired: ['PAYFAST_MERCHANT_ID', 'PAYFAST_MERCHANT_KEY'],
85
+ run: async () => {
86
+ const pay = new index_1.PayBridge({
87
+ provider: 'payfast',
88
+ credentials: {
89
+ merchantId: process.env.PAYFAST_MERCHANT_ID,
90
+ merchantKey: process.env.PAYFAST_MERCHANT_KEY,
91
+ passphrase: process.env.PAYFAST_PASSPHRASE,
92
+ },
93
+ sandbox: true,
94
+ });
95
+ const payment = await pay.createPayment({
96
+ amount: 1.0,
97
+ currency: 'ZAR',
98
+ reference: `cli-test-${timestamp}`,
99
+ customer: testCustomer,
100
+ urls: testUrls,
101
+ });
102
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
103
+ },
104
+ },
105
+ {
106
+ name: 'paystack',
107
+ envRequired: ['PAYSTACK_API_KEY'],
108
+ run: async () => {
109
+ const pay = new index_1.PayBridge({
110
+ provider: 'paystack',
111
+ credentials: { apiKey: process.env.PAYSTACK_API_KEY },
112
+ sandbox: true,
113
+ });
114
+ const payment = await pay.createPayment({
115
+ amount: 1.0,
116
+ currency: 'NGN',
117
+ reference: `cli-test-${timestamp}`,
118
+ customer: testCustomer,
119
+ urls: testUrls,
120
+ });
121
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
122
+ },
123
+ },
124
+ {
125
+ name: 'stripe',
126
+ envRequired: ['STRIPE_API_KEY'],
127
+ run: async () => {
128
+ const pay = new index_1.PayBridge({
129
+ provider: 'stripe',
130
+ credentials: { apiKey: process.env.STRIPE_API_KEY },
131
+ sandbox: true,
132
+ });
133
+ const payment = await pay.createPayment({
134
+ amount: 1.0,
135
+ currency: 'USD',
136
+ reference: `cli-test-${timestamp}`,
137
+ customer: testCustomer,
138
+ urls: testUrls,
139
+ });
140
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
141
+ },
142
+ },
143
+ {
144
+ name: 'peach',
145
+ envRequired: ['PEACH_ACCESS_TOKEN', 'PEACH_ENTITY_ID'],
146
+ run: async () => {
147
+ const pay = new index_1.PayBridge({
148
+ provider: 'peach',
149
+ credentials: {
150
+ apiKey: process.env.PEACH_ACCESS_TOKEN,
151
+ secretKey: process.env.PEACH_ENTITY_ID,
152
+ },
153
+ sandbox: true,
154
+ });
155
+ const payment = await pay.createPayment({
156
+ amount: 1.0,
157
+ currency: 'ZAR',
158
+ reference: `cli-test-${timestamp}`,
159
+ customer: testCustomer,
160
+ urls: testUrls,
161
+ });
162
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
163
+ },
164
+ },
165
+ {
166
+ name: 'flutterwave',
167
+ envRequired: ['FLUTTERWAVE_API_KEY'],
168
+ run: async () => {
169
+ const pay = new index_1.PayBridge({
170
+ provider: 'flutterwave',
171
+ credentials: { apiKey: process.env.FLUTTERWAVE_API_KEY },
172
+ sandbox: true,
173
+ });
174
+ const payment = await pay.createPayment({
175
+ amount: 1.0,
176
+ currency: 'NGN',
177
+ reference: `cli-test-${timestamp}`,
178
+ customer: testCustomer,
179
+ urls: testUrls,
180
+ });
181
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
182
+ },
183
+ },
184
+ {
185
+ name: 'adyen',
186
+ envRequired: ['ADYEN_API_KEY', 'ADYEN_MERCHANT_ACCOUNT'],
187
+ run: async () => {
188
+ const pay = new index_1.PayBridge({
189
+ provider: 'adyen',
190
+ credentials: {
191
+ apiKey: process.env.ADYEN_API_KEY,
192
+ merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
193
+ },
194
+ sandbox: true,
195
+ });
196
+ const payment = await pay.createPayment({
197
+ amount: 1.0,
198
+ currency: 'EUR',
199
+ reference: `cli-test-${timestamp}`,
200
+ customer: testCustomer,
201
+ urls: testUrls,
202
+ });
203
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
204
+ },
205
+ },
206
+ {
207
+ name: 'mercadopago',
208
+ envRequired: ['MERCADOPAGO_ACCESS_TOKEN'],
209
+ run: async () => {
210
+ const pay = new index_1.PayBridge({
211
+ provider: 'mercadopago',
212
+ credentials: { apiKey: process.env.MERCADOPAGO_ACCESS_TOKEN },
213
+ sandbox: true,
214
+ });
215
+ const payment = await pay.createPayment({
216
+ amount: 1.0,
217
+ currency: 'BRL',
218
+ reference: `cli-test-${timestamp}`,
219
+ customer: testCustomer,
220
+ urls: testUrls,
221
+ });
222
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
223
+ },
224
+ },
225
+ {
226
+ name: 'razorpay',
227
+ envRequired: ['RAZORPAY_KEY_ID', 'RAZORPAY_KEY_SECRET'],
228
+ run: async () => {
229
+ const pay = new index_1.PayBridge({
230
+ provider: 'razorpay',
231
+ credentials: {
232
+ apiKey: process.env.RAZORPAY_KEY_ID,
233
+ secretKey: process.env.RAZORPAY_KEY_SECRET,
234
+ },
235
+ sandbox: true,
236
+ });
237
+ const payment = await pay.createPayment({
238
+ amount: 1.0,
239
+ currency: 'INR',
240
+ reference: `cli-test-${timestamp}`,
241
+ customer: testCustomer,
242
+ urls: testUrls,
243
+ });
244
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
245
+ },
246
+ },
247
+ {
248
+ name: 'mollie',
249
+ envRequired: ['MOLLIE_API_KEY'],
250
+ run: async () => {
251
+ const pay = new index_1.PayBridge({
252
+ provider: 'mollie',
253
+ credentials: { apiKey: process.env.MOLLIE_API_KEY },
254
+ sandbox: true,
255
+ });
256
+ const payment = await pay.createPayment({
257
+ amount: 1.0,
258
+ currency: 'EUR',
259
+ reference: `cli-test-${timestamp}`,
260
+ customer: testCustomer,
261
+ urls: testUrls,
262
+ });
263
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
264
+ },
265
+ },
266
+ {
267
+ name: 'square',
268
+ envRequired: ['SQUARE_ACCESS_TOKEN', 'SQUARE_LOCATION_ID'],
269
+ run: async () => {
270
+ const pay = new index_1.PayBridge({
271
+ provider: 'square',
272
+ credentials: {
273
+ apiKey: process.env.SQUARE_ACCESS_TOKEN,
274
+ locationId: process.env.SQUARE_LOCATION_ID,
275
+ },
276
+ sandbox: true,
277
+ });
278
+ const payment = await pay.createPayment({
279
+ amount: 1.0,
280
+ currency: 'USD',
281
+ reference: `cli-test-${timestamp}`,
282
+ customer: testCustomer,
283
+ urls: testUrls,
284
+ });
285
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
286
+ },
287
+ },
288
+ {
289
+ name: 'pesapal',
290
+ envRequired: ['PESAPAL_CONSUMER_KEY', 'PESAPAL_CONSUMER_SECRET'],
291
+ run: async () => {
292
+ const pay = new index_1.PayBridge({
293
+ provider: 'pesapal',
294
+ credentials: {
295
+ apiKey: process.env.PESAPAL_CONSUMER_KEY,
296
+ secretKey: process.env.PESAPAL_CONSUMER_SECRET,
297
+ },
298
+ sandbox: true,
299
+ });
300
+ const payment = await pay.createPayment({
301
+ amount: 1.0,
302
+ currency: 'KES',
303
+ reference: `cli-test-${timestamp}`,
304
+ customer: testCustomer,
305
+ urls: testUrls,
306
+ });
307
+ return { id: payment.id, checkoutUrl: payment.checkoutUrl, status: payment.status };
308
+ },
309
+ },
310
+ {
311
+ name: 'moonpay',
312
+ envRequired: ['MOONPAY_API_KEY', 'MOONPAY_SECRET_KEY'],
313
+ run: async () => {
314
+ const ramp = new crypto_1.CryptoRamp({
315
+ provider: 'moonpay',
316
+ credentials: {
317
+ apiKey: process.env.MOONPAY_API_KEY,
318
+ secretKey: process.env.MOONPAY_SECRET_KEY,
319
+ },
320
+ sandbox: true,
321
+ });
322
+ const result = await ramp.createOnRamp({
323
+ fiatAmount: 1.0,
324
+ fiatCurrency: 'USD',
325
+ asset: 'BTC',
326
+ network: 'BTC',
327
+ destinationWallet: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
328
+ customer: testCustomer,
329
+ urls: testUrls,
330
+ reference: `cli-test-${timestamp}`,
331
+ });
332
+ return { id: result.id, checkoutUrl: result.checkoutUrl, status: result.status };
333
+ },
334
+ },
335
+ ];
@@ -0,0 +1,16 @@
1
+ export declare const colors: {
2
+ reset: string;
3
+ bright: string;
4
+ dim: string;
5
+ green: string;
6
+ yellow: string;
7
+ red: string;
8
+ cyan: string;
9
+ };
10
+ export declare function colorize(text: string, color: keyof typeof colors): string;
11
+ export declare function padEnd(str: string, length: number): string;
12
+ export declare function formatTable(rows: string[][], padding?: number): string;
13
+ export declare function readStdin(): Promise<string>;
14
+ export declare function parseHeaders(headerFlags: string[]): Record<string, string>;
15
+ export declare function printHelp(toStderr?: boolean): void;
16
+ export declare function printVersion(): void;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.colors = void 0;
4
+ exports.colorize = colorize;
5
+ exports.padEnd = padEnd;
6
+ exports.formatTable = formatTable;
7
+ exports.readStdin = readStdin;
8
+ exports.parseHeaders = parseHeaders;
9
+ exports.printHelp = printHelp;
10
+ exports.printVersion = printVersion;
11
+ exports.colors = {
12
+ reset: '\x1b[0m',
13
+ bright: '\x1b[1m',
14
+ dim: '\x1b[2m',
15
+ green: '\x1b[32m',
16
+ yellow: '\x1b[33m',
17
+ red: '\x1b[31m',
18
+ cyan: '\x1b[36m',
19
+ };
20
+ function colorize(text, color) {
21
+ return `${exports.colors[color]}${text}${exports.colors.reset}`;
22
+ }
23
+ function padEnd(str, length) {
24
+ while (str.length < length) {
25
+ str += ' ';
26
+ }
27
+ return str;
28
+ }
29
+ function formatTable(rows, padding = 2) {
30
+ if (rows.length === 0)
31
+ return '';
32
+ const colWidths = [];
33
+ for (const row of rows) {
34
+ row.forEach((cell, i) => {
35
+ colWidths[i] = Math.max(colWidths[i] || 0, cell.length);
36
+ });
37
+ }
38
+ return rows
39
+ .map((row) => row.map((cell, i) => padEnd(cell, colWidths[i])).join(' '.repeat(padding)))
40
+ .join('\n');
41
+ }
42
+ async function readStdin() {
43
+ const chunks = [];
44
+ for await (const chunk of process.stdin) {
45
+ chunks.push(chunk);
46
+ }
47
+ return Buffer.concat(chunks).toString('utf8');
48
+ }
49
+ function parseHeaders(headerFlags) {
50
+ const headers = {};
51
+ for (const flag of headerFlags) {
52
+ const [key, ...valueParts] = flag.split('=');
53
+ if (key && valueParts.length > 0) {
54
+ headers[key.trim()] = valueParts.join('=').trim();
55
+ }
56
+ }
57
+ return headers;
58
+ }
59
+ function printHelp(toStderr = false) {
60
+ const output = `
61
+ paybridge — unified payment SDK CLI
62
+
63
+ USAGE
64
+ paybridge <command> [options]
65
+
66
+ COMMANDS
67
+ test <provider> Run sandbox createPayment validation for a provider
68
+ test --all Run validation for every provider with env vars set
69
+ providers [--json] List all providers with capabilities (table or JSON)
70
+ webhook verify <p> Verify webhook signature (raw body from stdin)
71
+ webhook parse <p> Parse webhook event (raw body from stdin)
72
+ quote <p> [opts] Get a crypto on/off-ramp quote
73
+ help, -h, --help Print this help
74
+ version, -v Print version
75
+
76
+ PROVIDER ENV VARS
77
+ See SETUP.md or run 'paybridge test --all' for the full list.
78
+
79
+ EXAMPLES
80
+ paybridge providers
81
+ paybridge test stripe
82
+ STRIPE_API_KEY=sk_test_... paybridge test stripe
83
+ echo '{"id":"evt_x"}' | paybridge webhook parse paystack \\
84
+ --header x-paystack-signature=abc
85
+
86
+ Docs: https://github.com/kobie3717/paybridge
87
+ `.trim();
88
+ if (toStderr) {
89
+ console.error(output);
90
+ }
91
+ else {
92
+ console.log(output);
93
+ }
94
+ }
95
+ function printVersion() {
96
+ const pkg = require('../../package.json');
97
+ console.log(`paybridge v${pkg.version}`);
98
+ }
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "paybridge",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "One API for fiat + crypto payments. Multi-provider routing, automatic failover, MoonPay on/off-ramp. SA-first, global-ready.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "paybridge": "dist/cli/index.js"
9
+ },
7
10
  "scripts": {
8
11
  "build": "tsc",
12
+ "build:cli": "npm run build && node scripts/post-build.js",
9
13
  "clean": "rm -rf dist",
10
- "prepack": "npm run clean && npm run build",
11
- "prepublishOnly": "npm run clean && npm run build",
14
+ "cli": "node dist/cli/index.js",
15
+ "prepack": "npm run clean && npm run build:cli",
16
+ "prepublishOnly": "npm run clean && npm run build:cli",
12
17
  "test": "tsc && tsc --project tsconfig.test.json && node --test 'dist-test/**/*.test.js'",
13
18
  "test:e2e:moonpay": "tsx tests/e2e/moonpay-sandbox.ts",
14
19
  "test:e2e:yellowcard": "tsx tests/e2e/yellowcard-sandbox.ts",
@@ -39,7 +44,9 @@
39
44
  "pesapal",
40
45
  "transak",
41
46
  "ramp",
42
- "east-africa"
47
+ "east-africa",
48
+ "cli",
49
+ "command-line"
43
50
  ],
44
51
  "author": "Kobie Wentzel",
45
52
  "license": "MIT",