@profullstack/coinpay 0.2.0 → 0.3.1

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,142 +1,221 @@
1
1
  # @profullstack/coinpay
2
2
 
3
- CoinPay SDK & CLI - Accept cryptocurrency payments in your application.
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
4
42
 
5
- ## Overview
6
-
7
- CoinPay allows merchants to accept cryptocurrency payments from their customers. When a customer wants to pay:
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
+ ```
8
58
 
9
59
  1. **Your server** calls the CoinPay API to create a payment request
10
- 2. **CoinPay** generates a unique payment address and QR code
11
- 3. **Your customer** sends cryptocurrency to that address
60
+ 2. **CoinPay** generates a unique payment address and QR code — display these to your customer
61
+ 3. **Customer** sends cryptocurrency to the address
12
62
  4. **CoinPay** monitors the blockchain and notifies you via webhook when payment is confirmed
13
- 5. **Funds** are automatically forwarded to your configured wallet (minus a small fee)
63
+ 5. **Funds** are automatically forwarded to your configured wallet
64
+
65
+ ---
14
66
 
15
67
  ## Installation
16
68
 
17
69
  ```bash
18
- # Using pnpm (recommended)
70
+ # pnpm (recommended)
19
71
  pnpm add @profullstack/coinpay
20
72
 
21
- # Using npm
73
+ # npm
22
74
  npm install @profullstack/coinpay
23
75
 
24
- # Global CLI installation
76
+ # Global CLI
25
77
  pnpm add -g @profullstack/coinpay
26
78
  ```
27
79
 
80
+ **Requirements:** Node.js ≥ 20. Zero runtime dependencies — uses built-in `fetch` and `crypto`.
81
+
82
+ ---
83
+
28
84
  ## Quick Start
29
85
 
30
86
  ### 1. Get Your API Key
31
87
 
32
88
  1. Sign up at [coinpayportal.com](https://coinpayportal.com)
33
89
  2. Create a business in your dashboard
34
- 3. Configure your wallet addresses for each cryptocurrency you want to accept
90
+ 3. Configure wallet addresses for each crypto you want to accept
35
91
  4. Copy your API key (starts with `cp_live_`)
36
92
 
37
- ### 2. Create a Payment (SDK)
93
+ ### 2. Create a Payment
38
94
 
39
95
  ```javascript
40
- import { CoinPayClient } from '@profullstack/coinpay';
96
+ import { CoinPayClient, Blockchain } from '@profullstack/coinpay';
41
97
 
42
- // Initialize with your API key
43
- const coinpay = new CoinPayClient({
98
+ const client = new CoinPayClient({
44
99
  apiKey: 'cp_live_your_api_key_here',
45
100
  });
46
101
 
47
- // Create a payment when customer checks out
48
- const payment = await coinpay.createPayment({
49
- businessId: 'your-business-id', // From your dashboard
50
- amount: 99.99, // Amount in fiat currency
51
- currency: 'USD', // Fiat currency (default: USD)
52
- blockchain: 'BTC', // Cryptocurrency to accept
53
- description: 'Order #12345', // Shown to customer
54
- metadata: { // Your custom data
55
- orderId: '12345',
56
- customerEmail: 'customer@example.com'
57
- }
102
+ const { payment } = await client.createPayment({
103
+ businessId: 'your-business-id',
104
+ amount: 99.99,
105
+ currency: 'USD',
106
+ blockchain: Blockchain.BTC,
107
+ description: 'Order #12345',
108
+ metadata: { orderId: '12345' },
58
109
  });
59
110
 
60
- // Display to customer
61
- console.log('Send payment to:', payment.payment.payment_address);
62
- console.log('Amount:', payment.payment.crypto_amount, payment.payment.blockchain);
63
- console.log('QR Code:', payment.payment.qr_code);
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);
64
114
  ```
65
115
 
66
- ### 3. Create a Payment (cURL)
116
+ ### 3. Handle Payment Confirmation
67
117
 
68
- ```bash
69
- curl -X POST https://coinpayportal.com/api/payments/create \
70
- -H "Authorization: Bearer cp_live_your_api_key_here" \
71
- -H "Content-Type: application/json" \
72
- -d '{
73
- "business_id": "your-business-id",
74
- "amount": 99.99,
75
- "currency": "USD",
76
- "blockchain": "BTC",
77
- "description": "Order #12345",
78
- "metadata": {
79
- "orderId": "12345",
80
- "customerEmail": "customer@example.com"
118
+ ```javascript
119
+ import { createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
120
+
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);
81
127
  }
82
- }'
128
+ },
129
+ }));
83
130
  ```
84
131
 
85
- ### 4. Create a Payment (CLI)
132
+ ---
86
133
 
87
- ```bash
88
- # Configure your API key (one-time setup)
89
- coinpay config set-key cp_live_your_api_key_here
134
+ ## Supported Blockchains
90
135
 
91
- # Create a payment
92
- coinpay payment create \
93
- --business-id your-business-id \
94
- --amount 99.99 \
95
- --blockchain BTC \
96
- --description "Order #12345"
97
- ```
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 |
98
146
 
99
- ## Supported Blockchains
147
+ Use the `Blockchain` constant to avoid typos:
100
148
 
101
- | Blockchain | Code | Description |
102
- |------------|------|-------------|
103
- | Bitcoin | `BTC` | Native Bitcoin |
104
- | Bitcoin Cash | `BCH` | Bitcoin Cash |
105
- | Ethereum | `ETH` | Native Ether |
106
- | Polygon | `MATIC` | Native MATIC |
107
- | Solana | `SOL` | Native SOL |
108
- | USDC (Ethereum) | `USDC_ETH` | USDC on Ethereum |
109
- | USDC (Polygon) | `USDC_MATIC` | USDC on Polygon |
110
- | USDC (Solana) | `USDC_SOL` | USDC on Solana |
149
+ ```javascript
150
+ import { Blockchain } from '@profullstack/coinpay';
111
151
 
112
- ## SDK API Reference
152
+ Blockchain.BTC // 'BTC'
153
+ Blockchain.ETH // 'ETH'
154
+ Blockchain.USDC_POL // 'USDC_POL'
155
+ ```
156
+
157
+ ---
158
+
159
+ ## API Reference
113
160
 
114
161
  ### CoinPayClient
115
162
 
163
+ The main class for all API operations.
164
+
116
165
  ```javascript
117
166
  import { CoinPayClient } from '@profullstack/coinpay';
118
167
 
119
168
  const client = new CoinPayClient({
120
- apiKey: 'cp_live_xxxxx', // Required: Your API key
121
- baseUrl: 'https://coinpayportal.com/api', // Optional: API URL
122
- timeout: 30000, // Optional: Request timeout (ms)
169
+ apiKey: 'cp_live_xxxxx', // Required
170
+ baseUrl: 'https://coinpayportal.com/api', // Optional (default)
171
+ timeout: 30000, // Optional: ms (default: 30s)
123
172
  });
124
173
  ```
125
174
 
126
- ### Creating Payments
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
+
185
+ ### Payments
186
+
187
+ #### `client.createPayment(params)`
188
+
189
+ Create a new payment request. Generates a unique blockchain address for the customer to pay.
127
190
 
128
191
  ```javascript
129
- // Create a payment
130
- const payment = await client.createPayment({
131
- businessId: 'biz_123', // Required: Your business ID
132
- amount: 100, // Required: Amount in fiat
133
- currency: 'USD', // Optional: Fiat currency (default: USD)
134
- blockchain: 'ETH', // Required: Blockchain to use
135
- description: 'Order #123', // Optional: Description for customer
136
- metadata: { ... }, // Optional: Your custom data
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
+ },
137
202
  });
203
+ ```
204
+
205
+ **Parameters:**
206
+
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 |
138
215
 
139
- // Response structure
216
+ **Returns:**
217
+
218
+ ```javascript
140
219
  {
141
220
  success: true,
142
221
  payment: {
@@ -150,7 +229,8 @@ const payment = await client.createPayment({
150
229
  qr_code: 'data:image/png;base64,...',
151
230
  status: 'pending',
152
231
  expires_at: '2024-01-01T01:00:00.000Z',
153
- created_at: '2024-01-01T00:00:00.000Z'
232
+ created_at: '2024-01-01T00:00:00.000Z',
233
+ metadata: { orderId: '123' }
154
234
  },
155
235
  usage: {
156
236
  current: 45,
@@ -160,250 +240,310 @@ const payment = await client.createPayment({
160
240
  }
161
241
  ```
162
242
 
163
- ### Checking Payment Status
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
+ ---
164
258
 
165
- There are two ways to know when a payment is complete:
259
+ #### `client.listPayments(params)`
166
260
 
167
- #### Option 1: Polling (Simple)
261
+ List payments for a business with optional filtering and pagination.
168
262
 
169
- Use `getPayment()` to check status, or `waitForPayment()` to poll until complete:
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.
170
279
 
171
280
  ```javascript
172
- // Check status once
173
- const result = await client.getPayment('pay_abc123');
174
- console.log(result.payment.status);
175
-
176
- // Or wait for payment to complete (polls automatically)
177
- const payment = await client.waitForPayment('pay_abc123', {
178
- interval: 5000, // Check every 5 seconds
179
- timeout: 600000, // Give up after 10 minutes
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'],
180
285
  onStatusChange: (status, payment) => {
181
- console.log(`Status changed to: ${status}`);
182
- }
286
+ console.log(`Status ${status}`);
287
+ },
183
288
  });
184
289
 
185
- if (payment.payment.status === 'confirmed' || payment.payment.status === 'forwarded') {
290
+ if (payment.status === 'confirmed' || payment.status === 'forwarded') {
186
291
  console.log('Payment successful!');
187
- } else {
188
- console.log('Payment failed or expired');
189
292
  }
190
293
  ```
191
294
 
192
- #### Option 2: Webhooks (Recommended for Production)
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` |
193
301
 
194
- Configure a webhook URL in your business settings to receive real-time notifications:
302
+ > ⚠️ For production, use [webhooks](#webhook-integration) instead of polling.
195
303
 
196
- ```javascript
197
- // Your webhook endpoint receives POST requests like:
198
- {
199
- "event": "payment.confirmed",
200
- "data": {
201
- "payment": {
202
- "id": "pay_abc123",
203
- "status": "confirmed",
204
- "metadata": { "orderId": "12345" }
205
- }
206
- }
207
- }
208
- ```
304
+ ---
209
305
 
210
- See [Webhook Integration](#webhook-integration) for full details.
306
+ ### Payment Statuses
211
307
 
212
- **Payment Statuses:**
213
- - `pending` - Waiting for payment
214
- - `detected` - Payment detected, waiting for confirmations
215
- - `confirmed` - Payment confirmed on blockchain
216
- - `forwarding` - Forwarding to your wallet
217
- - `forwarded` - Successfully sent to your wallet
218
- - `expired` - Payment request expired
219
- - `failed` - Payment failed
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 |
220
317
 
221
- ### Getting QR Code
318
+ ---
222
319
 
223
- The QR code endpoint returns binary PNG image data.
320
+ ### QR Codes
224
321
 
225
- ```javascript
226
- // Get QR code URL for use in HTML <img> tags
227
- const qrUrl = client.getPaymentQRUrl('pay_abc123');
228
- // Returns: "https://coinpayportal.com/api/payments/pay_abc123/qr"
322
+ #### `client.getPaymentQRUrl(paymentId)`
229
323
 
230
- // Use directly in HTML
231
- // <img src={qrUrl} alt="Payment QR Code" />
324
+ Returns the URL to the QR code image. **Synchronous** — no network request.
232
325
 
233
- // Get QR code as binary data (for server-side processing)
234
- const imageData = await client.getPaymentQR('pay_abc123');
326
+ ```javascript
327
+ const url = client.getPaymentQRUrl('pay_abc123');
328
+ // "https://coinpayportal.com/api/payments/pay_abc123/qr"
235
329
 
236
- // Save to file (Node.js)
237
- import fs from 'fs';
238
- fs.writeFileSync('payment-qr.png', Buffer.from(imageData));
330
+ // Use in HTML:
331
+ // <img src={url} alt="Payment QR Code" />
239
332
  ```
240
333
 
241
- ### Listing Payments
334
+ #### `client.getPaymentQR(paymentId)`
335
+
336
+ Fetches the QR code as binary PNG data.
242
337
 
243
338
  ```javascript
244
- const payments = await client.listPayments({
245
- businessId: 'biz_123',
246
- status: 'completed', // Optional filter
247
- limit: 20, // Optional (default: 20)
248
- offset: 0, // Optional pagination
249
- });
339
+ import fs from 'fs';
340
+
341
+ const imageData = await client.getPaymentQR('pay_abc123');
342
+ fs.writeFileSync('payment-qr.png', Buffer.from(imageData));
250
343
  ```
251
344
 
345
+ ---
346
+
252
347
  ### Exchange Rates
253
348
 
349
+ #### `client.getExchangeRate(crypto, fiat?)`
350
+
351
+ Get the exchange rate for a single cryptocurrency.
352
+
254
353
  ```javascript
255
- // Get single rate
256
354
  const rate = await client.getExchangeRate('BTC', 'USD');
257
- // { from: 'BTC', to: 'USD', rate: 43250.00 }
355
+ ```
258
356
 
259
- // Get multiple rates
357
+ #### `client.getExchangeRates(cryptos, fiat?)`
358
+
359
+ Get rates for multiple cryptocurrencies in one request.
360
+
361
+ ```javascript
260
362
  const rates = await client.getExchangeRates(['BTC', 'ETH', 'SOL'], 'USD');
261
363
  ```
262
364
 
263
- ## Direct API Usage (fetch/curl)
365
+ ---
264
366
 
265
- ### Create Payment
367
+ ### Business Management
368
+
369
+ #### `client.createBusiness(params)`
266
370
 
267
371
  ```javascript
268
- // Using fetch
269
- const response = await fetch('https://coinpayportal.com/api/payments/create', {
270
- method: 'POST',
271
- headers: {
272
- 'Authorization': 'Bearer cp_live_your_api_key',
273
- 'Content-Type': 'application/json',
372
+ const result = await client.createBusiness({
373
+ name: 'My Store',
374
+ webhookUrl: 'https://mystore.com/webhook',
375
+ walletAddresses: {
376
+ BTC: 'bc1q...',
377
+ ETH: '0x...',
378
+ SOL: '...',
274
379
  },
275
- body: JSON.stringify({
276
- business_id: 'your-business-id',
277
- amount: 50.00,
278
- currency: 'USD',
279
- blockchain: 'ETH',
280
- description: 'Premium subscription',
281
- metadata: {
282
- userId: 'user_123',
283
- plan: 'premium'
284
- }
285
- }),
286
380
  });
381
+ ```
382
+
383
+ #### `client.getBusiness(businessId)`
287
384
 
288
- const data = await response.json();
289
- console.log(data.payment.payment_address);
385
+ ```javascript
386
+ const result = await client.getBusiness('biz_123');
290
387
  ```
291
388
 
292
- ```bash
293
- # Using cURL
294
- curl -X POST https://coinpayportal.com/api/payments/create \
295
- -H "Authorization: Bearer cp_live_your_api_key" \
296
- -H "Content-Type: application/json" \
297
- -d '{
298
- "business_id": "your-business-id",
299
- "amount": 50.00,
300
- "currency": "USD",
301
- "blockchain": "ETH",
302
- "description": "Premium subscription"
303
- }'
389
+ #### `client.listBusinesses()`
390
+
391
+ ```javascript
392
+ const result = await client.listBusinesses();
304
393
  ```
305
394
 
306
- ### Get Payment Status
395
+ #### `client.updateBusiness(businessId, params)`
307
396
 
308
397
  ```javascript
309
- const response = await fetch('https://coinpayportal.com/api/payments/pay_abc123', {
310
- headers: {
311
- 'Authorization': 'Bearer cp_live_your_api_key',
312
- },
398
+ const result = await client.updateBusiness('biz_123', {
399
+ name: 'Updated Store Name',
400
+ webhookUrl: 'https://mystore.com/webhook/v2',
313
401
  });
314
- const data = await response.json();
315
402
  ```
316
403
 
317
- ```bash
318
- curl https://coinpayportal.com/api/payments/pay_abc123 \
319
- -H "Authorization: Bearer cp_live_your_api_key"
404
+ ---
405
+
406
+ ### Webhooks
407
+
408
+ #### `client.getWebhookLogs(businessId, limit?)`
409
+
410
+ Retrieve recent webhook delivery logs.
411
+
412
+ ```javascript
413
+ const logs = await client.getWebhookLogs('biz_123', 50);
320
414
  ```
321
415
 
322
- ## Webhook Integration
416
+ #### `client.testWebhook(businessId, eventType?)`
323
417
 
324
- When payment status changes, CoinPay sends a POST request to your configured webhook URL:
418
+ Send a test webhook event to your configured endpoint.
325
419
 
326
420
  ```javascript
327
- // Webhook payload
328
- {
329
- "event": "payment.confirmed",
330
- "timestamp": "2024-01-01T00:15:00.000Z",
331
- "data": {
332
- "payment": {
333
- "id": "pay_abc123",
334
- "business_id": "biz_123",
335
- "amount": 100.00,
336
- "currency": "USD",
337
- "crypto_amount": "0.0456",
338
- "blockchain": "ETH",
339
- "status": "confirmed",
340
- "tx_hash": "0xabc...def",
341
- "metadata": {
342
- "orderId": "12345"
343
- }
344
- }
345
- },
346
- "signature": "sha256_hmac_signature"
347
- }
421
+ await client.testWebhook('biz_123', 'payment.completed');
348
422
  ```
349
423
 
350
- ### Verifying Webhooks
424
+ ---
425
+
426
+ ### Standalone Functions
427
+
428
+ Convenience functions that auto-create a client. Best for one-off operations.
351
429
 
352
430
  ```javascript
353
- import { verifyWebhookSignature, createWebhookHandler } from '@profullstack/coinpay';
431
+ import { createPayment, getPayment, listPayments } from '@profullstack/coinpay';
354
432
 
355
- // Manual verification
356
- const isValid = verifyWebhookSignature({
357
- payload: rawBody,
358
- signature: req.headers['x-coinpay-signature'],
359
- 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',
360
439
  });
361
440
 
362
- // Express middleware
363
- app.post('/webhook', createWebhookHandler({
364
- secret: 'your-webhook-secret',
365
- onEvent: async (event) => {
366
- switch (event.type) {
367
- case 'payment.confirmed':
368
- // Mark order as paid
369
- await markOrderPaid(event.data.payment.metadata.orderId);
370
- break;
371
- case 'payment.forwarded':
372
- // Funds received in your wallet
373
- break;
374
- case 'payment.expired':
375
- // Handle expired payment
376
- break;
377
- }
378
- },
379
- }));
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
+ });
380
462
  ```
381
463
 
382
- ### Webhook Events
464
+ ---
383
465
 
384
- | Event | Description |
385
- |-------|-------------|
386
- | `payment.created` | Payment request created |
387
- | `payment.detected` | Payment detected on blockchain |
388
- | `payment.confirmed` | Payment confirmed (safe to fulfill order) |
389
- | `payment.forwarding` | Forwarding funds to your wallet |
390
- | `payment.forwarded` | Funds sent to your wallet |
391
- | `payment.expired` | Payment request expired |
392
- | `payment.failed` | Payment failed |
466
+ ### Constants
467
+
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
+ ---
393
528
 
394
529
  ## CLI Reference
395
530
 
396
- ### Configuration
531
+ ### Installation
397
532
 
398
533
  ```bash
399
- # Set your API key
400
- coinpay config set-key cp_live_xxxxx
534
+ # Global
535
+ pnpm add -g @profullstack/coinpay
401
536
 
402
- # Set custom API URL (for development)
403
- coinpay config set-url http://localhost:3000/api
537
+ # Or use npx
538
+ npx @profullstack/coinpay --help
539
+ ```
540
+
541
+ ### Configuration
404
542
 
405
- # Show current configuration
406
- coinpay config show
543
+ ```bash
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
407
547
  ```
408
548
 
409
549
  ### Payments
@@ -422,134 +562,357 @@ coinpay payment get pay_abc123
422
562
  # List payments
423
563
  coinpay payment list --business-id biz_123 --status pending --limit 10
424
564
 
425
- # Get QR code (saves as PNG file)
426
- coinpay payment qr pay_abc123 --output payment-qr.png
565
+ # Get QR code
566
+ coinpay payment qr pay_abc123
427
567
  ```
428
568
 
429
569
  ### Businesses
430
570
 
431
571
  ```bash
432
- # List your businesses
433
572
  coinpay business list
434
-
435
- # Get business details
436
573
  coinpay business get biz_123
437
-
438
- # Create a business
439
574
  coinpay business create --name "My Store" --webhook-url https://mysite.com/webhook
575
+ coinpay business update biz_123 --name "New Name"
440
576
  ```
441
577
 
442
578
  ### Exchange Rates
443
579
 
444
580
  ```bash
445
- # Get rate for a cryptocurrency
446
581
  coinpay rates get BTC
447
-
448
- # Get all rates
449
582
  coinpay rates list
450
583
  ```
451
584
 
452
- ## Environment Variables
585
+ ### Webhooks
453
586
 
454
- | Variable | Description |
455
- |----------|-------------|
456
- | `COINPAY_API_KEY` | API key (overrides config file) |
457
- | `COINPAY_BASE_URL` | Custom API URL (for development) |
587
+ ```bash
588
+ coinpay webhook logs biz_123
589
+ coinpay webhook test biz_123 --event payment.completed
590
+ ```
591
+
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
+ ```
620
+
621
+ ### Signature Verification
622
+
623
+ Every webhook includes an `X-CoinPay-Signature` header in the format `t=<timestamp>,v1=<hmac-sha256>`. Always verify signatures before processing events.
624
+
625
+ #### Using the Middleware (Express)
626
+
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
+ ```
653
+
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
+ ---
458
700
 
459
701
  ## Error Handling
460
702
 
703
+ All API errors include a `status` code and optional `response` object:
704
+
461
705
  ```javascript
462
706
  try {
463
707
  const payment = await client.createPayment({ ... });
464
708
  } catch (error) {
465
- if (error.status === 401) {
466
- console.error('Invalid API key');
467
- } else if (error.status === 400) {
468
- console.error('Invalid request:', error.response?.error);
469
- } else if (error.status === 429) {
470
- console.error('Rate limit exceeded or transaction limit reached');
471
- console.error('Usage:', error.response?.usage);
472
- } else {
473
- 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;
474
727
  }
475
728
  }
476
729
  ```
477
730
 
478
- ## Common Integration Patterns
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
479
738
 
480
739
  ### E-commerce Checkout
481
740
 
482
741
  ```javascript
483
- // In your checkout handler
742
+ import { CoinPayClient, createWebhookHandler, WebhookEvent } from '@profullstack/coinpay';
743
+
744
+ const client = new CoinPayClient({ apiKey: process.env.COINPAY_API_KEY });
745
+
746
+ // Checkout endpoint
484
747
  app.post('/checkout', async (req, res) => {
485
- const { orderId, amount, cryptocurrency } = req.body;
486
-
487
- const payment = await coinpay.createPayment({
748
+ const { orderId, amount, blockchain } = req.body;
749
+
750
+ const { payment } = await client.createPayment({
488
751
  businessId: process.env.COINPAY_BUSINESS_ID,
489
752
  amount,
490
- blockchain: cryptocurrency,
753
+ blockchain,
491
754
  description: `Order #${orderId}`,
492
- metadata: { orderId }
755
+ metadata: { orderId },
493
756
  });
494
-
495
- // Save payment ID to your order
496
- await db.orders.update(orderId, {
497
- paymentId: payment.payment.id,
498
- paymentAddress: payment.payment.payment_address
757
+
758
+ await db.orders.update(orderId, {
759
+ paymentId: payment.id,
760
+ paymentAddress: payment.payment_address,
499
761
  });
500
-
762
+
501
763
  res.json({
502
- paymentAddress: payment.payment.payment_address,
503
- amount: payment.payment.crypto_amount,
504
- qrCode: payment.payment.qr_code,
505
- expiresAt: payment.payment.expires_at
764
+ paymentAddress: payment.payment_address,
765
+ cryptoAmount: payment.crypto_amount,
766
+ qrCode: payment.qr_code,
767
+ expiresAt: payment.expires_at,
506
768
  });
507
769
  });
508
770
 
509
- // Webhook handler
510
- app.post('/webhook/coinpay', createWebhookHandler({
511
- secret: process.env.COINPAY_WEBHOOK_SECRET,
512
- onEvent: async (event) => {
513
- if (event.type === 'payment.confirmed') {
514
- const { orderId } = event.data.payment.metadata;
515
- await db.orders.update(orderId, { status: 'paid' });
516
- await sendOrderConfirmationEmail(orderId);
517
- }
518
- }
519
- }));
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
+ );
520
784
  ```
521
785
 
522
- ### Subscription Payments
786
+ ### Stablecoin Subscriptions
787
+
788
+ Use USDC for predictable pricing — no volatility:
523
789
 
524
790
  ```javascript
525
- // Create subscription payment
526
- const payment = await coinpay.createPayment({
527
- businessId: process.env.COINPAY_BUSINESS_ID,
791
+ const { payment } = await client.createPayment({
792
+ businessId: BUSINESS_ID,
528
793
  amount: 9.99,
529
- blockchain: 'USDC_MATIC', // Stablecoin for predictable pricing
794
+ blockchain: Blockchain.USDC_POL, // USDC on Polygon — low fees
530
795
  description: 'Monthly subscription',
531
- metadata: {
532
- userId: user.id,
533
- subscriptionId: subscription.id,
534
- period: '2024-01'
535
- }
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,
536
849
  });
537
850
  ```
538
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
+
539
893
  ## Testing
540
894
 
541
- For development and testing, you can:
895
+ ```bash
896
+ # Run tests
897
+ pnpm test
542
898
 
543
- 1. Use the dashboard's "Create Test Payment" feature
544
- 2. Set a custom `baseUrl` pointing to your local development server
545
- 3. Use testnet addresses (when supported)
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
+ ---
546
906
 
547
907
  ## Support
548
908
 
549
- - Documentation: [docs.coinpayportal.com](https://docs.coinpayportal.com)
550
- - Email: support@coinpayportal.com
551
- - Status: [status.coinpayportal.com](https://status.coinpayportal.com)
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
+ ---
552
915
 
553
916
  ## License
554
917
 
555
- MIT
918
+ [MIT](./LICENSE) © Profullstack, Inc.