@profullstack/coinpay 0.1.0 → 0.3.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
@@ -1,280 +1,918 @@
1
1
  # @profullstack/coinpay
2
2
 
3
- CoinPay SDK & CLI - Cryptocurrency payment integration for Node.js
3
+ > CoinPay SDK & CLI Accept cryptocurrency payments in your Node.js application.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@profullstack/coinpay)](https://www.npmjs.com/package/@profullstack/coinpay)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
8
+
9
+ Non-custodial, multi-chain payment processing for **Bitcoin**, **Ethereum**, **Solana**, **Polygon**, **Bitcoin Cash**, and **USDC** (on ETH, POL, SOL).
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ - [How It Works](#how-it-works)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Supported Blockchains](#supported-blockchains)
19
+ - [API Reference](#api-reference)
20
+ - [CoinPayClient](#coinpayclient)
21
+ - [Payments](#payments)
22
+ - [Payment Status Polling](#payment-status-polling)
23
+ - [QR Codes](#qr-codes)
24
+ - [Exchange Rates](#exchange-rates)
25
+ - [Business Management](#business-management)
26
+ - [Webhooks](#webhooks)
27
+ - [Standalone Functions](#standalone-functions)
28
+ - [Constants](#constants)
29
+ - [CLI Reference](#cli-reference)
30
+ - [Webhook Integration](#webhook-integration)
31
+ - [Error Handling](#error-handling)
32
+ - [Integration Patterns](#integration-patterns)
33
+ - [TypeScript](#typescript)
34
+ - [Environment Variables](#environment-variables)
35
+ - [Examples](#examples)
36
+ - [Testing](#testing)
37
+ - [License](#license)
38
+
39
+ ---
40
+
41
+ ## How It Works
42
+
43
+ ```
44
+ ┌──────────┐ 1. Create payment ┌──────────┐
45
+ │ Your │ ───────────────────────> │ CoinPay │
46
+ │ Server │ <─────────────────────── │ API │
47
+ │ │ Address + QR code │ │
48
+ └────┬─────┘ └────┬─────┘
49
+ │ │
50
+ │ 2. Show address/QR │ 4. Webhook notification
51
+ │ to customer │ (payment confirmed)
52
+ ▼ │
53
+ ┌──────────┐ 3. Sends crypto ┌────▼─────┐
54
+ │ Customer │ ───────────────────────> │Blockchain│
55
+ │ │ │ Network │
56
+ └──────────┘ └──────────┘
57
+ ```
58
+
59
+ 1. **Your server** calls the CoinPay API to create a payment request
60
+ 2. **CoinPay** generates a unique payment address and QR code — display these to your customer
61
+ 3. **Customer** sends cryptocurrency to the address
62
+ 4. **CoinPay** monitors the blockchain and notifies you via webhook when payment is confirmed
63
+ 5. **Funds** are automatically forwarded to your configured wallet
64
+
65
+ ---
4
66
 
5
67
  ## Installation
6
68
 
7
69
  ```bash
8
- # Using pnpm (recommended)
70
+ # pnpm (recommended)
9
71
  pnpm add @profullstack/coinpay
10
72
 
11
- # Using npm
73
+ # npm
12
74
  npm install @profullstack/coinpay
13
75
 
14
- # Global CLI installation
76
+ # Global CLI
15
77
  pnpm add -g @profullstack/coinpay
16
78
  ```
17
79
 
80
+ **Requirements:** Node.js ≥ 20. Zero runtime dependencies — uses built-in `fetch` and `crypto`.
81
+
82
+ ---
83
+
18
84
  ## Quick Start
19
85
 
20
- ### SDK Usage
86
+ ### 1. Get Your API Key
87
+
88
+ 1. Sign up at [coinpayportal.com](https://coinpayportal.com)
89
+ 2. Create a business in your dashboard
90
+ 3. Configure wallet addresses for each crypto you want to accept
91
+ 4. Copy your API key (starts with `cp_live_`)
92
+
93
+ ### 2. Create a Payment
21
94
 
22
95
  ```javascript
23
- import { CoinPayClient } from '@profullstack/coinpay';
96
+ import { CoinPayClient, Blockchain } from '@profullstack/coinpay';
24
97
 
25
- // Initialize the client
26
- const coinpay = new CoinPayClient({
27
- apiKey: 'your-api-key',
98
+ const client = new CoinPayClient({
99
+ apiKey: 'cp_live_your_api_key_here',
28
100
  });
29
101
 
30
- // Create a payment
31
- const payment = await coinpay.createPayment({
32
- businessId: 'biz_123',
33
- amount: 100,
102
+ const { payment } = await client.createPayment({
103
+ businessId: 'your-business-id',
104
+ amount: 99.99,
34
105
  currency: 'USD',
35
- cryptocurrency: 'BTC',
106
+ blockchain: Blockchain.BTC,
36
107
  description: 'Order #12345',
108
+ metadata: { orderId: '12345' },
37
109
  });
38
110
 
39
- console.log(`Payment address: ${payment.address}`);
40
- console.log(`Amount: ${payment.cryptoAmount} ${payment.cryptocurrency}`);
111
+ console.log('Send payment to:', payment.payment_address);
112
+ console.log('Amount:', payment.crypto_amount, 'BTC');
113
+ console.log('QR Code:', payment.qr_code);
41
114
  ```
42
115
 
43
- ### CLI Usage
116
+ ### 3. Handle Payment Confirmation
44
117
 
45
- ```bash
46
- # Configure your API key
47
- coinpay config set-key sk_live_xxxxx
118
+ ```javascript
119
+ import { createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
48
120
 
49
- # Create a payment
50
- coinpay payment create --business-id biz_123 --amount 100 --currency USD --crypto BTC
121
+ app.post('/webhook', createWebhookHandler({
122
+ secret: 'your-webhook-secret',
123
+ onEvent: async (event) => {
124
+ if (event.type === WebhookEvent.PAYMENT_COMPLETED) {
125
+ const orderId = event.data.payment.metadata.orderId;
126
+ await markOrderAsPaid(orderId);
127
+ }
128
+ },
129
+ }));
130
+ ```
51
131
 
52
- # Get payment details
53
- coinpay payment get pay_abc123
132
+ ---
54
133
 
55
- # List payments
56
- coinpay payment list --business-id biz_123
134
+ ## Supported Blockchains
57
135
 
58
- # Get exchange rates
59
- coinpay rates get BTC
136
+ | Blockchain | Code | Type |
137
+ |------------|------|------|
138
+ | Bitcoin | `BTC` | Native |
139
+ | Bitcoin Cash | `BCH` | Native |
140
+ | Ethereum | `ETH` | Native |
141
+ | Polygon | `POL` | Native |
142
+ | Solana | `SOL` | Native |
143
+ | USDC (Ethereum) | `USDC_ETH` | Stablecoin |
144
+ | USDC (Polygon) | `USDC_POL` | Stablecoin |
145
+ | USDC (Solana) | `USDC_SOL` | Stablecoin |
146
+
147
+ Use the `Blockchain` constant to avoid typos:
148
+
149
+ ```javascript
150
+ import { Blockchain } from '@profullstack/coinpay';
151
+
152
+ Blockchain.BTC // 'BTC'
153
+ Blockchain.ETH // 'ETH'
154
+ Blockchain.USDC_POL // 'USDC_POL'
60
155
  ```
61
156
 
62
- ## SDK API Reference
157
+ ---
158
+
159
+ ## API Reference
63
160
 
64
161
  ### CoinPayClient
65
162
 
163
+ The main class for all API operations.
164
+
66
165
  ```javascript
67
166
  import { CoinPayClient } from '@profullstack/coinpay';
68
167
 
69
168
  const client = new CoinPayClient({
70
- apiKey: 'your-api-key',
71
- baseUrl: 'https://coinpay.dev/api', // optional
72
- timeout: 30000, // optional, in milliseconds
169
+ apiKey: 'cp_live_xxxxx', // Required
170
+ baseUrl: 'https://coinpayportal.com/api', // Optional (default)
171
+ timeout: 30000, // Optional: ms (default: 30s)
73
172
  });
74
173
  ```
75
174
 
175
+ | Option | Type | Default | Description |
176
+ |--------|------|---------|-------------|
177
+ | `apiKey` | `string` | — | **Required.** Your CoinPay API key |
178
+ | `baseUrl` | `string` | `https://coinpayportal.com/api` | API base URL |
179
+ | `timeout` | `number` | `30000` | Request timeout in milliseconds |
180
+
181
+ **Throws** `Error` if `apiKey` is missing or empty.
182
+
183
+ ---
184
+
76
185
  ### Payments
77
186
 
187
+ #### `client.createPayment(params)`
188
+
189
+ Create a new payment request. Generates a unique blockchain address for the customer to pay.
190
+
78
191
  ```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',
192
+ const { payment, usage } = await client.createPayment({
193
+ businessId: 'biz_123', // Required from your dashboard
194
+ amount: 100.00, // Required — fiat amount
195
+ currency: 'USD', // Optional — fiat currency (default: 'USD')
196
+ blockchain: 'ETH', // Required — see Supported Blockchains
197
+ description: 'Order #123', // Optional — shown to customer
198
+ metadata: { // Optional — your custom data
199
+ orderId: '123',
200
+ customerEmail: 'a@b.com',
201
+ },
88
202
  });
203
+ ```
89
204
 
90
- // Get payment by ID
91
- const payment = await client.getPayment('pay_abc123');
205
+ **Parameters:**
92
206
 
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
207
+ | Param | Type | Required | Description |
208
+ |-------|------|----------|-------------|
209
+ | `businessId` | `string` | ✅ | Business ID from your dashboard |
210
+ | `amount` | `number` | ✅ | Amount in fiat currency |
211
+ | `currency` | `string` | — | Fiat currency code (default: `'USD'`). Supports: `USD`, `EUR`, `GBP`, `CAD`, `AUD` |
212
+ | `blockchain` | `string` | ✅ | Blockchain code (e.g., `'BTC'`, `'ETH'`, `'USDC_POL'`) |
213
+ | `description` | `string` | — | Payment description visible to the customer |
214
+ | `metadata` | `object` | — | Arbitrary key-value data attached to the payment |
215
+
216
+ **Returns:**
217
+
218
+ ```javascript
219
+ {
220
+ success: true,
221
+ payment: {
222
+ id: 'pay_abc123',
223
+ business_id: 'biz_123',
224
+ amount: 100,
225
+ currency: 'USD',
226
+ blockchain: 'ETH',
227
+ crypto_amount: '0.0456',
228
+ payment_address: '0x1234...5678',
229
+ qr_code: 'data:image/png;base64,...',
230
+ status: 'pending',
231
+ expires_at: '2024-01-01T01:00:00.000Z',
232
+ created_at: '2024-01-01T00:00:00.000Z',
233
+ metadata: { orderId: '123' }
234
+ },
235
+ usage: {
236
+ current: 45,
237
+ limit: 100,
238
+ remaining: 55
239
+ }
240
+ }
241
+ ```
242
+
243
+ ---
244
+
245
+ #### `client.getPayment(paymentId)`
246
+
247
+ Retrieve a payment by its ID.
248
+
249
+ ```javascript
250
+ const { payment } = await client.getPayment('pay_abc123');
251
+
252
+ console.log(payment.status); // 'pending', 'confirmed', etc.
253
+ console.log(payment.crypto_amount); // '0.0456'
254
+ console.log(payment.tx_hash); // '0xabc...def' (once detected)
255
+ ```
256
+
257
+ ---
258
+
259
+ #### `client.listPayments(params)`
260
+
261
+ List payments for a business with optional filtering and pagination.
262
+
263
+ ```javascript
264
+ const { payments } = await client.listPayments({
265
+ businessId: 'biz_123', // Required
266
+ status: 'completed', // Optional — filter by status
267
+ limit: 20, // Optional — results per page (default: 20)
268
+ offset: 0, // Optional — pagination offset (default: 0)
269
+ });
270
+ ```
271
+
272
+ ---
273
+
274
+ ### Payment Status Polling
275
+
276
+ #### `client.waitForPayment(paymentId, options?)`
277
+
278
+ Polls `getPayment()` until the payment reaches a terminal status. Useful for simple integrations that don't use webhooks.
279
+
280
+ ```javascript
281
+ const { payment } = await client.waitForPayment('pay_abc123', {
282
+ interval: 5000, // Poll every 5s (default)
283
+ timeout: 600000, // Give up after 10 min (default: 1 hour)
284
+ targetStatuses: ['confirmed', 'forwarded', 'expired', 'failed'],
285
+ onStatusChange: (status, payment) => {
286
+ console.log(`Status → ${status}`);
287
+ },
99
288
  });
100
289
 
101
- // Get payment QR code
102
- const qr = await client.getPaymentQR('pay_abc123', 'png');
290
+ if (payment.status === 'confirmed' || payment.status === 'forwarded') {
291
+ console.log('Payment successful!');
292
+ }
103
293
  ```
104
294
 
295
+ | Option | Type | Default | Description |
296
+ |--------|------|---------|-------------|
297
+ | `interval` | `number` | `5000` | Polling interval in ms |
298
+ | `timeout` | `number` | `3600000` | Max wait time in ms |
299
+ | `targetStatuses` | `string[]` | `['confirmed','forwarded','expired','failed']` | Statuses that stop polling |
300
+ | `onStatusChange` | `function` | — | Callback `(status, payment) => void` |
301
+
302
+ > ⚠️ For production, use [webhooks](#webhook-integration) instead of polling.
303
+
304
+ ---
305
+
306
+ ### Payment Statuses
307
+
308
+ | Status | Description |
309
+ |--------|-------------|
310
+ | `pending` | Waiting for customer to send payment |
311
+ | `detected` | Payment detected on blockchain, awaiting confirmations |
312
+ | `confirmed` | Payment confirmed — safe to fulfill the order |
313
+ | `forwarding` | Forwarding funds to your wallet |
314
+ | `forwarded` | Funds successfully sent to your wallet |
315
+ | `expired` | Payment request expired (customer didn't pay in time) |
316
+ | `failed` | Payment failed |
317
+
318
+ ---
319
+
320
+ ### QR Codes
321
+
322
+ #### `client.getPaymentQRUrl(paymentId)`
323
+
324
+ Returns the URL to the QR code image. **Synchronous** — no network request.
325
+
326
+ ```javascript
327
+ const url = client.getPaymentQRUrl('pay_abc123');
328
+ // "https://coinpayportal.com/api/payments/pay_abc123/qr"
329
+
330
+ // Use in HTML:
331
+ // <img src={url} alt="Payment QR Code" />
332
+ ```
333
+
334
+ #### `client.getPaymentQR(paymentId)`
335
+
336
+ Fetches the QR code as binary PNG data.
337
+
338
+ ```javascript
339
+ import fs from 'fs';
340
+
341
+ const imageData = await client.getPaymentQR('pay_abc123');
342
+ fs.writeFileSync('payment-qr.png', Buffer.from(imageData));
343
+ ```
344
+
345
+ ---
346
+
105
347
  ### Exchange Rates
106
348
 
349
+ #### `client.getExchangeRate(crypto, fiat?)`
350
+
351
+ Get the exchange rate for a single cryptocurrency.
352
+
107
353
  ```javascript
108
- // Get single rate
109
354
  const rate = await client.getExchangeRate('BTC', 'USD');
355
+ ```
356
+
357
+ #### `client.getExchangeRates(cryptos, fiat?)`
358
+
359
+ Get rates for multiple cryptocurrencies in one request.
110
360
 
111
- // Get multiple rates
361
+ ```javascript
112
362
  const rates = await client.getExchangeRates(['BTC', 'ETH', 'SOL'], 'USD');
113
363
  ```
114
364
 
115
- ### Businesses
365
+ ---
366
+
367
+ ### Business Management
368
+
369
+ #### `client.createBusiness(params)`
116
370
 
117
371
  ```javascript
118
- // Create a business
119
- const business = await client.createBusiness({
372
+ const result = await client.createBusiness({
120
373
  name: 'My Store',
121
- webhookUrl: 'https://your-site.com/webhook',
374
+ webhookUrl: 'https://mystore.com/webhook',
122
375
  walletAddresses: {
123
376
  BTC: 'bc1q...',
124
377
  ETH: '0x...',
378
+ SOL: '...',
125
379
  },
126
380
  });
381
+ ```
382
+
383
+ #### `client.getBusiness(businessId)`
384
+
385
+ ```javascript
386
+ const result = await client.getBusiness('biz_123');
387
+ ```
127
388
 
128
- // Get business
129
- const business = await client.getBusiness('biz_123');
389
+ #### `client.listBusinesses()`
390
+
391
+ ```javascript
392
+ const result = await client.listBusinesses();
393
+ ```
130
394
 
131
- // List businesses
132
- const businesses = await client.listBusinesses();
395
+ #### `client.updateBusiness(businessId, params)`
133
396
 
134
- // Update business
135
- const updated = await client.updateBusiness('biz_123', {
136
- name: 'Updated Name',
397
+ ```javascript
398
+ const result = await client.updateBusiness('biz_123', {
399
+ name: 'Updated Store Name',
400
+ webhookUrl: 'https://mystore.com/webhook/v2',
137
401
  });
138
402
  ```
139
403
 
404
+ ---
405
+
140
406
  ### Webhooks
141
407
 
408
+ #### `client.getWebhookLogs(businessId, limit?)`
409
+
410
+ Retrieve recent webhook delivery logs.
411
+
142
412
  ```javascript
143
- // Get webhook logs
144
413
  const logs = await client.getWebhookLogs('biz_123', 50);
414
+ ```
415
+
416
+ #### `client.testWebhook(businessId, eventType?)`
417
+
418
+ Send a test webhook event to your configured endpoint.
145
419
 
146
- // Test webhook
147
- const result = await client.testWebhook('biz_123', 'payment.completed');
420
+ ```javascript
421
+ await client.testWebhook('biz_123', 'payment.completed');
148
422
  ```
149
423
 
150
- ## Webhook Verification
424
+ ---
425
+
426
+ ### Standalone Functions
427
+
428
+ Convenience functions that auto-create a client. Best for one-off operations.
151
429
 
152
430
  ```javascript
153
- import { verifyWebhookSignature, createWebhookHandler } from '@profullstack/coinpay';
431
+ import { createPayment, getPayment, listPayments } from '@profullstack/coinpay';
154
432
 
155
- // Manual verification
156
- const isValid = verifyWebhookSignature({
157
- payload: rawBody,
158
- signature: req.headers['x-coinpay-signature'],
159
- secret: 'your-webhook-secret',
433
+ // Create payment without instantiating a client
434
+ const result = await createPayment({
435
+ apiKey: 'cp_live_xxxxx',
436
+ businessId: 'biz_123',
437
+ amount: 50,
438
+ blockchain: 'BTC',
160
439
  });
161
440
 
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
- }));
441
+ // Or pass an existing client
442
+ const result2 = await createPayment({
443
+ client: existingClient,
444
+ businessId: 'biz_123',
445
+ amount: 50,
446
+ blockchain: 'BTC',
447
+ });
448
+
449
+ // Get payment
450
+ const payment = await getPayment({
451
+ apiKey: 'cp_live_xxxxx',
452
+ paymentId: 'pay_abc123',
453
+ });
454
+
455
+ // List payments
456
+ const list = await listPayments({
457
+ apiKey: 'cp_live_xxxxx',
458
+ businessId: 'biz_123',
459
+ status: 'completed',
460
+ limit: 10,
461
+ });
181
462
  ```
182
463
 
183
- ## Webhook Events
464
+ ---
184
465
 
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 |
466
+ ### Constants
194
467
 
195
- ## CLI Commands
468
+ ```javascript
469
+ import {
470
+ Blockchain,
471
+ PaymentStatus,
472
+ FiatCurrency,
473
+ WebhookEvent,
474
+ } from '@profullstack/coinpay';
475
+ ```
476
+
477
+ #### `Blockchain`
478
+
479
+ | Key | Value | Description |
480
+ |-----|-------|-------------|
481
+ | `BTC` | `'BTC'` | Bitcoin |
482
+ | `BCH` | `'BCH'` | Bitcoin Cash |
483
+ | `ETH` | `'ETH'` | Ethereum |
484
+ | `POL` | `'POL'` | Polygon |
485
+ | `SOL` | `'SOL'` | Solana |
486
+ | `USDC_ETH` | `'USDC_ETH'` | USDC on Ethereum |
487
+ | `USDC_POL` | `'USDC_POL'` | USDC on Polygon |
488
+ | `USDC_SOL` | `'USDC_SOL'` | USDC on Solana |
489
+
490
+ > `Cryptocurrency` is exported as a **deprecated** alias for `Blockchain`.
491
+
492
+ #### `PaymentStatus`
493
+
494
+ | Key | Value |
495
+ |-----|-------|
496
+ | `PENDING` | `'pending'` |
497
+ | `CONFIRMING` | `'confirming'` |
498
+ | `COMPLETED` | `'completed'` |
499
+ | `EXPIRED` | `'expired'` |
500
+ | `FAILED` | `'failed'` |
501
+ | `REFUNDED` | `'refunded'` |
502
+
503
+ #### `FiatCurrency`
504
+
505
+ | Key | Value |
506
+ |-----|-------|
507
+ | `USD` | `'USD'` |
508
+ | `EUR` | `'EUR'` |
509
+ | `GBP` | `'GBP'` |
510
+ | `CAD` | `'CAD'` |
511
+ | `AUD` | `'AUD'` |
512
+
513
+ #### `WebhookEvent`
514
+
515
+ | Key | Value |
516
+ |-----|-------|
517
+ | `PAYMENT_CREATED` | `'payment.created'` |
518
+ | `PAYMENT_PENDING` | `'payment.pending'` |
519
+ | `PAYMENT_CONFIRMING` | `'payment.confirming'` |
520
+ | `PAYMENT_COMPLETED` | `'payment.completed'` |
521
+ | `PAYMENT_EXPIRED` | `'payment.expired'` |
522
+ | `PAYMENT_FAILED` | `'payment.failed'` |
523
+ | `PAYMENT_REFUNDED` | `'payment.refunded'` |
524
+ | `BUSINESS_CREATED` | `'business.created'` |
525
+ | `BUSINESS_UPDATED` | `'business.updated'` |
526
+
527
+ ---
528
+
529
+ ## CLI Reference
530
+
531
+ ### Installation
532
+
533
+ ```bash
534
+ # Global
535
+ pnpm add -g @profullstack/coinpay
536
+
537
+ # Or use npx
538
+ npx @profullstack/coinpay --help
539
+ ```
196
540
 
197
541
  ### Configuration
198
542
 
199
543
  ```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
544
+ coinpay config set-key cp_live_xxxxx # Save API key
545
+ coinpay config set-url http://localhost:3000/api # Custom URL
546
+ coinpay config show # Display config
203
547
  ```
204
548
 
205
549
  ### Payments
206
550
 
207
551
  ```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)
552
+ # Create a payment
553
+ coinpay payment create \
554
+ --business-id biz_123 \
555
+ --amount 100 \
556
+ --blockchain BTC \
557
+ --description "Order #12345"
214
558
 
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]
559
+ # Get payment details
560
+ coinpay payment get pay_abc123
561
+
562
+ # List payments
563
+ coinpay payment list --business-id biz_123 --status pending --limit 10
564
+
565
+ # Get QR code
566
+ coinpay payment qr pay_abc123
218
567
  ```
219
568
 
220
569
  ### Businesses
221
570
 
222
571
  ```bash
223
- coinpay business create --name <name> [--webhook-url <url>]
224
- coinpay business get <business-id>
225
572
  coinpay business list
226
- coinpay business update <business-id> [--name <name>] [--webhook-url <url>]
573
+ coinpay business get biz_123
574
+ coinpay business create --name "My Store" --webhook-url https://mysite.com/webhook
575
+ coinpay business update biz_123 --name "New Name"
227
576
  ```
228
577
 
229
578
  ### Exchange Rates
230
579
 
231
580
  ```bash
232
- coinpay rates get <crypto> [--fiat <currency>]
233
- coinpay rates list [--fiat <currency>]
581
+ coinpay rates get BTC
582
+ coinpay rates list
234
583
  ```
235
584
 
236
585
  ### Webhooks
237
586
 
238
587
  ```bash
239
- coinpay webhook logs <business-id> [--limit <n>]
240
- coinpay webhook test <business-id> [--event <type>]
588
+ coinpay webhook logs biz_123
589
+ coinpay webhook test biz_123 --event payment.completed
241
590
  ```
242
591
 
243
- ## Supported Cryptocurrencies
592
+ ---
593
+
594
+ ## Webhook Integration
595
+
596
+ ### Webhook Payload
597
+
598
+ When a payment status changes, CoinPay sends a `POST` request to your webhook URL:
599
+
600
+ ```json
601
+ {
602
+ "id": "evt_abc123",
603
+ "type": "payment.completed",
604
+ "created_at": "2024-01-01T00:15:00.000Z",
605
+ "business_id": "biz_123",
606
+ "data": {
607
+ "payment": {
608
+ "id": "pay_abc123",
609
+ "status": "confirmed",
610
+ "amount": 100.00,
611
+ "currency": "USD",
612
+ "crypto_amount": "0.0456",
613
+ "blockchain": "ETH",
614
+ "tx_hash": "0xabc...def",
615
+ "metadata": { "orderId": "12345" }
616
+ }
617
+ }
618
+ }
619
+ ```
244
620
 
245
- - **BTC** - Bitcoin
246
- - **BCH** - Bitcoin Cash
247
- - **ETH** - Ethereum
248
- - **MATIC** - Polygon
249
- - **SOL** - Solana
621
+ ### Signature Verification
250
622
 
251
- ## Supported Fiat Currencies
623
+ Every webhook includes an `X-CoinPay-Signature` header in the format `t=<timestamp>,v1=<hmac-sha256>`. Always verify signatures before processing events.
252
624
 
253
- - USD, EUR, GBP, CAD, AUD, and more
625
+ #### Using the Middleware (Express)
254
626
 
255
- ## Environment Variables
627
+ ```javascript
628
+ import express from 'express';
629
+ import { createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
630
+
631
+ const app = express();
632
+
633
+ app.post('/webhook',
634
+ express.text({ type: 'application/json' }),
635
+ createWebhookHandler({
636
+ secret: process.env.COINPAY_WEBHOOK_SECRET,
637
+ onEvent: async (event) => {
638
+ switch (event.type) {
639
+ case WebhookEvent.PAYMENT_COMPLETED:
640
+ await fulfillOrder(event.data.payment.metadata.orderId);
641
+ break;
642
+ case WebhookEvent.PAYMENT_EXPIRED:
643
+ await cancelOrder(event.data.payment.metadata.orderId);
644
+ break;
645
+ }
646
+ },
647
+ onError: (error) => {
648
+ console.error('Webhook error:', error);
649
+ },
650
+ })
651
+ );
652
+ ```
256
653
 
257
- | Variable | Description |
258
- |----------|-------------|
259
- | `COINPAY_API_KEY` | API key (overrides config file) |
260
- | `COINPAY_BASE_URL` | Custom API URL |
654
+ #### Manual Verification
655
+
656
+ ```javascript
657
+ import { verifyWebhookSignature, parseWebhookPayload } from '@profullstack/coinpay';
658
+
659
+ const isValid = verifyWebhookSignature({
660
+ payload: rawBody, // Raw request body string
661
+ signature: req.headers['x-coinpay-signature'], // Signature header
662
+ secret: process.env.COINPAY_WEBHOOK_SECRET, // Your secret
663
+ tolerance: 300, // Optional: seconds (default: 300)
664
+ });
665
+
666
+ if (isValid) {
667
+ const event = parseWebhookPayload(rawBody);
668
+ // event.id, event.type, event.data, event.createdAt, event.businessId
669
+ }
670
+ ```
671
+
672
+ #### Generating Test Signatures
673
+
674
+ ```javascript
675
+ import { generateWebhookSignature } from '@profullstack/coinpay';
676
+
677
+ const signature = generateWebhookSignature({
678
+ payload: JSON.stringify(testEvent),
679
+ secret: 'whsec_test_secret',
680
+ timestamp: Math.floor(Date.now() / 1000), // Optional
681
+ });
682
+ // "t=1705312500,v1=a3f2b1..."
683
+ ```
684
+
685
+ ### Webhook Events
686
+
687
+ | Event | When |
688
+ |-------|------|
689
+ | `payment.created` | Payment request created |
690
+ | `payment.pending` | Awaiting blockchain detection |
691
+ | `payment.confirming` | Transaction detected, awaiting confirmations |
692
+ | `payment.completed` | **Payment confirmed — safe to fulfill** |
693
+ | `payment.expired` | Customer didn't pay in time |
694
+ | `payment.failed` | Payment failed |
695
+ | `payment.refunded` | Payment was refunded |
696
+ | `business.created` | New business created |
697
+ | `business.updated` | Business settings updated |
698
+
699
+ ---
261
700
 
262
701
  ## Error Handling
263
702
 
703
+ All API errors include a `status` code and optional `response` object:
704
+
264
705
  ```javascript
265
706
  try {
266
707
  const payment = await client.createPayment({ ... });
267
708
  } 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);
709
+ console.log(error.message); // Human-readable message
710
+ console.log(error.status); // HTTP status code (401, 400, 429, etc.)
711
+ console.log(error.response); // Full error response from the API
712
+
713
+ switch (error.status) {
714
+ case 400:
715
+ // Invalid request — check parameters
716
+ break;
717
+ case 401:
718
+ // Invalid API key
719
+ break;
720
+ case 404:
721
+ // Resource not found
722
+ break;
723
+ case 429:
724
+ // Rate limit or transaction limit exceeded
725
+ console.log('Usage:', error.response?.usage);
726
+ break;
274
727
  }
275
728
  }
276
729
  ```
277
730
 
731
+ **Timeout errors** throw a standard `Error` with message `"Request timeout after {ms}ms"`.
732
+
733
+ **Constructor errors** throw if `apiKey` is missing: `"API key is required"`.
734
+
735
+ ---
736
+
737
+ ## Integration Patterns
738
+
739
+ ### E-commerce Checkout
740
+
741
+ ```javascript
742
+ import { CoinPayClient, createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
743
+
744
+ const client = new CoinPayClient({ apiKey: process.env.COINPAY_API_KEY });
745
+
746
+ // Checkout endpoint
747
+ app.post('/checkout', async (req, res) => {
748
+ const { orderId, amount, blockchain } = req.body;
749
+
750
+ const { payment } = await client.createPayment({
751
+ businessId: process.env.COINPAY_BUSINESS_ID,
752
+ amount,
753
+ blockchain,
754
+ description: `Order #${orderId}`,
755
+ metadata: { orderId },
756
+ });
757
+
758
+ await db.orders.update(orderId, {
759
+ paymentId: payment.id,
760
+ paymentAddress: payment.payment_address,
761
+ });
762
+
763
+ res.json({
764
+ paymentAddress: payment.payment_address,
765
+ cryptoAmount: payment.crypto_amount,
766
+ qrCode: payment.qr_code,
767
+ expiresAt: payment.expires_at,
768
+ });
769
+ });
770
+
771
+ // Webhook
772
+ app.post('/webhook', express.text({ type: 'application/json' }),
773
+ createWebhookHandler({
774
+ secret: process.env.COINPAY_WEBHOOK_SECRET,
775
+ onEvent: async (event) => {
776
+ if (event.type === WebhookEvent.PAYMENT_COMPLETED) {
777
+ const { orderId } = event.data.payment.metadata;
778
+ await db.orders.update(orderId, { status: 'paid' });
779
+ await sendConfirmationEmail(orderId);
780
+ }
781
+ },
782
+ })
783
+ );
784
+ ```
785
+
786
+ ### Stablecoin Subscriptions
787
+
788
+ Use USDC for predictable pricing — no volatility:
789
+
790
+ ```javascript
791
+ const { payment } = await client.createPayment({
792
+ businessId: BUSINESS_ID,
793
+ amount: 9.99,
794
+ blockchain: Blockchain.USDC_POL, // USDC on Polygon — low fees
795
+ description: 'Monthly subscription',
796
+ metadata: { userId: user.id, period: '2024-01' },
797
+ });
798
+ ```
799
+
800
+ ### Direct API (fetch / cURL)
801
+
802
+ ```bash
803
+ # Create payment
804
+ curl -X POST https://coinpayportal.com/api/payments/create \
805
+ -H "Authorization: Bearer cp_live_your_api_key" \
806
+ -H "Content-Type: application/json" \
807
+ -d '{
808
+ "business_id": "your-business-id",
809
+ "amount": 50.00,
810
+ "currency": "USD",
811
+ "blockchain": "ETH"
812
+ }'
813
+
814
+ # Check payment status
815
+ curl https://coinpayportal.com/api/payments/pay_abc123 \
816
+ -H "Authorization: Bearer cp_live_your_api_key"
817
+ ```
818
+
819
+ ---
820
+
821
+ ## TypeScript
822
+
823
+ Full TypeScript support via `.d.ts` declaration files — no build step required.
824
+
825
+ ```typescript
826
+ import {
827
+ CoinPayClient,
828
+ Blockchain,
829
+ PaymentStatus,
830
+ WebhookEvent,
831
+ } from '@profullstack/coinpay';
832
+
833
+ import type {
834
+ CoinPayClientOptions,
835
+ PaymentParams,
836
+ Payment,
837
+ CreatePaymentResponse,
838
+ WaitForPaymentOptions,
839
+ VerifyWebhookParams,
840
+ ParsedWebhookEvent,
841
+ } from '@profullstack/coinpay';
842
+
843
+ const client = new CoinPayClient({ apiKey: 'cp_live_xxxxx' });
844
+
845
+ const { payment }: CreatePaymentResponse = await client.createPayment({
846
+ businessId: 'biz_123',
847
+ amount: 100,
848
+ blockchain: Blockchain.ETH,
849
+ });
850
+ ```
851
+
852
+ ### Subpath Imports
853
+
854
+ ```typescript
855
+ // Import only what you need
856
+ import { Blockchain, PaymentStatus } from '@profullstack/coinpay/payments';
857
+ import { verifyWebhookSignature, WebhookEvent } from '@profullstack/coinpay/webhooks';
858
+ ```
859
+
860
+ ---
861
+
862
+ ## Environment Variables
863
+
864
+ | Variable | Description |
865
+ |----------|-------------|
866
+ | `COINPAY_API_KEY` | API key (overrides config file in CLI) |
867
+ | `COINPAY_BASE_URL` | Custom API URL (for development) |
868
+
869
+ ---
870
+
871
+ ## Examples
872
+
873
+ See the [`examples/`](./examples/) directory for runnable code:
874
+
875
+ | Example | Description |
876
+ |---------|-------------|
877
+ | [`01-quick-start.js`](./examples/01-quick-start.js) | Create a payment and check status |
878
+ | [`02-create-payment.js`](./examples/02-create-payment.js) | All blockchain types, metadata, multi-currency |
879
+ | [`03-check-payment-status.js`](./examples/03-check-payment-status.js) | One-time check and `waitForPayment` polling |
880
+ | [`04-list-payments.js`](./examples/04-list-payments.js) | Filtering and pagination |
881
+ | [`05-exchange-rates.js`](./examples/05-exchange-rates.js) | Single and batch rate lookups |
882
+ | [`06-webhook-handler.js`](./examples/06-webhook-handler.js) | Express webhook server |
883
+ | [`07-ecommerce-checkout.js`](./examples/07-ecommerce-checkout.js) | Complete checkout → webhook → fulfillment flow |
884
+ | [`08-business-management.js`](./examples/08-business-management.js) | Create, list, and update businesses |
885
+ | [`09-error-handling.js`](./examples/09-error-handling.js) | Auth, validation, rate-limit, and timeout errors |
886
+
887
+ ```bash
888
+ COINPAY_API_KEY=cp_live_xxx COINPAY_BUSINESS_ID=biz_xxx node examples/01-quick-start.js
889
+ ```
890
+
891
+ ---
892
+
893
+ ## Testing
894
+
895
+ ```bash
896
+ # Run tests
897
+ pnpm test
898
+
899
+ # Watch mode
900
+ pnpm test:watch
901
+ ```
902
+
903
+ Tests use [Vitest](https://vitest.dev/) with mocked `fetch` — no API key needed.
904
+
905
+ ---
906
+
907
+ ## Support
908
+
909
+ - **Docs:** [docs.coinpayportal.com](https://docs.coinpayportal.com)
910
+ - **Dashboard:** [coinpayportal.com](https://coinpayportal.com)
911
+ - **Email:** support@coinpayportal.com
912
+ - **Issues:** [github.com/profullstack/coinpayportal/issues](https://github.com/profullstack/coinpayportal/issues)
913
+
914
+ ---
915
+
278
916
  ## License
279
917
 
280
- MIT
918
+ [MIT](./LICENSE) © Profullstack, Inc.