neppayments
Version:
A simple and easy-to-use package for integrating Nepali payment gateways (Khalti and eSewa) into your applications
149 lines (148 loc) • 6.19 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KhaltiGateway = void 0;
const axios_1 = __importDefault(require("axios"));
const payment_enums_1 = require("../types/payment.enums");
function isApiError(error) {
return typeof error === 'object' && error !== null &&
('error_key' in error || 'detail' in error);
}
/**
* Khalti payment gateway implementation
*/
class KhaltiGateway {
/**
* Create a new Khalti gateway instance
* @param config Khalti configuration
*/
constructor(config) {
this.config = config;
if (!config.secretKey) {
throw new payment_enums_1.PaymentError('Khalti secret key is required', payment_enums_1.PaymentErrorCode.INVALID_CONFIG);
}
// Set base URL based on environment
this.baseUrl = config.environment === 'sandbox'
? 'https://dev.khalti.com/api/v2'
: 'https://khalti.com/api/v2';
}
/**
* Validate payment options
* @private
*/
validatePaymentOptions(options) {
// Validate amount
if (options.amount < 1000) {
throw new payment_enums_1.PaymentError('Amount should be greater than Rs. 10 (1000 paisa)', payment_enums_1.PaymentErrorCode.INVALID_AMOUNT);
}
// Validate URLs
if (!options.return_url || !options.website_url) {
throw new payment_enums_1.PaymentError('return_url and website_url are required', payment_enums_1.PaymentErrorCode.INVALID_URL);
}
try {
new URL(options.return_url);
new URL(options.website_url);
}
catch (_a) {
throw new payment_enums_1.PaymentError('Invalid return_url or website_url', payment_enums_1.PaymentErrorCode.INVALID_URL);
}
// Validate amount breakdown if provided
if (options.amount_breakdown) {
const total = options.amount_breakdown.reduce((sum, item) => sum + item.amount, 0);
if (total !== options.amount) {
throw new payment_enums_1.PaymentError('Sum of amount_breakdown amounts must equal the total amount', payment_enums_1.PaymentErrorCode.INVALID_AMOUNT);
}
}
// Validate product details if provided
if (options.product_details) {
for (const product of options.product_details) {
if (product.total_price !== product.unit_price * product.quantity) {
throw new payment_enums_1.PaymentError('Product total_price must equal unit_price * quantity', payment_enums_1.PaymentErrorCode.INVALID_AMOUNT);
}
}
}
}
/**
* Create a new payment
* @param options Payment options
* @returns Payment response with payment URL and ID
*/
async createPayment(options) {
try {
// Validate options
this.validatePaymentOptions(options);
const response = await axios_1.default.post(`${this.baseUrl}/epayment/initiate/`, {
amount: options.amount,
purchase_order_id: options.purchase_order_id,
purchase_order_name: options.purchase_order_name,
return_url: options.return_url,
website_url: options.website_url,
customer_info: options.customer_info,
amount_breakdown: options.amount_breakdown,
product_details: options.product_details,
merchant_username: options.merchant_username,
merchant_extra: options.merchant_extra
}, {
headers: {
Authorization: `Key ${this.config.secretKey}`
}
});
const data = response.data;
return {
pidx: data.pidx,
payment_url: data.payment_url,
expires_at: data.expires_at,
expires_in: data.expires_in
};
}
catch (error) {
if (isApiError(error)) {
if (error.error_key === 'validation_error') {
throw new payment_enums_1.PaymentError(Object.values(error).filter(v => typeof v === 'string').join(', '), payment_enums_1.PaymentErrorCode.VALIDATION_ERROR);
}
throw new payment_enums_1.PaymentError(error.detail || 'Failed to create payment', payment_enums_1.PaymentErrorCode.PAYMENT_FAILED);
}
throw error;
}
}
/**
* Verify a payment
* @param options Verification options
* @returns Payment verification response
*/
async verifyPayment(options) {
try {
const response = await axios_1.default.post(`${this.baseUrl}/epayment/lookup/`, {
pidx: options.pidx
}, {
headers: {
Authorization: `Key ${this.config.secretKey}`
}
});
const payment = response.data;
const allowedStatuses = ['COMPLETED', 'FAILED', 'PENDING', 'EXPIRED', 'CANCELED'];
let status = (payment.status || '').toUpperCase();
if (!allowedStatuses.includes(status)) {
status = 'FAILED';
}
return {
status: status,
transaction_id: payment.transaction_id,
amount: payment.amount,
payment_details: payment
};
}
catch (error) {
if (isApiError(error)) {
if (error.error_key === 'validation_error') {
throw new payment_enums_1.PaymentError(Object.values(error).filter(v => typeof v === 'string').join(', '), payment_enums_1.PaymentErrorCode.VALIDATION_ERROR);
}
throw new payment_enums_1.PaymentError(error.detail || 'Failed to verify payment', payment_enums_1.PaymentErrorCode.VERIFICATION_FAILED);
}
throw error;
}
}
}
exports.KhaltiGateway = KhaltiGateway;