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>
221 lines (191 loc) • 8 kB
JavaScript
const axios = require('axios');
const crypto = require('crypto');
class TripayClient {
constructor(config) {
const requiredFields = ['apiKey', 'privateKey', 'merchantCode'];
for (let field of requiredFields) {
if (!config[field]) throw new Error(`Missing required field: ${field}`);
}
this.apiKey = config.apiKey;
this.privateKey = config.privateKey;
this.merchantCode = config.merchantCode;
this.isProduction = config.isProduction || false;
this.URL = this.isProduction
? 'https://tripay.co.id/api'
: 'https://tripay.co.id/api-sandbox';
this.client = axios.create({
baseURL: this.URL,
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
this.client.interceptors.response.use(response => {
return response;
}, error => {
if (error.response) {
throw new Error(`Tripay API error: ${error.response.data.message || error.message}`);
}
throw new Error(`Tripay API error: ${error.message}`);
});
}
async getPaymentChannels() {
try {
const response = await this.client.get('/merchant/payment-channel');
return response.data;
} catch (error) {
throw new Error(`Payment channels error: ${error.message}`);
}
}
async createPayment(options) {
try {
const merchantRef = options.merchantRef || options.orderId || `ORDER-${Date.now()}`;
const amount = parseInt(options.amount);
// Generate signature
const signature = crypto
.createHmac('sha256', this.privateKey)
.update(this.merchantCode + merchantRef + amount)
.digest('hex');
const requestData = {
method: options.paymentMethod || 'BRIVA',
merchant_ref: merchantRef,
amount: amount,
customer_name: options.customerName || options.name || 'Customer',
customer_email: options.customerEmail || options.email,
customer_phone: options.customerPhone || options.phone || '08123456789',
order_items: options.orderItems || [{
sku: 'ITEM-001',
name: options.itemName || options.name || 'Product',
price: amount,
quantity: 1
}],
callback_url: options.callbackUrl || options.callback_link,
return_url: options.returnUrl || options.callbackUrl || options.callback_link,
expired_time: options.expiredTime || (Math.floor(Date.now() / 1000) + (24 * 60 * 60)),
signature: signature
};
const response = await this.client.post('/transaction/create', requestData);
if (!response.data.success) {
throw new Error(response.data.message || 'Payment creation failed');
}
return {
status: 'success',
data: {
reference: response.data.data.reference,
merchantRef: response.data.data.merchant_ref,
paymentUrl: response.data.data.checkout_url,
qrUrl: response.data.data.qr_url,
amount: response.data.data.amount,
fee: response.data.data.total_fee,
totalAmount: response.data.data.amount_received,
expiredTime: response.data.data.expired_time,
paymentMethod: response.data.data.payment_method,
paymentName: response.data.data.payment_name,
instructions: response.data.data.instructions
}
};
} catch (error) {
throw new Error(`Payment creation error: ${error.message}`);
}
}
async getTransactionDetail(reference) {
try {
const response = await this.client.get(`/transaction/detail?reference=${reference}`);
if (!response.data.success) {
throw new Error(response.data.message || 'Failed to get transaction detail');
}
return response.data.data;
} catch (error) {
throw new Error(`Transaction detail error: ${error.message}`);
}
}
async handleCallback(callbackData) {
try {
const verification = await this.verifyCallback(callbackData);
if (!verification.status) {
throw new Error(verification.error.message);
}
const data = verification.data;
// Status mapping
const statusMapping = {
'PAID': 'success',
'UNPAID': 'pending',
'EXPIRED': 'expired',
'FAILED': 'failed',
'REFUND': 'refunded'
};
return {
status: statusMapping[data.status] || 'unknown',
reference: data.reference,
merchantRef: data.merchant_ref,
orderId: data.merchant_ref,
amount: parseInt(data.amount),
totalAmount: parseInt(data.amount_received),
fee: parseInt(data.total_fee),
paymentMethod: data.payment_method,
paymentName: data.payment_name,
customerName: data.customer_name,
customerEmail: data.customer_email,
customerPhone: data.customer_phone,
paymentStatus: data.status,
paidAt: data.paid_at
};
} catch (error) {
throw new Error(`Error in Tripay callback handling: ${error.message}`);
}
}
async verifyCallback(data) {
try {
const callbackSignature = data.signature || '';
const privateKey = this.privateKey;
// Calculate signature
const json = JSON.stringify(data);
const expectedSignature = crypto
.createHmac('sha256', privateKey)
.update(json)
.digest('hex');
if (callbackSignature !== expectedSignature) {
return {
status: false,
error: {
code: 401,
message: 'Invalid callback signature'
}
};
}
if (data.status === 'FAILED') {
return {
status: false,
error: {
code: 400,
message: 'Payment failed'
}
};
}
return {
status: true,
data: data
};
} catch (error) {
return {
status: false,
error: {
code: 500,
message: error.message
}
};
}
}
async getFeeCalculator(amount, code) {
try {
const response = await this.client.get(`/merchant/fee-calculator?amount=${amount}&code=${code}`);
if (!response.data.success) {
throw new Error(response.data.message || 'Failed to calculate fee');
}
return response.data.data;
} catch (error) {
throw new Error(`Fee calculator error: ${error.message}`);
}
}
}
module.exports = TripayClient;