@profullstack/coinpay 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/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # @profullstack/coinpay
2
+
3
+ CoinPay SDK & CLI - Cryptocurrency payment integration for Node.js
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Using pnpm (recommended)
9
+ pnpm add @profullstack/coinpay
10
+
11
+ # Using npm
12
+ npm install @profullstack/coinpay
13
+
14
+ # Global CLI installation
15
+ pnpm add -g @profullstack/coinpay
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### SDK Usage
21
+
22
+ ```javascript
23
+ import { CoinPayClient } from '@profullstack/coinpay';
24
+
25
+ // Initialize the client
26
+ const coinpay = new CoinPayClient({
27
+ apiKey: 'your-api-key',
28
+ });
29
+
30
+ // Create a payment
31
+ const payment = await coinpay.createPayment({
32
+ businessId: 'biz_123',
33
+ amount: 100,
34
+ currency: 'USD',
35
+ cryptocurrency: 'BTC',
36
+ description: 'Order #12345',
37
+ });
38
+
39
+ console.log(`Payment address: ${payment.address}`);
40
+ console.log(`Amount: ${payment.cryptoAmount} ${payment.cryptocurrency}`);
41
+ ```
42
+
43
+ ### CLI Usage
44
+
45
+ ```bash
46
+ # Configure your API key
47
+ coinpay config set-key sk_live_xxxxx
48
+
49
+ # Create a payment
50
+ coinpay payment create --business-id biz_123 --amount 100 --currency USD --crypto BTC
51
+
52
+ # Get payment details
53
+ coinpay payment get pay_abc123
54
+
55
+ # List payments
56
+ coinpay payment list --business-id biz_123
57
+
58
+ # Get exchange rates
59
+ coinpay rates get BTC
60
+ ```
61
+
62
+ ## SDK API Reference
63
+
64
+ ### CoinPayClient
65
+
66
+ ```javascript
67
+ import { CoinPayClient } from '@profullstack/coinpay';
68
+
69
+ const client = new CoinPayClient({
70
+ apiKey: 'your-api-key',
71
+ baseUrl: 'https://coinpay.dev/api', // optional
72
+ timeout: 30000, // optional, in milliseconds
73
+ });
74
+ ```
75
+
76
+ ### Payments
77
+
78
+ ```javascript
79
+ // Create a payment
80
+ const payment = await client.createPayment({
81
+ businessId: 'biz_123',
82
+ amount: 100,
83
+ currency: 'USD',
84
+ cryptocurrency: 'BTC',
85
+ description: 'Order #12345',
86
+ metadata: JSON.stringify({ orderId: '12345' }),
87
+ callbackUrl: 'https://your-site.com/webhook',
88
+ });
89
+
90
+ // Get payment by ID
91
+ const payment = await client.getPayment('pay_abc123');
92
+
93
+ // List payments
94
+ const payments = await client.listPayments({
95
+ businessId: 'biz_123',
96
+ status: 'completed', // optional
97
+ limit: 20, // optional
98
+ offset: 0, // optional
99
+ });
100
+
101
+ // Get payment QR code
102
+ const qr = await client.getPaymentQR('pay_abc123', 'png');
103
+ ```
104
+
105
+ ### Exchange Rates
106
+
107
+ ```javascript
108
+ // Get single rate
109
+ const rate = await client.getExchangeRate('BTC', 'USD');
110
+
111
+ // Get multiple rates
112
+ const rates = await client.getExchangeRates(['BTC', 'ETH', 'SOL'], 'USD');
113
+ ```
114
+
115
+ ### Businesses
116
+
117
+ ```javascript
118
+ // Create a business
119
+ const business = await client.createBusiness({
120
+ name: 'My Store',
121
+ webhookUrl: 'https://your-site.com/webhook',
122
+ walletAddresses: {
123
+ BTC: 'bc1q...',
124
+ ETH: '0x...',
125
+ },
126
+ });
127
+
128
+ // Get business
129
+ const business = await client.getBusiness('biz_123');
130
+
131
+ // List businesses
132
+ const businesses = await client.listBusinesses();
133
+
134
+ // Update business
135
+ const updated = await client.updateBusiness('biz_123', {
136
+ name: 'Updated Name',
137
+ });
138
+ ```
139
+
140
+ ### Webhooks
141
+
142
+ ```javascript
143
+ // Get webhook logs
144
+ const logs = await client.getWebhookLogs('biz_123', 50);
145
+
146
+ // Test webhook
147
+ const result = await client.testWebhook('biz_123', 'payment.completed');
148
+ ```
149
+
150
+ ## Webhook Verification
151
+
152
+ ```javascript
153
+ import { verifyWebhookSignature, createWebhookHandler } from '@profullstack/coinpay';
154
+
155
+ // Manual verification
156
+ const isValid = verifyWebhookSignature({
157
+ payload: rawBody,
158
+ signature: req.headers['x-coinpay-signature'],
159
+ secret: 'your-webhook-secret',
160
+ });
161
+
162
+ // Express middleware
163
+ app.post('/webhook', createWebhookHandler({
164
+ secret: 'your-webhook-secret',
165
+ onEvent: async (event) => {
166
+ console.log('Received event:', event.type);
167
+
168
+ switch (event.type) {
169
+ case 'payment.completed':
170
+ // Handle completed payment
171
+ break;
172
+ case 'payment.expired':
173
+ // Handle expired payment
174
+ break;
175
+ }
176
+ },
177
+ onError: (error) => {
178
+ console.error('Webhook error:', error);
179
+ },
180
+ }));
181
+ ```
182
+
183
+ ## Webhook Events
184
+
185
+ | Event | Description |
186
+ |-------|-------------|
187
+ | `payment.created` | Payment was created |
188
+ | `payment.pending` | Payment is pending confirmation |
189
+ | `payment.confirming` | Payment is being confirmed |
190
+ | `payment.completed` | Payment completed successfully |
191
+ | `payment.expired` | Payment expired |
192
+ | `payment.failed` | Payment failed |
193
+ | `payment.refunded` | Payment was refunded |
194
+
195
+ ## CLI Commands
196
+
197
+ ### Configuration
198
+
199
+ ```bash
200
+ coinpay config set-key <api-key> # Set API key
201
+ coinpay config set-url <base-url> # Set custom API URL
202
+ coinpay config show # Show current config
203
+ ```
204
+
205
+ ### Payments
206
+
207
+ ```bash
208
+ coinpay payment create [options]
209
+ --business-id <id> Business ID (required)
210
+ --amount <amount> Amount in fiat (required)
211
+ --currency <code> Fiat currency (required)
212
+ --crypto <code> Cryptocurrency (required)
213
+ --description <text> Description (optional)
214
+
215
+ coinpay payment get <payment-id>
216
+ coinpay payment list --business-id <id> [--status <status>] [--limit <n>]
217
+ coinpay payment qr <payment-id> [--format png|svg]
218
+ ```
219
+
220
+ ### Businesses
221
+
222
+ ```bash
223
+ coinpay business create --name <name> [--webhook-url <url>]
224
+ coinpay business get <business-id>
225
+ coinpay business list
226
+ coinpay business update <business-id> [--name <name>] [--webhook-url <url>]
227
+ ```
228
+
229
+ ### Exchange Rates
230
+
231
+ ```bash
232
+ coinpay rates get <crypto> [--fiat <currency>]
233
+ coinpay rates list [--fiat <currency>]
234
+ ```
235
+
236
+ ### Webhooks
237
+
238
+ ```bash
239
+ coinpay webhook logs <business-id> [--limit <n>]
240
+ coinpay webhook test <business-id> [--event <type>]
241
+ ```
242
+
243
+ ## Supported Cryptocurrencies
244
+
245
+ - **BTC** - Bitcoin
246
+ - **BCH** - Bitcoin Cash
247
+ - **ETH** - Ethereum
248
+ - **MATIC** - Polygon
249
+ - **SOL** - Solana
250
+
251
+ ## Supported Fiat Currencies
252
+
253
+ - USD, EUR, GBP, CAD, AUD, and more
254
+
255
+ ## Environment Variables
256
+
257
+ | Variable | Description |
258
+ |----------|-------------|
259
+ | `COINPAY_API_KEY` | API key (overrides config file) |
260
+ | `COINPAY_BASE_URL` | Custom API URL |
261
+
262
+ ## Error Handling
263
+
264
+ ```javascript
265
+ try {
266
+ const payment = await client.createPayment({ ... });
267
+ } catch (error) {
268
+ if (error.status === 401) {
269
+ console.error('Invalid API key');
270
+ } else if (error.status === 400) {
271
+ console.error('Invalid request:', error.response);
272
+ } else {
273
+ console.error('Error:', error.message);
274
+ }
275
+ }
276
+ ```
277
+
278
+ ## License
279
+
280
+ MIT
package/bin/coinpay.js ADDED
@@ -0,0 +1,485 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CoinPay CLI
5
+ * Command-line interface for CoinPay cryptocurrency payments
6
+ */
7
+
8
+ import { CoinPayClient } from '../src/client.js';
9
+ import { PaymentStatus, Cryptocurrency, FiatCurrency } from '../src/payments.js';
10
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
11
+ import { homedir } from 'os';
12
+ import { join } from 'path';
13
+
14
+ const VERSION = '0.1.0';
15
+ const CONFIG_FILE = join(homedir(), '.coinpay.json');
16
+
17
+ // ANSI colors
18
+ const colors = {
19
+ reset: '\x1b[0m',
20
+ bright: '\x1b[1m',
21
+ red: '\x1b[31m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ cyan: '\x1b[36m',
26
+ };
27
+
28
+ /**
29
+ * Print colored output
30
+ */
31
+ const print = {
32
+ error: (msg) => console.error(`${colors.red}Error:${colors.reset} ${msg}`),
33
+ success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
34
+ info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`),
35
+ warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
36
+ json: (data) => console.log(JSON.stringify(data, null, 2)),
37
+ };
38
+
39
+ /**
40
+ * Load configuration
41
+ */
42
+ function loadConfig() {
43
+ try {
44
+ if (existsSync(CONFIG_FILE)) {
45
+ return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
46
+ }
47
+ } catch {
48
+ // Ignore errors
49
+ }
50
+ return {};
51
+ }
52
+
53
+ /**
54
+ * Save configuration
55
+ */
56
+ function saveConfig(config) {
57
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
58
+ }
59
+
60
+ /**
61
+ * Get API key from config or environment
62
+ */
63
+ function getApiKey() {
64
+ const config = loadConfig();
65
+ return process.env.COINPAY_API_KEY || config.apiKey;
66
+ }
67
+
68
+ /**
69
+ * Get base URL from config or environment
70
+ */
71
+ function getBaseUrl() {
72
+ const config = loadConfig();
73
+ return process.env.COINPAY_BASE_URL || config.baseUrl || 'https://coinpay.dev/api';
74
+ }
75
+
76
+ /**
77
+ * Create client instance
78
+ */
79
+ function createClient() {
80
+ const apiKey = getApiKey();
81
+ if (!apiKey) {
82
+ print.error('API key not configured. Run: coinpay config set-key <api-key>');
83
+ process.exit(1);
84
+ }
85
+ return new CoinPayClient({ apiKey, baseUrl: getBaseUrl() });
86
+ }
87
+
88
+ /**
89
+ * Parse command line arguments
90
+ */
91
+ function parseArgs(args) {
92
+ const result = { command: null, subcommand: null, args: [], flags: {} };
93
+
94
+ for (let i = 0; i < args.length; i++) {
95
+ const arg = args[i];
96
+
97
+ if (arg.startsWith('--')) {
98
+ const [key, value] = arg.slice(2).split('=');
99
+ result.flags[key] = value ?? true;
100
+ } else if (arg.startsWith('-')) {
101
+ result.flags[arg.slice(1)] = args[++i] ?? true;
102
+ } else if (!result.command) {
103
+ result.command = arg;
104
+ } else if (!result.subcommand) {
105
+ result.subcommand = arg;
106
+ } else {
107
+ result.args.push(arg);
108
+ }
109
+ }
110
+
111
+ return result;
112
+ }
113
+
114
+ /**
115
+ * Show help
116
+ */
117
+ function showHelp() {
118
+ console.log(`
119
+ ${colors.bright}CoinPay CLI${colors.reset} v${VERSION}
120
+
121
+ ${colors.cyan}Usage:${colors.reset}
122
+ coinpay <command> [options]
123
+
124
+ ${colors.cyan}Commands:${colors.reset}
125
+ ${colors.bright}config${colors.reset}
126
+ set-key <api-key> Set your API key
127
+ set-url <base-url> Set custom API URL
128
+ show Show current configuration
129
+
130
+ ${colors.bright}payment${colors.reset}
131
+ create Create a new payment
132
+ get <id> Get payment details
133
+ list List payments
134
+ qr <id> Get payment QR code
135
+
136
+ ${colors.bright}business${colors.reset}
137
+ create Create a new business
138
+ get <id> Get business details
139
+ list List businesses
140
+ update <id> Update business
141
+
142
+ ${colors.bright}rates${colors.reset}
143
+ get <crypto> Get exchange rate
144
+ list Get all exchange rates
145
+
146
+ ${colors.bright}webhook${colors.reset}
147
+ logs <business-id> Get webhook logs
148
+ test <business-id> Send test webhook
149
+
150
+ ${colors.cyan}Options:${colors.reset}
151
+ --help, -h Show help
152
+ --version, -v Show version
153
+ --json Output as JSON
154
+ --business-id <id> Business ID for operations
155
+ --amount <amount> Payment amount
156
+ --currency <code> Fiat currency (USD, EUR, etc.)
157
+ --crypto <code> Cryptocurrency (BTC, ETH, etc.)
158
+
159
+ ${colors.cyan}Examples:${colors.reset}
160
+ coinpay config set-key sk_live_xxxxx
161
+ coinpay payment create --business-id biz_123 --amount 100 --currency USD --crypto BTC
162
+ coinpay payment get pay_abc123
163
+ coinpay rates get BTC
164
+ coinpay business list
165
+
166
+ ${colors.cyan}Environment Variables:${colors.reset}
167
+ COINPAY_API_KEY API key (overrides config)
168
+ COINPAY_BASE_URL Custom API URL
169
+ `);
170
+ }
171
+
172
+ /**
173
+ * Config commands
174
+ */
175
+ async function handleConfig(subcommand, args) {
176
+ const config = loadConfig();
177
+
178
+ switch (subcommand) {
179
+ case 'set-key':
180
+ if (!args[0]) {
181
+ print.error('API key required');
182
+ return;
183
+ }
184
+ config.apiKey = args[0];
185
+ saveConfig(config);
186
+ print.success('API key saved');
187
+ break;
188
+
189
+ case 'set-url':
190
+ if (!args[0]) {
191
+ print.error('Base URL required');
192
+ return;
193
+ }
194
+ config.baseUrl = args[0];
195
+ saveConfig(config);
196
+ print.success('Base URL saved');
197
+ break;
198
+
199
+ case 'show':
200
+ print.info(`Config file: ${CONFIG_FILE}`);
201
+ print.json({
202
+ apiKey: config.apiKey ? `${config.apiKey.slice(0, 10)}...` : '(not set)',
203
+ baseUrl: config.baseUrl || '(default)',
204
+ });
205
+ break;
206
+
207
+ default:
208
+ print.error(`Unknown config command: ${subcommand}`);
209
+ showHelp();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Payment commands
215
+ */
216
+ async function handlePayment(subcommand, args, flags) {
217
+ const client = createClient();
218
+
219
+ switch (subcommand) {
220
+ case 'create': {
221
+ const { 'business-id': businessId, amount, currency, crypto, description } = flags;
222
+
223
+ if (!businessId || !amount || !currency || !crypto) {
224
+ print.error('Required: --business-id, --amount, --currency, --crypto');
225
+ return;
226
+ }
227
+
228
+ const payment = await client.createPayment({
229
+ businessId,
230
+ amount: parseFloat(amount),
231
+ currency,
232
+ cryptocurrency: crypto,
233
+ description,
234
+ });
235
+
236
+ print.success('Payment created');
237
+ print.json(payment);
238
+ break;
239
+ }
240
+
241
+ case 'get': {
242
+ const paymentId = args[0];
243
+ if (!paymentId) {
244
+ print.error('Payment ID required');
245
+ return;
246
+ }
247
+
248
+ const payment = await client.getPayment(paymentId);
249
+ print.json(payment);
250
+ break;
251
+ }
252
+
253
+ case 'list': {
254
+ const { 'business-id': businessId, status, limit } = flags;
255
+
256
+ if (!businessId) {
257
+ print.error('Required: --business-id');
258
+ return;
259
+ }
260
+
261
+ const payments = await client.listPayments({
262
+ businessId,
263
+ status,
264
+ limit: limit ? parseInt(limit, 10) : undefined,
265
+ });
266
+
267
+ print.json(payments);
268
+ break;
269
+ }
270
+
271
+ case 'qr': {
272
+ const paymentId = args[0];
273
+ const { format } = flags;
274
+
275
+ if (!paymentId) {
276
+ print.error('Payment ID required');
277
+ return;
278
+ }
279
+
280
+ const qr = await client.getPaymentQR(paymentId, format);
281
+ print.json(qr);
282
+ break;
283
+ }
284
+
285
+ default:
286
+ print.error(`Unknown payment command: ${subcommand}`);
287
+ showHelp();
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Business commands
293
+ */
294
+ async function handleBusiness(subcommand, args, flags) {
295
+ const client = createClient();
296
+
297
+ switch (subcommand) {
298
+ case 'create': {
299
+ const { name, 'webhook-url': webhookUrl } = flags;
300
+
301
+ if (!name) {
302
+ print.error('Required: --name');
303
+ return;
304
+ }
305
+
306
+ const business = await client.createBusiness({ name, webhookUrl });
307
+ print.success('Business created');
308
+ print.json(business);
309
+ break;
310
+ }
311
+
312
+ case 'get': {
313
+ const businessId = args[0];
314
+ if (!businessId) {
315
+ print.error('Business ID required');
316
+ return;
317
+ }
318
+
319
+ const business = await client.getBusiness(businessId);
320
+ print.json(business);
321
+ break;
322
+ }
323
+
324
+ case 'list': {
325
+ const businesses = await client.listBusinesses();
326
+ print.json(businesses);
327
+ break;
328
+ }
329
+
330
+ case 'update': {
331
+ const businessId = args[0];
332
+ const { name, 'webhook-url': webhookUrl } = flags;
333
+
334
+ if (!businessId) {
335
+ print.error('Business ID required');
336
+ return;
337
+ }
338
+
339
+ const updates = {};
340
+ if (name) updates.name = name;
341
+ if (webhookUrl) updates.webhookUrl = webhookUrl;
342
+
343
+ const business = await client.updateBusiness(businessId, updates);
344
+ print.success('Business updated');
345
+ print.json(business);
346
+ break;
347
+ }
348
+
349
+ default:
350
+ print.error(`Unknown business command: ${subcommand}`);
351
+ showHelp();
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Rates commands
357
+ */
358
+ async function handleRates(subcommand, args, flags) {
359
+ const client = createClient();
360
+
361
+ switch (subcommand) {
362
+ case 'get': {
363
+ const crypto = args[0];
364
+ const { fiat } = flags;
365
+
366
+ if (!crypto) {
367
+ print.error('Cryptocurrency code required (BTC, ETH, etc.)');
368
+ return;
369
+ }
370
+
371
+ const rate = await client.getExchangeRate(crypto, fiat);
372
+ print.json(rate);
373
+ break;
374
+ }
375
+
376
+ case 'list': {
377
+ const { fiat } = flags;
378
+ const cryptos = Object.values(Cryptocurrency);
379
+ const rates = await client.getExchangeRates(cryptos, fiat);
380
+ print.json(rates);
381
+ break;
382
+ }
383
+
384
+ default:
385
+ print.error(`Unknown rates command: ${subcommand}`);
386
+ showHelp();
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Webhook commands
392
+ */
393
+ async function handleWebhook(subcommand, args, flags) {
394
+ const client = createClient();
395
+
396
+ switch (subcommand) {
397
+ case 'logs': {
398
+ const businessId = args[0];
399
+ const { limit } = flags;
400
+
401
+ if (!businessId) {
402
+ print.error('Business ID required');
403
+ return;
404
+ }
405
+
406
+ const logs = await client.getWebhookLogs(businessId, limit ? parseInt(limit, 10) : undefined);
407
+ print.json(logs);
408
+ break;
409
+ }
410
+
411
+ case 'test': {
412
+ const businessId = args[0];
413
+ const { event } = flags;
414
+
415
+ if (!businessId) {
416
+ print.error('Business ID required');
417
+ return;
418
+ }
419
+
420
+ const result = await client.testWebhook(businessId, event);
421
+ print.success('Test webhook sent');
422
+ print.json(result);
423
+ break;
424
+ }
425
+
426
+ default:
427
+ print.error(`Unknown webhook command: ${subcommand}`);
428
+ showHelp();
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Main entry point
434
+ */
435
+ async function main() {
436
+ const { command, subcommand, args, flags } = parseArgs(process.argv.slice(2));
437
+
438
+ // Handle global flags
439
+ if (flags.version || flags.v) {
440
+ console.log(VERSION);
441
+ return;
442
+ }
443
+
444
+ if (flags.help || flags.h || !command) {
445
+ showHelp();
446
+ return;
447
+ }
448
+
449
+ try {
450
+ switch (command) {
451
+ case 'config':
452
+ await handleConfig(subcommand, args);
453
+ break;
454
+
455
+ case 'payment':
456
+ await handlePayment(subcommand, args, flags);
457
+ break;
458
+
459
+ case 'business':
460
+ await handleBusiness(subcommand, args, flags);
461
+ break;
462
+
463
+ case 'rates':
464
+ await handleRates(subcommand, args, flags);
465
+ break;
466
+
467
+ case 'webhook':
468
+ await handleWebhook(subcommand, args, flags);
469
+ break;
470
+
471
+ default:
472
+ print.error(`Unknown command: ${command}`);
473
+ showHelp();
474
+ process.exit(1);
475
+ }
476
+ } catch (error) {
477
+ print.error(error.message);
478
+ if (flags.debug) {
479
+ console.error(error);
480
+ }
481
+ process.exit(1);
482
+ }
483
+ }
484
+
485
+ main();
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@profullstack/coinpay",
3
+ "version": "0.1.0",
4
+ "description": "CoinPay SDK & CLI - Cryptocurrency payment integration for Node.js",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "bin": {
8
+ "coinpay": "./bin/coinpay.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./src/index.js",
13
+ "types": "./src/index.d.ts"
14
+ },
15
+ "./payments": {
16
+ "import": "./src/payments.js",
17
+ "types": "./src/payments.d.ts"
18
+ },
19
+ "./webhooks": {
20
+ "import": "./src/webhooks.js",
21
+ "types": "./src/webhooks.d.ts"
22
+ }
23
+ },
24
+ "files": [
25
+ "src",
26
+ "bin",
27
+ "README.md"
28
+ ],
29
+ "keywords": [
30
+ "coinpay",
31
+ "profullstack",
32
+ "cryptocurrency",
33
+ "payments",
34
+ "bitcoin",
35
+ "ethereum",
36
+ "solana",
37
+ "polygon",
38
+ "sdk",
39
+ "cli"
40
+ ],
41
+ "author": "Profullstack, Inc.",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/profullstack/coinpayportal.git",
46
+ "directory": "packages/sdk"
47
+ },
48
+ "engines": {
49
+ "node": ">=20.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "vitest": "^2.1.8",
53
+ "eslint": "^8.57.0",
54
+ "prettier": "^3.4.1"
55
+ },
56
+ "peerDependencies": {},
57
+ "dependencies": {},
58
+ "scripts": {
59
+ "test": "vitest run",
60
+ "test:watch": "vitest",
61
+ "lint": "eslint src bin test",
62
+ "format": "prettier --write src bin test"
63
+ }
64
+ }
package/src/client.js ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * CoinPay API Client
3
+ * Main client class for interacting with the CoinPay API
4
+ */
5
+
6
+ const DEFAULT_BASE_URL = 'https://coinpay.dev/api';
7
+
8
+ /**
9
+ * CoinPay API Client
10
+ */
11
+ export class CoinPayClient {
12
+ #apiKey;
13
+ #baseUrl;
14
+ #timeout;
15
+
16
+ /**
17
+ * Create a new CoinPay client
18
+ * @param {Object} options - Client options
19
+ * @param {string} options.apiKey - Your CoinPay API key
20
+ * @param {string} [options.baseUrl] - API base URL (default: https://coinpay.dev/api)
21
+ * @param {number} [options.timeout] - Request timeout in ms (default: 30000)
22
+ */
23
+ constructor({ apiKey, baseUrl = DEFAULT_BASE_URL, timeout = 30000 }) {
24
+ if (!apiKey) {
25
+ throw new Error('API key is required');
26
+ }
27
+
28
+ this.#apiKey = apiKey;
29
+ this.#baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
30
+ this.#timeout = timeout;
31
+ }
32
+
33
+ /**
34
+ * Make an authenticated API request
35
+ * @param {string} endpoint - API endpoint
36
+ * @param {Object} [options] - Fetch options
37
+ * @returns {Promise<Object>} API response
38
+ */
39
+ async request(endpoint, options = {}) {
40
+ const url = `${this.#baseUrl}${endpoint}`;
41
+ const controller = new AbortController();
42
+ const timeoutId = setTimeout(() => controller.abort(), this.#timeout);
43
+
44
+ try {
45
+ const response = await fetch(url, {
46
+ ...options,
47
+ signal: controller.signal,
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ 'X-API-Key': this.#apiKey,
51
+ ...options.headers,
52
+ },
53
+ });
54
+
55
+ const data = await response.json();
56
+
57
+ if (!response.ok) {
58
+ const error = new Error(data.error || `HTTP ${response.status}`);
59
+ error.status = response.status;
60
+ error.response = data;
61
+ throw error;
62
+ }
63
+
64
+ return data;
65
+ } catch (error) {
66
+ if (error.name === 'AbortError') {
67
+ throw new Error(`Request timeout after ${this.#timeout}ms`);
68
+ }
69
+ throw error;
70
+ } finally {
71
+ clearTimeout(timeoutId);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Create a new payment
77
+ * @param {Object} params - Payment parameters
78
+ * @param {string} params.businessId - Business ID
79
+ * @param {number} params.amount - Amount in fiat currency
80
+ * @param {string} params.currency - Fiat currency code (USD, EUR, etc.)
81
+ * @param {string} params.cryptocurrency - Cryptocurrency code (BTC, ETH, etc.)
82
+ * @param {string} [params.description] - Payment description
83
+ * @param {string} [params.metadata] - Custom metadata (JSON string)
84
+ * @param {string} [params.callbackUrl] - Webhook callback URL
85
+ * @returns {Promise<Object>} Created payment
86
+ */
87
+ async createPayment({
88
+ businessId,
89
+ amount,
90
+ currency,
91
+ cryptocurrency,
92
+ description,
93
+ metadata,
94
+ callbackUrl,
95
+ }) {
96
+ return this.request('/payments/create', {
97
+ method: 'POST',
98
+ body: JSON.stringify({
99
+ businessId,
100
+ amount,
101
+ currency,
102
+ cryptocurrency,
103
+ description,
104
+ metadata,
105
+ callbackUrl,
106
+ }),
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Get payment by ID
112
+ * @param {string} paymentId - Payment ID
113
+ * @returns {Promise<Object>} Payment details
114
+ */
115
+ async getPayment(paymentId) {
116
+ return this.request(`/payments/${paymentId}`);
117
+ }
118
+
119
+ /**
120
+ * List payments for a business
121
+ * @param {Object} params - Query parameters
122
+ * @param {string} params.businessId - Business ID
123
+ * @param {string} [params.status] - Filter by status
124
+ * @param {number} [params.limit] - Number of results (default: 20)
125
+ * @param {number} [params.offset] - Pagination offset
126
+ * @returns {Promise<Object>} List of payments
127
+ */
128
+ async listPayments({ businessId, status, limit = 20, offset = 0 }) {
129
+ const params = new URLSearchParams({
130
+ businessId,
131
+ limit: String(limit),
132
+ offset: String(offset),
133
+ });
134
+
135
+ if (status) {
136
+ params.set('status', status);
137
+ }
138
+
139
+ return this.request(`/payments?${params.toString()}`);
140
+ }
141
+
142
+ /**
143
+ * Get payment QR code
144
+ * @param {string} paymentId - Payment ID
145
+ * @param {string} [format] - QR code format (png, svg)
146
+ * @returns {Promise<Object>} QR code data
147
+ */
148
+ async getPaymentQR(paymentId, format = 'png') {
149
+ return this.request(`/payments/${paymentId}/qr?format=${format}`);
150
+ }
151
+
152
+ /**
153
+ * Get exchange rates
154
+ * @param {string} cryptocurrency - Cryptocurrency code
155
+ * @param {string} [fiatCurrency] - Fiat currency code (default: USD)
156
+ * @returns {Promise<Object>} Exchange rate
157
+ */
158
+ async getExchangeRate(cryptocurrency, fiatCurrency = 'USD') {
159
+ return this.request(`/rates?crypto=${cryptocurrency}&fiat=${fiatCurrency}`);
160
+ }
161
+
162
+ /**
163
+ * Get multiple exchange rates
164
+ * @param {string[]} cryptocurrencies - Array of cryptocurrency codes
165
+ * @param {string} [fiatCurrency] - Fiat currency code (default: USD)
166
+ * @returns {Promise<Object>} Exchange rates
167
+ */
168
+ async getExchangeRates(cryptocurrencies, fiatCurrency = 'USD') {
169
+ return this.request('/rates/batch', {
170
+ method: 'POST',
171
+ body: JSON.stringify({
172
+ cryptocurrencies,
173
+ fiatCurrency,
174
+ }),
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Get business details
180
+ * @param {string} businessId - Business ID
181
+ * @returns {Promise<Object>} Business details
182
+ */
183
+ async getBusiness(businessId) {
184
+ return this.request(`/businesses/${businessId}`);
185
+ }
186
+
187
+ /**
188
+ * List businesses
189
+ * @returns {Promise<Object>} List of businesses
190
+ */
191
+ async listBusinesses() {
192
+ return this.request('/businesses');
193
+ }
194
+
195
+ /**
196
+ * Create a new business
197
+ * @param {Object} params - Business parameters
198
+ * @param {string} params.name - Business name
199
+ * @param {string} [params.webhookUrl] - Webhook URL
200
+ * @param {Object} [params.walletAddresses] - Wallet addresses by chain
201
+ * @returns {Promise<Object>} Created business
202
+ */
203
+ async createBusiness({ name, webhookUrl, walletAddresses }) {
204
+ return this.request('/businesses', {
205
+ method: 'POST',
206
+ body: JSON.stringify({
207
+ name,
208
+ webhookUrl,
209
+ walletAddresses,
210
+ }),
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Update business
216
+ * @param {string} businessId - Business ID
217
+ * @param {Object} params - Update parameters
218
+ * @returns {Promise<Object>} Updated business
219
+ */
220
+ async updateBusiness(businessId, params) {
221
+ return this.request(`/businesses/${businessId}`, {
222
+ method: 'PATCH',
223
+ body: JSON.stringify(params),
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Get webhook logs
229
+ * @param {string} businessId - Business ID
230
+ * @param {number} [limit] - Number of results
231
+ * @returns {Promise<Object>} Webhook logs
232
+ */
233
+ async getWebhookLogs(businessId, limit = 50) {
234
+ return this.request(`/webhooks/logs?businessId=${businessId}&limit=${limit}`);
235
+ }
236
+
237
+ /**
238
+ * Test webhook endpoint
239
+ * @param {string} businessId - Business ID
240
+ * @param {string} [eventType] - Event type to simulate
241
+ * @returns {Promise<Object>} Test result
242
+ */
243
+ async testWebhook(businessId, eventType = 'payment.completed') {
244
+ return this.request('/webhooks/test', {
245
+ method: 'POST',
246
+ body: JSON.stringify({
247
+ businessId,
248
+ eventType,
249
+ }),
250
+ });
251
+ }
252
+ }
253
+
254
+ export default CoinPayClient;
package/src/index.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CoinPay SDK
3
+ * Cryptocurrency payment integration for Node.js
4
+ *
5
+ * @module @profullstack/coinpay
6
+ */
7
+
8
+ import { CoinPayClient } from './client.js';
9
+ import { createPayment, getPayment, listPayments } from './payments.js';
10
+ import {
11
+ verifyWebhookSignature,
12
+ generateWebhookSignature,
13
+ parseWebhookPayload,
14
+ createWebhookHandler,
15
+ WebhookEvent,
16
+ } from './webhooks.js';
17
+
18
+ export {
19
+ CoinPayClient,
20
+ createPayment,
21
+ getPayment,
22
+ listPayments,
23
+ verifyWebhookSignature,
24
+ generateWebhookSignature,
25
+ parseWebhookPayload,
26
+ createWebhookHandler,
27
+ WebhookEvent,
28
+ };
29
+
30
+ export default CoinPayClient;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Payment utilities for CoinPay SDK
3
+ */
4
+
5
+ import { CoinPayClient } from './client.js';
6
+
7
+ /**
8
+ * Create a payment using a client instance or API key
9
+ * @param {Object} params - Payment parameters
10
+ * @param {string} params.apiKey - API key (if not using client)
11
+ * @param {CoinPayClient} [params.client] - Existing client instance
12
+ * @param {string} params.businessId - Business ID
13
+ * @param {number} params.amount - Amount in fiat currency
14
+ * @param {string} params.currency - Fiat currency code
15
+ * @param {string} params.cryptocurrency - Cryptocurrency code
16
+ * @param {string} [params.description] - Payment description
17
+ * @param {string} [params.metadata] - Custom metadata
18
+ * @param {string} [params.callbackUrl] - Webhook callback URL
19
+ * @returns {Promise<Object>} Created payment
20
+ */
21
+ export async function createPayment({
22
+ apiKey,
23
+ client,
24
+ businessId,
25
+ amount,
26
+ currency,
27
+ cryptocurrency,
28
+ description,
29
+ metadata,
30
+ callbackUrl,
31
+ }) {
32
+ const coinpay = client || new CoinPayClient({ apiKey });
33
+
34
+ return coinpay.createPayment({
35
+ businessId,
36
+ amount,
37
+ currency,
38
+ cryptocurrency,
39
+ description,
40
+ metadata,
41
+ callbackUrl,
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Get payment by ID
47
+ * @param {Object} params - Parameters
48
+ * @param {string} params.apiKey - API key (if not using client)
49
+ * @param {CoinPayClient} [params.client] - Existing client instance
50
+ * @param {string} params.paymentId - Payment ID
51
+ * @returns {Promise<Object>} Payment details
52
+ */
53
+ export async function getPayment({ apiKey, client, paymentId }) {
54
+ const coinpay = client || new CoinPayClient({ apiKey });
55
+ return coinpay.getPayment(paymentId);
56
+ }
57
+
58
+ /**
59
+ * List payments
60
+ * @param {Object} params - Parameters
61
+ * @param {string} params.apiKey - API key (if not using client)
62
+ * @param {CoinPayClient} [params.client] - Existing client instance
63
+ * @param {string} params.businessId - Business ID
64
+ * @param {string} [params.status] - Filter by status
65
+ * @param {number} [params.limit] - Number of results
66
+ * @param {number} [params.offset] - Pagination offset
67
+ * @returns {Promise<Object>} List of payments
68
+ */
69
+ export async function listPayments({
70
+ apiKey,
71
+ client,
72
+ businessId,
73
+ status,
74
+ limit,
75
+ offset,
76
+ }) {
77
+ const coinpay = client || new CoinPayClient({ apiKey });
78
+ return coinpay.listPayments({ businessId, status, limit, offset });
79
+ }
80
+
81
+ /**
82
+ * Payment status constants
83
+ */
84
+ export const PaymentStatus = {
85
+ PENDING: 'pending',
86
+ CONFIRMING: 'confirming',
87
+ COMPLETED: 'completed',
88
+ EXPIRED: 'expired',
89
+ FAILED: 'failed',
90
+ REFUNDED: 'refunded',
91
+ };
92
+
93
+ /**
94
+ * Supported cryptocurrencies
95
+ */
96
+ export const Cryptocurrency = {
97
+ BTC: 'BTC',
98
+ BCH: 'BCH',
99
+ ETH: 'ETH',
100
+ MATIC: 'MATIC',
101
+ SOL: 'SOL',
102
+ };
103
+
104
+ /**
105
+ * Supported fiat currencies
106
+ */
107
+ export const FiatCurrency = {
108
+ USD: 'USD',
109
+ EUR: 'EUR',
110
+ GBP: 'GBP',
111
+ CAD: 'CAD',
112
+ AUD: 'AUD',
113
+ };
114
+
115
+ export default {
116
+ createPayment,
117
+ getPayment,
118
+ listPayments,
119
+ PaymentStatus,
120
+ Cryptocurrency,
121
+ FiatCurrency,
122
+ };
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Webhook utilities for CoinPay SDK
3
+ */
4
+
5
+ import { createHmac, timingSafeEqual } from 'crypto';
6
+
7
+ /**
8
+ * Webhook event types
9
+ */
10
+ export const WebhookEvent = {
11
+ PAYMENT_CREATED: 'payment.created',
12
+ PAYMENT_PENDING: 'payment.pending',
13
+ PAYMENT_CONFIRMING: 'payment.confirming',
14
+ PAYMENT_COMPLETED: 'payment.completed',
15
+ PAYMENT_EXPIRED: 'payment.expired',
16
+ PAYMENT_FAILED: 'payment.failed',
17
+ PAYMENT_REFUNDED: 'payment.refunded',
18
+ BUSINESS_CREATED: 'business.created',
19
+ BUSINESS_UPDATED: 'business.updated',
20
+ };
21
+
22
+ /**
23
+ * Verify webhook signature
24
+ * @param {Object} params - Verification parameters
25
+ * @param {string} params.payload - Raw request body (string)
26
+ * @param {string} params.signature - Signature from X-CoinPay-Signature header
27
+ * @param {string} params.secret - Your webhook secret
28
+ * @param {number} [params.tolerance] - Timestamp tolerance in seconds (default: 300)
29
+ * @returns {boolean} True if signature is valid
30
+ */
31
+ export function verifyWebhookSignature({
32
+ payload,
33
+ signature,
34
+ secret,
35
+ tolerance = 300,
36
+ }) {
37
+ if (!payload || !signature || !secret) {
38
+ throw new Error('Missing required parameters: payload, signature, and secret are required');
39
+ }
40
+
41
+ try {
42
+ // Parse signature header (format: t=timestamp,v1=signature)
43
+ const parts = signature.split(',');
44
+ const signatureParts = {};
45
+
46
+ for (const part of parts) {
47
+ const [key, value] = part.split('=');
48
+ signatureParts[key] = value;
49
+ }
50
+
51
+ const timestamp = signatureParts.t;
52
+ const expectedSignature = signatureParts.v1;
53
+
54
+ if (!timestamp || !expectedSignature) {
55
+ return false;
56
+ }
57
+
58
+ // Check timestamp tolerance
59
+ const timestampAge = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
60
+ if (Math.abs(timestampAge) > tolerance) {
61
+ return false;
62
+ }
63
+
64
+ // Compute expected signature
65
+ const signedPayload = `${timestamp}.${payload}`;
66
+ const computedSignature = createHmac('sha256', secret)
67
+ .update(signedPayload)
68
+ .digest('hex');
69
+
70
+ // Timing-safe comparison
71
+ const expectedBuffer = Buffer.from(expectedSignature, 'hex');
72
+ const computedBuffer = Buffer.from(computedSignature, 'hex');
73
+
74
+ if (expectedBuffer.length !== computedBuffer.length) {
75
+ return false;
76
+ }
77
+
78
+ return timingSafeEqual(expectedBuffer, computedBuffer);
79
+ } catch (error) {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Parse webhook payload
86
+ * @param {string} payload - Raw request body
87
+ * @returns {Object} Parsed webhook event
88
+ */
89
+ export function parseWebhookPayload(payload) {
90
+ try {
91
+ const event = JSON.parse(payload);
92
+
93
+ return {
94
+ id: event.id,
95
+ type: event.type,
96
+ data: event.data,
97
+ createdAt: new Date(event.created_at),
98
+ businessId: event.business_id,
99
+ };
100
+ } catch (error) {
101
+ throw new Error(`Failed to parse webhook payload: ${error.message}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create a webhook handler middleware for Express/Connect
107
+ * @param {Object} options - Handler options
108
+ * @param {string} options.secret - Webhook secret
109
+ * @param {Function} options.onEvent - Event handler function
110
+ * @param {Function} [options.onError] - Error handler function
111
+ * @returns {Function} Express middleware
112
+ */
113
+ export function createWebhookHandler({ secret, onEvent, onError }) {
114
+ return async (req, res) => {
115
+ try {
116
+ const signature = req.headers['x-coinpay-signature'];
117
+ const payload = typeof req.body === 'string' ? req.body : JSON.stringify(req.body);
118
+
119
+ // Verify signature
120
+ const isValid = verifyWebhookSignature({
121
+ payload,
122
+ signature,
123
+ secret,
124
+ });
125
+
126
+ if (!isValid) {
127
+ res.status(401).json({ error: 'Invalid signature' });
128
+ return;
129
+ }
130
+
131
+ // Parse and handle event
132
+ const event = parseWebhookPayload(payload);
133
+ await onEvent(event);
134
+
135
+ res.status(200).json({ received: true });
136
+ } catch (error) {
137
+ if (onError) {
138
+ onError(error);
139
+ }
140
+ res.status(500).json({ error: 'Webhook handler error' });
141
+ }
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Generate a webhook signature (for testing)
147
+ * @param {Object} params - Signature parameters
148
+ * @param {string} params.payload - Request body
149
+ * @param {string} params.secret - Webhook secret
150
+ * @param {number} [params.timestamp] - Unix timestamp (default: now)
151
+ * @returns {string} Signature header value
152
+ */
153
+ export function generateWebhookSignature({ payload, secret, timestamp }) {
154
+ const ts = timestamp || Math.floor(Date.now() / 1000);
155
+ const signedPayload = `${ts}.${payload}`;
156
+ const signature = createHmac('sha256', secret)
157
+ .update(signedPayload)
158
+ .digest('hex');
159
+
160
+ return `t=${ts},v1=${signature}`;
161
+ }
162
+
163
+ export default {
164
+ WebhookEvent,
165
+ verifyWebhookSignature,
166
+ parseWebhookPayload,
167
+ createWebhookHandler,
168
+ generateWebhookSignature,
169
+ };