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>

175 lines (149 loc) • 6.74 kB
const axios = require('axios'); const crypto = require('crypto'); class PhonePeClient { constructor(config) { const requiredFields = ['merchantId', 'saltKey', 'saltIndex']; for (let field of requiredFields) { if (!config[field]) throw new Error(`Missing required field: ${field}`); } this.merchantId = config.merchantId; this.saltKey = config.saltKey; this.saltIndex = config.saltIndex; this.baseURL = config.sandbox ? 'https://api-preprod.phonepe.com/apis/pg-sandbox' : 'https://api.phonepe.com/apis/hermes'; this.client = axios.create({ baseURL: this.baseURL, headers: { 'Content-Type': 'application/json' } }); } generateChecksum(payload) { const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64'); const checksumString = base64Payload + '/pg/v1/pay' + this.saltKey; const checksum = crypto.createHash('sha256').update(checksumString).digest('hex'); return checksum + '###' + this.saltIndex; } verifyChecksum(base64Response, checksumHeader) { const [receivedChecksum] = checksumHeader.split('###'); const checksumString = base64Response + this.saltKey; const expectedChecksum = crypto.createHash('sha256').update(checksumString).digest('hex'); return receivedChecksum === expectedChecksum; } async createPayment(options) { try { const orderId = options.orderId || `ORDER-${Date.now()}`; const transactionId = `TXN-${Date.now()}`; const payload = { merchantId: this.merchantId, merchantTransactionId: transactionId, merchantUserId: options.userId || `USER-${Date.now()}`, amount: Math.round(parseFloat(options.amount) * 100), // Amount in paise redirectUrl: options.successUrl || options.callback_link, redirectMode: 'POST', callbackUrl: options.callbackUrl || options.callback_link, mobileNumber: options.phone || '', paymentInstrument: { type: options.paymentType || 'PAY_PAGE' // PAY_PAGE, UPI_COLLECT, etc. } }; const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64'); const checksum = this.generateChecksum(payload); const response = await this.client.post('/pg/v1/pay', { request: base64Payload }, { headers: { 'X-VERIFY': checksum } }); if (response.data.success) { return { status: 'success', data: { url: response.data.data.instrumentResponse.redirectInfo.url, transactionId: transactionId, orderId: orderId, amount: payload.amount / 100, currency: 'INR' } }; } else { throw new Error(response.data.message || 'Payment creation failed'); } } catch (error) { throw new Error(`Payment creation error: ${error.response?.data?.message || error.message}`); } } async handleCallback(callbackData) { try { const base64Response = callbackData.response; const checksumHeader = callbackData['X-VERIFY']; // Verify checksum if (!this.verifyChecksum(base64Response, checksumHeader)) { throw new Error('Invalid checksum'); } // Decode response const response = JSON.parse(Buffer.from(base64Response, 'base64').toString()); // Status mapping const statusMapping = { 'PAYMENT_SUCCESS': 'success', 'PAYMENT_PENDING': 'pending', 'PAYMENT_DECLINED': 'failed', 'PAYMENT_ERROR': 'failed', 'INTERNAL_SERVER_ERROR': 'failed' }; return { status: statusMapping[response.code] || 'unknown', orderId: response.data.merchantOrderId, transactionId: response.data.merchantTransactionId, amount: response.data.amount / 100, currency: 'INR', paymentStatus: response.code, paymentMethod: response.data.paymentInstrument?.type }; } catch (error) { throw new Error(`Error in PhonePe callback handling: ${error.message}`); } } async checkStatus(transactionId) { try { const checksumString = `/pg/v1/status/${this.merchantId}/${transactionId}` + this.saltKey; const checksum = crypto.createHash('sha256').update(checksumString).digest('hex') + '###' + this.saltIndex; const response = await this.client.get(`/pg/v1/status/${this.merchantId}/${transactionId}`, { headers: { 'X-VERIFY': checksum, 'X-MERCHANT-ID': this.merchantId } }); return response.data; } catch (error) { throw new Error(`Error checking status: ${error.response?.data?.message || error.message}`); } } async refund(transactionId, options = {}) { try { const refundId = `REFUND-${Date.now()}`; const payload = { merchantId: this.merchantId, merchantTransactionId: refundId, originalTransactionId: transactionId, amount: Math.round(parseFloat(options.amount) * 100), callbackUrl: options.callbackUrl }; const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64'); const checksum = this.generateChecksum(payload); const response = await this.client.post('/pg/v1/refund', { request: base64Payload }, { headers: { 'X-VERIFY': checksum } }); return response.data; } catch (error) { throw new Error(`Error processing refund: ${error.response?.data?.message || error.message}`); } } } module.exports = PhonePeClient;