@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.
@@ -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
+ };
@@ -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
+ };