@ktmcp-cli/nowpayments 1.0.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/.env.example +17 -0
- package/AGENT.md +513 -0
- package/INSTALL.md +392 -0
- package/LICENSE +21 -0
- package/OPENCLAW.md +628 -0
- package/PROJECT_SUMMARY.md +305 -0
- package/README.md +375 -0
- package/banner.png +0 -0
- package/bin/nowpayments.js +89 -0
- package/examples/basic-payment.js +66 -0
- package/examples/invoice-flow.js +78 -0
- package/examples/monitor-payment.js +94 -0
- package/logo.png +0 -0
- package/openapi.json +2791 -0
- package/package.json +40 -0
- package/src/commands/auth.js +65 -0
- package/src/commands/currencies.js +95 -0
- package/src/commands/estimate.js +85 -0
- package/src/commands/invoice.js +188 -0
- package/src/commands/payment.js +241 -0
- package/src/commands/payout.js +184 -0
- package/src/commands/status.js +28 -0
- package/src/lib/api.js +133 -0
- package/src/lib/auth.js +70 -0
- package/src/lib/config.js +110 -0
- package/test.sh +250 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment command
|
|
3
|
+
*
|
|
4
|
+
* Create and manage cryptocurrency payments.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Command } = require('commander');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const Table = require('cli-table3');
|
|
10
|
+
const { makeRequest, handleApiError, displayResponse } = require('../lib/api');
|
|
11
|
+
|
|
12
|
+
const command = new Command('payment');
|
|
13
|
+
|
|
14
|
+
command
|
|
15
|
+
.description('Manage payments')
|
|
16
|
+
.summary('Create and query payment transactions');
|
|
17
|
+
|
|
18
|
+
// Create a new payment
|
|
19
|
+
command
|
|
20
|
+
.command('create')
|
|
21
|
+
.description('Create a new payment')
|
|
22
|
+
.requiredOption('-p, --price <amount>', 'Price amount')
|
|
23
|
+
.requiredOption('-c, --currency <currency>', 'Price currency (e.g., USD)')
|
|
24
|
+
.requiredOption('--pay-currency <currency>', 'Cryptocurrency to receive (e.g., BTC)')
|
|
25
|
+
.option('--ipn-url <url>', 'IPN callback URL')
|
|
26
|
+
.option('--order-id <id>', 'Your order ID')
|
|
27
|
+
.option('--order-description <desc>', 'Order description')
|
|
28
|
+
.option('--purchase-id <id>', 'Purchase ID')
|
|
29
|
+
.option('--payout-address <address>', 'Payout address')
|
|
30
|
+
.option('--payout-currency <currency>', 'Payout currency')
|
|
31
|
+
.option('--payout-extra-id <id>', 'Payout extra ID (for currencies that require it)')
|
|
32
|
+
.option('--fixed-rate', 'Use fixed rate')
|
|
33
|
+
.action(async (options) => {
|
|
34
|
+
try {
|
|
35
|
+
const price = parseFloat(options.price);
|
|
36
|
+
|
|
37
|
+
if (isNaN(price) || price <= 0) {
|
|
38
|
+
console.error(chalk.red('Error: Price must be a positive number'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const paymentData = {
|
|
43
|
+
price_amount: price,
|
|
44
|
+
price_currency: options.currency.toUpperCase(),
|
|
45
|
+
pay_currency: options.payCurrency.toUpperCase(),
|
|
46
|
+
ipn_callback_url: options.ipnUrl,
|
|
47
|
+
order_id: options.orderId,
|
|
48
|
+
order_description: options.orderDescription,
|
|
49
|
+
purchase_id: options.purchaseId,
|
|
50
|
+
payout_address: options.payoutAddress,
|
|
51
|
+
payout_currency: options.payoutCurrency?.toUpperCase(),
|
|
52
|
+
payout_extra_id: options.payoutExtraId,
|
|
53
|
+
fixed_rate: options.fixedRate || false
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Remove undefined values
|
|
57
|
+
Object.keys(paymentData).forEach(key => {
|
|
58
|
+
if (paymentData[key] === undefined) {
|
|
59
|
+
delete paymentData[key];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const data = await makeRequest(command.parent.parent, '/payment', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: paymentData
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
69
|
+
console.log(chalk.green('\n✓ Payment created successfully'));
|
|
70
|
+
console.log(chalk.cyan('\nPayment Details:'));
|
|
71
|
+
console.log(chalk.bold(' Payment ID:'), response.payment_id);
|
|
72
|
+
console.log(chalk.bold(' Status:'), getStatusColor(response.payment_status));
|
|
73
|
+
console.log(chalk.bold(' Pay Address:'), chalk.yellow(response.pay_address));
|
|
74
|
+
console.log(chalk.bold(' Pay Amount:'), `${response.pay_amount} ${response.pay_currency}`);
|
|
75
|
+
console.log(chalk.bold(' Price:'), `${response.price_amount} ${response.price_currency}`);
|
|
76
|
+
|
|
77
|
+
if (response.order_id) {
|
|
78
|
+
console.log(chalk.bold(' Order ID:'), response.order_id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(chalk.gray('\n Created:'), new Date(response.created_at).toLocaleString());
|
|
82
|
+
|
|
83
|
+
if (response.expiration_estimate_date) {
|
|
84
|
+
console.log(chalk.gray(' Expires:'), new Date(response.expiration_estimate_date).toLocaleString());
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
handleApiError(error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Get payment details
|
|
93
|
+
command
|
|
94
|
+
.command('get <payment-id>')
|
|
95
|
+
.description('Get payment details by ID')
|
|
96
|
+
.action(async (paymentId) => {
|
|
97
|
+
try {
|
|
98
|
+
const data = await makeRequest(command.parent.parent, `/payment/${paymentId}`);
|
|
99
|
+
|
|
100
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
101
|
+
console.log(chalk.cyan('\nPayment Details:'));
|
|
102
|
+
|
|
103
|
+
const table = new Table({
|
|
104
|
+
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
table.push(
|
|
108
|
+
['Payment ID', response.payment_id],
|
|
109
|
+
['Status', getStatusColor(response.payment_status)],
|
|
110
|
+
['Pay Address', response.pay_address],
|
|
111
|
+
['Pay Amount', `${response.pay_amount} ${response.pay_currency}`],
|
|
112
|
+
['Price', `${response.price_amount} ${response.price_currency}`],
|
|
113
|
+
['Actually Paid', response.actually_paid ? `${response.actually_paid} ${response.pay_currency}` : 'N/A']
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (response.outcome_amount) {
|
|
117
|
+
table.push(['Outcome', `${response.outcome_amount} ${response.outcome_currency}`]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (response.order_id) {
|
|
121
|
+
table.push(['Order ID', response.order_id]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (response.order_description) {
|
|
125
|
+
table.push(['Description', response.order_description]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
table.push(
|
|
129
|
+
['Created', new Date(response.created_at).toLocaleString()],
|
|
130
|
+
['Updated', new Date(response.updated_at).toLocaleString()]
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
console.log(table.toString());
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
handleApiError(error);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// List payments
|
|
141
|
+
command
|
|
142
|
+
.command('list')
|
|
143
|
+
.description('List all payments')
|
|
144
|
+
.option('-l, --limit <number>', 'Number of payments to return', '10')
|
|
145
|
+
.option('-p, --page <number>', 'Page number', '0')
|
|
146
|
+
.option('--sort-by <field>', 'Sort by field (created_at, updated_at)')
|
|
147
|
+
.option('--order <order>', 'Sort order (asc, desc)', 'desc')
|
|
148
|
+
.option('--date-from <date>', 'Filter by start date (YYYY-MM-DD)')
|
|
149
|
+
.option('--date-to <date>', 'Filter by end date (YYYY-MM-DD)')
|
|
150
|
+
.action(async (options) => {
|
|
151
|
+
try {
|
|
152
|
+
const queryParams = {
|
|
153
|
+
limit: parseInt(options.limit),
|
|
154
|
+
page: parseInt(options.page),
|
|
155
|
+
sortBy: options.sortBy,
|
|
156
|
+
orderBy: options.order,
|
|
157
|
+
dateFrom: options.dateFrom,
|
|
158
|
+
dateTo: options.dateTo
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const data = await makeRequest(command.parent.parent, '/payment', {
|
|
162
|
+
queryParams
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
166
|
+
const payments = response.data || [];
|
|
167
|
+
|
|
168
|
+
if (payments.length === 0) {
|
|
169
|
+
console.log(chalk.yellow('\nNo payments found'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(chalk.cyan(`\nPayments (${payments.length}):`));
|
|
174
|
+
|
|
175
|
+
const table = new Table({
|
|
176
|
+
head: ['ID', 'Status', 'Amount', 'Currency', 'Created'],
|
|
177
|
+
colWidths: [15, 15, 15, 10, 25]
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
payments.forEach(payment => {
|
|
181
|
+
table.push([
|
|
182
|
+
payment.payment_id?.toString().substring(0, 12) || 'N/A',
|
|
183
|
+
getStatusColor(payment.payment_status),
|
|
184
|
+
payment.pay_amount || 'N/A',
|
|
185
|
+
payment.pay_currency || 'N/A',
|
|
186
|
+
new Date(payment.created_at).toLocaleString()
|
|
187
|
+
]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
console.log(table.toString());
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
handleApiError(error);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Update payment estimate
|
|
198
|
+
command
|
|
199
|
+
.command('update-estimate <payment-id>')
|
|
200
|
+
.description('Update merchant estimate for a payment')
|
|
201
|
+
.action(async (paymentId) => {
|
|
202
|
+
try {
|
|
203
|
+
const data = await makeRequest(command.parent.parent, `/payment/${paymentId}/update-merchant-estimate`, {
|
|
204
|
+
method: 'POST'
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
208
|
+
console.log(chalk.green('\n✓ Estimate updated successfully'));
|
|
209
|
+
console.log(chalk.cyan('\nUpdated Payment:'));
|
|
210
|
+
console.log(chalk.bold(' Payment ID:'), response.payment_id);
|
|
211
|
+
console.log(chalk.bold(' Status:'), getStatusColor(response.payment_status));
|
|
212
|
+
console.log(chalk.bold(' New Pay Amount:'), `${response.pay_amount} ${response.pay_currency}`);
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
handleApiError(error);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get colored status text
|
|
221
|
+
* @param {string} status - Payment status
|
|
222
|
+
* @returns {string} Colored status
|
|
223
|
+
*/
|
|
224
|
+
function getStatusColor(status) {
|
|
225
|
+
const colors = {
|
|
226
|
+
'waiting': chalk.yellow,
|
|
227
|
+
'confirming': chalk.cyan,
|
|
228
|
+
'confirmed': chalk.blue,
|
|
229
|
+
'sending': chalk.magenta,
|
|
230
|
+
'partially_paid': chalk.yellow,
|
|
231
|
+
'finished': chalk.green,
|
|
232
|
+
'failed': chalk.red,
|
|
233
|
+
'refunded': chalk.gray,
|
|
234
|
+
'expired': chalk.red
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const color = colors[status] || chalk.white;
|
|
238
|
+
return color(status);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = command;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payout command
|
|
3
|
+
*
|
|
4
|
+
* Create and manage cryptocurrency payouts/withdrawals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Command } = require('commander');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const Table = require('cli-table3');
|
|
10
|
+
const { makeRequest, handleApiError, displayResponse } = require('../lib/api');
|
|
11
|
+
|
|
12
|
+
const command = new Command('payout');
|
|
13
|
+
|
|
14
|
+
command
|
|
15
|
+
.description('Manage payouts and withdrawals')
|
|
16
|
+
.summary('Create and query payout transactions');
|
|
17
|
+
|
|
18
|
+
// Create a new payout
|
|
19
|
+
command
|
|
20
|
+
.command('create')
|
|
21
|
+
.description('Create a new payout/withdrawal')
|
|
22
|
+
.requiredOption('-a, --amount <amount>', 'Payout amount')
|
|
23
|
+
.requiredOption('-c, --currency <currency>', 'Cryptocurrency (e.g., BTC)')
|
|
24
|
+
.requiredOption('--address <address>', 'Destination wallet address')
|
|
25
|
+
.option('--ipn-url <url>', 'IPN callback URL')
|
|
26
|
+
.option('--extra-id <id>', 'Extra ID for currencies that require it (e.g., XRP destination tag)')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
const amount = parseFloat(options.amount);
|
|
30
|
+
|
|
31
|
+
if (isNaN(amount) || amount <= 0) {
|
|
32
|
+
console.error(chalk.red('Error: Amount must be a positive number'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const payoutData = {
|
|
37
|
+
withdrawals: [{
|
|
38
|
+
address: options.address,
|
|
39
|
+
currency: options.currency.toUpperCase(),
|
|
40
|
+
amount: amount,
|
|
41
|
+
ipn_callback_url: options.ipnUrl,
|
|
42
|
+
extra_id: options.extraId
|
|
43
|
+
}]
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const data = await makeRequest(command.parent.parent, '/payout', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: payoutData
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
52
|
+
console.log(chalk.green('\n✓ Payout created successfully'));
|
|
53
|
+
console.log(chalk.cyan('\nPayout Details:'));
|
|
54
|
+
|
|
55
|
+
if (response.withdrawals && response.withdrawals.length > 0) {
|
|
56
|
+
const payout = response.withdrawals[0];
|
|
57
|
+
console.log(chalk.bold(' Payout ID:'), payout.id);
|
|
58
|
+
console.log(chalk.bold(' Status:'), payout.status);
|
|
59
|
+
console.log(chalk.bold(' Amount:'), `${payout.amount} ${payout.currency}`);
|
|
60
|
+
console.log(chalk.bold(' Address:'), payout.address);
|
|
61
|
+
|
|
62
|
+
if (payout.extra_id) {
|
|
63
|
+
console.log(chalk.bold(' Extra ID:'), payout.extra_id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(chalk.yellow('\n⚠ Note: Some payouts may require 2FA verification'));
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
handleApiError(error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Verify payout (2FA)
|
|
75
|
+
command
|
|
76
|
+
.command('verify <payout-id> <verification-code>')
|
|
77
|
+
.description('Verify a payout with 2FA code')
|
|
78
|
+
.action(async (payoutId, code) => {
|
|
79
|
+
try {
|
|
80
|
+
const data = await makeRequest(command.parent.parent, `/payout/${payoutId}/verify`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
body: { verification_code: code }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
86
|
+
console.log(chalk.green('\n✓ Payout verified successfully'));
|
|
87
|
+
console.log(chalk.cyan('\nStatus:'), response.status || 'Verified');
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
handleApiError(error);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Get payout details
|
|
95
|
+
command
|
|
96
|
+
.command('get <payout-id>')
|
|
97
|
+
.description('Get payout details by ID')
|
|
98
|
+
.action(async (payoutId) => {
|
|
99
|
+
try {
|
|
100
|
+
const data = await makeRequest(command.parent.parent, `/payout/${payoutId}`);
|
|
101
|
+
|
|
102
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
103
|
+
console.log(chalk.cyan('\nPayout Details:'));
|
|
104
|
+
|
|
105
|
+
const table = new Table({
|
|
106
|
+
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
table.push(
|
|
110
|
+
['Payout ID', response.id],
|
|
111
|
+
['Status', response.status],
|
|
112
|
+
['Amount', `${response.amount} ${response.currency}`],
|
|
113
|
+
['Address', response.address]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (response.extra_id) {
|
|
117
|
+
table.push(['Extra ID', response.extra_id]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (response.hash) {
|
|
121
|
+
table.push(['Transaction Hash', response.hash]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
table.push(
|
|
125
|
+
['Created', new Date(response.created_at).toLocaleString()]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
console.log(table.toString());
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
handleApiError(error);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// List payouts
|
|
136
|
+
command
|
|
137
|
+
.command('list')
|
|
138
|
+
.description('List all payouts')
|
|
139
|
+
.option('-l, --limit <number>', 'Number of payouts to return', '10')
|
|
140
|
+
.option('-p, --page <number>', 'Page number', '0')
|
|
141
|
+
.action(async (options) => {
|
|
142
|
+
try {
|
|
143
|
+
const queryParams = {
|
|
144
|
+
limit: parseInt(options.limit),
|
|
145
|
+
page: parseInt(options.page)
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const data = await makeRequest(command.parent.parent, '/payout', {
|
|
149
|
+
queryParams
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
displayResponse(command.parent.parent, data, (response) => {
|
|
153
|
+
const payouts = response.data || [];
|
|
154
|
+
|
|
155
|
+
if (payouts.length === 0) {
|
|
156
|
+
console.log(chalk.yellow('\nNo payouts found'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(chalk.cyan(`\nPayouts (${payouts.length}):`));
|
|
161
|
+
|
|
162
|
+
const table = new Table({
|
|
163
|
+
head: ['ID', 'Status', 'Amount', 'Currency', 'Created'],
|
|
164
|
+
colWidths: [20, 15, 15, 10, 25]
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
payouts.forEach(payout => {
|
|
168
|
+
table.push([
|
|
169
|
+
payout.id?.toString().substring(0, 18) || 'N/A',
|
|
170
|
+
payout.status || 'N/A',
|
|
171
|
+
payout.amount || 'N/A',
|
|
172
|
+
payout.currency || 'N/A',
|
|
173
|
+
new Date(payout.created_at).toLocaleString()
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log(table.toString());
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
handleApiError(error);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
module.exports = command;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command
|
|
3
|
+
*
|
|
4
|
+
* Check API status and availability.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Command } = require('commander');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { makeRequest, handleApiError, displayResponse } = require('../lib/api');
|
|
10
|
+
|
|
11
|
+
const command = new Command('status');
|
|
12
|
+
|
|
13
|
+
command
|
|
14
|
+
.description('Check NOWPayments API status')
|
|
15
|
+
.summary('Verify API connectivity and status')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const data = await makeRequest(command.parent, '/status');
|
|
19
|
+
|
|
20
|
+
displayResponse(command.parent, data, (response) => {
|
|
21
|
+
console.log(chalk.green('✓ NOWPayments API is'), chalk.bold(response.message || 'OK'));
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
handleApiError(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
module.exports = command;
|
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client for NOWPayments
|
|
3
|
+
*
|
|
4
|
+
* Handles all HTTP requests to the NOWPayments API with proper error handling,
|
|
5
|
+
* authentication, and response formatting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fetch = require('node-fetch');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { getBaseUrl } = require('./config');
|
|
11
|
+
const { getAuthHeaders } = require('./auth');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Make an API request to NOWPayments
|
|
15
|
+
* @param {Object} program - Commander program instance
|
|
16
|
+
* @param {string} endpoint - API endpoint (e.g., '/status')
|
|
17
|
+
* @param {Object} options - Request options
|
|
18
|
+
* @param {string} options.method - HTTP method (GET, POST, etc.)
|
|
19
|
+
* @param {Object} options.body - Request body for POST/PUT requests
|
|
20
|
+
* @param {Object} options.queryParams - URL query parameters
|
|
21
|
+
* @returns {Promise<Object>} API response data
|
|
22
|
+
*/
|
|
23
|
+
async function makeRequest(program, endpoint, options = {}) {
|
|
24
|
+
const { method = 'GET', body = null, queryParams = {} } = options;
|
|
25
|
+
|
|
26
|
+
// Build URL
|
|
27
|
+
const baseUrl = getBaseUrl(program._sandbox);
|
|
28
|
+
const url = new URL(`${baseUrl}${endpoint}`);
|
|
29
|
+
|
|
30
|
+
// Add query parameters
|
|
31
|
+
Object.keys(queryParams).forEach(key => {
|
|
32
|
+
if (queryParams[key] !== undefined && queryParams[key] !== null) {
|
|
33
|
+
url.searchParams.append(key, queryParams[key]);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Build request options
|
|
38
|
+
const requestOptions = {
|
|
39
|
+
method,
|
|
40
|
+
headers: getAuthHeaders(program)
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Add body for POST/PUT requests
|
|
44
|
+
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
45
|
+
requestOptions.body = JSON.stringify(body);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (program._debug) {
|
|
49
|
+
console.log(chalk.gray(`[DEBUG] ${method} ${url.toString()}`));
|
|
50
|
+
if (body) {
|
|
51
|
+
console.log(chalk.gray(`[DEBUG] Body: ${JSON.stringify(body, null, 2)}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(url.toString(), requestOptions);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
|
|
59
|
+
if (program._debug) {
|
|
60
|
+
console.log(chalk.gray(`[DEBUG] Status: ${response.status}`));
|
|
61
|
+
console.log(chalk.gray(`[DEBUG] Response: ${JSON.stringify(data, null, 2)}`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle error responses
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const error = new Error(data.message || `API request failed with status ${response.status}`);
|
|
67
|
+
error.statusCode = response.status;
|
|
68
|
+
error.data = data;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return data;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.statusCode) {
|
|
75
|
+
// API error
|
|
76
|
+
throw error;
|
|
77
|
+
} else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
78
|
+
// Network error
|
|
79
|
+
const networkError = new Error('Network error: Unable to reach NOWPayments API');
|
|
80
|
+
networkError.originalError = error;
|
|
81
|
+
throw networkError;
|
|
82
|
+
} else {
|
|
83
|
+
// Other error (parsing, etc.)
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handle and format API errors for display
|
|
91
|
+
* @param {Error} error - Error object
|
|
92
|
+
*/
|
|
93
|
+
function handleApiError(error) {
|
|
94
|
+
if (error.statusCode) {
|
|
95
|
+
console.error(chalk.red(`\nAPI Error (${error.statusCode}):`));
|
|
96
|
+
console.error(chalk.yellow(error.message));
|
|
97
|
+
|
|
98
|
+
if (error.data && error.data.errors) {
|
|
99
|
+
console.error(chalk.yellow('\nDetails:'));
|
|
100
|
+
console.error(JSON.stringify(error.data.errors, null, 2));
|
|
101
|
+
}
|
|
102
|
+
} else if (error.originalError) {
|
|
103
|
+
console.error(chalk.red('\nNetwork Error:'));
|
|
104
|
+
console.error(chalk.yellow(error.message));
|
|
105
|
+
} else {
|
|
106
|
+
console.error(chalk.red('\nUnexpected Error:'));
|
|
107
|
+
console.error(chalk.yellow(error.message));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format and display API response
|
|
115
|
+
* @param {Object} program - Commander program instance
|
|
116
|
+
* @param {Object} data - Response data
|
|
117
|
+
* @param {Function} formatter - Optional custom formatter function
|
|
118
|
+
*/
|
|
119
|
+
function displayResponse(program, data, formatter = null) {
|
|
120
|
+
if (program._json) {
|
|
121
|
+
console.log(JSON.stringify(data, null, 2));
|
|
122
|
+
} else if (formatter) {
|
|
123
|
+
formatter(data);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(JSON.stringify(data, null, 2));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
makeRequest,
|
|
131
|
+
handleApiError,
|
|
132
|
+
displayResponse
|
|
133
|
+
};
|
package/src/lib/auth.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication utilities for NOWPayments CLI
|
|
3
|
+
*
|
|
4
|
+
* Handles API key validation and authentication headers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { getApiKey } = require('./config');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate that an API key is available
|
|
12
|
+
* @param {Object} program - Commander program instance
|
|
13
|
+
* @throws {Error} If no API key is found
|
|
14
|
+
*/
|
|
15
|
+
function requireApiKey(program) {
|
|
16
|
+
const apiKey = getApiKey(program);
|
|
17
|
+
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
console.error(chalk.red('Error: No API key found'));
|
|
20
|
+
console.error(chalk.yellow('\nPlease set your API key using one of these methods:'));
|
|
21
|
+
console.error(' 1. Environment variable: export NOWPAYMENTS_API_KEY=your_key');
|
|
22
|
+
console.error(' 2. .env file: NOWPAYMENTS_API_KEY=your_key');
|
|
23
|
+
console.error(' 3. Command flag: --api-key your_key');
|
|
24
|
+
console.error(' 4. Save to config: nowpayments auth set your_key');
|
|
25
|
+
console.error(chalk.cyan('\nGet your API key at: https://nowpayments.io'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return apiKey;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get authentication headers for API requests
|
|
34
|
+
* @param {Object} program - Commander program instance
|
|
35
|
+
* @returns {Object} Headers object with authentication
|
|
36
|
+
*/
|
|
37
|
+
function getAuthHeaders(program) {
|
|
38
|
+
const apiKey = requireApiKey(program);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
'x-api-key': apiKey,
|
|
42
|
+
'Content-Type': 'application/json'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate API key format (basic check)
|
|
48
|
+
* @param {string} apiKey - API key to validate
|
|
49
|
+
* @returns {boolean} Whether the key appears valid
|
|
50
|
+
*/
|
|
51
|
+
function validateApiKeyFormat(apiKey) {
|
|
52
|
+
// Basic validation - NOWPayments API keys are typically alphanumeric
|
|
53
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check length (typical API keys are 32-64 characters)
|
|
58
|
+
if (apiKey.length < 20 || apiKey.length > 100) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check for valid characters (alphanumeric and common symbols)
|
|
63
|
+
return /^[a-zA-Z0-9_-]+$/.test(apiKey);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
requireApiKey,
|
|
68
|
+
getAuthHeaders,
|
|
69
|
+
validateApiKeyFormat
|
|
70
|
+
};
|