UNPKG

quickpos

Version:

<div align="center"> <h1>💳 QuickPos 🚀</h1> <p><strong>A powerful, multi-gateway payment integration module for Node.js</strong></p> <p>Seamlessly integrate with 50+ payment providers worldwide</p>

246 lines (214 loc) • 8.29 kB
const axios = require('axios'); const crypto = require('crypto'); class CoinPaymentsClient { constructor(config) { const requiredFields = ['publicKey', 'privateKey']; for (let field of requiredFields) { if (!config[field]) throw new Error(`Missing required field: ${field}`); } this.publicKey = config.publicKey; this.privateKey = config.privateKey; this.URL = 'https://www.coinpayments.net/api.php'; this.ipnSecret = config.ipnSecret || ''; this.client = axios.create({ baseURL: this.URL, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); this.client.interceptors.response.use(response => { return response; }, error => { if (error.response) { throw new Error(`CoinPayments API error: ${error.response.data.error || error.message}`); } throw new Error(`CoinPayments API error: ${error.message}`); }); } generateHMAC(params) { const paramString = new URLSearchParams(params).toString(); return crypto .createHmac('sha512', this.privateKey) .update(paramString) .digest('hex'); } async apiCall(cmd, params = {}) { try { const requestParams = { version: 1, cmd: cmd, key: this.publicKey, format: 'json', ...params }; const hmac = this.generateHMAC(requestParams); const formData = new URLSearchParams(requestParams); const response = await axios.post(this.URL, formData, { headers: { 'HMAC': hmac, 'Content-Type': 'application/x-www-form-urlencoded' } }); if (response.data.error !== 'ok') { throw new Error(response.data.error || 'API call failed'); } return response.data.result; } catch (error) { throw new Error(`API call error: ${error.message}`); } } async createPayment(options) { try { const params = { amount: parseFloat(options.amount), currency1: options.currency1 || options.currency || 'USD', currency2: options.currency2 || 'BTC', buyer_email: options.buyerEmail || options.email, buyer_name: options.buyerName || options.name || 'Customer', item_name: options.itemName || options.name || 'Product', item_number: options.itemNumber || options.orderId || `ORDER-${Date.now()}`, invoice: options.invoice || options.orderId || `INV-${Date.now()}`, custom: options.custom || '', ipn_url: options.ipnUrl || options.callback_link }; const result = await this.apiCall('create_transaction', params); return { status: 'success', data: { txnId: result.txn_id, address: result.address, amount: result.amount, confirmsNeeded: result.confirms_needed, timeout: result.timeout, statusUrl: result.status_url, qrcodeUrl: result.qrcode_url, url: result.checkout_url || result.status_url } }; } catch (error) { throw new Error(`Payment creation error: ${error.message}`); } } async getTransactionInfo(txnId) { try { const result = await this.apiCall('get_tx_info', { txid: txnId }); return result; } catch (error) { throw new Error(`Transaction info error: ${error.message}`); } } async getCallbackAddress(currency) { try { const result = await this.apiCall('get_callback_address', { currency: currency, ipn_url: this.ipnUrl }); return result; } catch (error) { throw new Error(`Callback address error: ${error.message}`); } } async handleCallback(callbackData) { try { const verification = await this.verifyIPNCallback(callbackData); if (!verification.status) { throw new Error(verification.error.message); } const data = verification.data; // Status mapping // Status >= 100 or status == 2 means payment is complete const status = parseInt(data.status); let paymentStatus = 'pending'; if (status >= 100 || status === 2) { paymentStatus = 'success'; } else if (status < 0) { paymentStatus = 'failed'; } return { status: paymentStatus, txnId: data.txn_id, orderId: data.item_number || data.invoice, amount: parseFloat(data.amount1 || data.amount), currency: data.currency1, receivedAmount: parseFloat(data.amount2 || 0), receivedCurrency: data.currency2, fee: parseFloat(data.fee || 0), statusCode: status, statusText: data.status_text }; } catch (error) { throw new Error(`Error in CoinPayments callback handling: ${error.message}`); } } async verifyIPNCallback(data) { try { // Verify HMAC signature if available if (this.ipnSecret && data.hmac) { const merchant = data.merchant; delete data.hmac; delete data.merchant; const paramString = new URLSearchParams( Object.keys(data) .sort() .reduce((obj, key) => { obj[key] = data[key]; return obj; }, {}) ).toString(); const expectedHmac = crypto .createHmac('sha512', this.ipnSecret) .update(paramString) .digest('hex'); if (data.hmac !== expectedHmac) { return { status: false, error: { code: 401, message: 'Invalid IPN HMAC signature' } }; } data.merchant = merchant; } const status = parseInt(data.status); if (status < 0) { return { status: false, error: { code: 400, message: `Payment failed: ${data.status_text}` } }; } return { status: true, data: data }; } catch (error) { return { status: false, error: { code: 500, message: error.message } }; } } async getRates(short = 1, accepted = 1) { try { const result = await this.apiCall('rates', { short, accepted }); return result; } catch (error) { throw new Error(`Get rates error: ${error.message}`); } } async getBasicInfo() { try { const result = await this.apiCall('get_basic_info'); return result; } catch (error) { throw new Error(`Get basic info error: ${error.message}`); } } } module.exports = CoinPaymentsClient;