@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 +280 -0
- package/bin/coinpay.js +485 -0
- package/package.json +64 -0
- package/src/client.js +254 -0
- package/src/index.js +30 -0
- package/src/payments.js +122 -0
- package/src/webhooks.js +169 -0
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;
|
package/src/payments.js
ADDED
|
@@ -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
|
+
};
|
package/src/webhooks.js
ADDED
|
@@ -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
|
+
};
|