import * as crypto from 'crypto'; /** * Generates a valid VNPay callback payload with a correct HMAC-SHA512 signature. * * Used in E2E tests to simulate a real VNPay callback without needing * the actual sandbox environment. */ export function buildVnpayCallbackData( hashSecret: string, opts: { orderId: string; amountVND: number; responseCode?: string; transactionNo?: string; }, ): Record { const params: Record = { vnp_TxnRef: opts.orderId, vnp_ResponseCode: opts.responseCode ?? '00', vnp_Amount: (opts.amountVND * 100).toString(), vnp_TransactionNo: opts.transactionNo ?? `VNP${Date.now()}`, vnp_BankCode: 'NCB', vnp_CardType: 'ATM', vnp_OrderInfo: `E2E test payment ${opts.orderId}`, vnp_PayDate: formatVnpayDate(new Date()), vnp_TmnCode: process.env.VNPAY_TMN_CODE ?? 'TESTCODE', vnp_TransactionStatus: opts.responseCode === '00' ? '00' : '02', }; const sorted = sortObject(params); const signData = new URLSearchParams(sorted).toString(); const hmac = crypto.createHmac('sha512', hashSecret); const secureHash = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex'); return { ...sorted, vnp_SecureHash: secureHash }; } /** * Generates a valid MoMo IPN callback payload with a correct HMAC-SHA256 signature. */ export function buildMomoCallbackData( secretKey: string, opts: { orderId: string; amount: number; resultCode?: number; transId?: string; }, ): Record { const resultCode = opts.resultCode ?? 0; const transId = opts.transId ?? `MOMO${Date.now()}`; const requestId = crypto.randomUUID(); const extraData = ''; const message = resultCode === 0 ? 'Successful.' : 'Transaction failed.'; const orderInfo = `E2E test payment ${opts.orderId}`; const orderType = 'momo_wallet'; const payType = 'qr'; const responseTime = Date.now().toString(); const partnerCode = process.env.MOMO_PARTNER_CODE ?? 'TESTPARTNER'; const accessKey = process.env.MOMO_ACCESS_KEY ?? 'TESTACCESSKEY'; const rawSignature = [ `accessKey=${accessKey}`, `amount=${opts.amount}`, `extraData=${extraData}`, `message=${message}`, `orderId=${opts.orderId}`, `orderInfo=${orderInfo}`, `orderType=${orderType}`, `partnerCode=${partnerCode}`, `payType=${payType}`, `requestId=${requestId}`, `responseTime=${responseTime}`, `resultCode=${resultCode}`, `transId=${transId}`, ].join('&'); const signature = crypto .createHmac('sha256', secretKey) .update(rawSignature) .digest('hex'); return { orderId: opts.orderId, amount: opts.amount.toString(), extraData, message, orderInfo, orderType, partnerCode, payType, requestId, responseTime, resultCode: resultCode.toString(), transId, signature, }; } function formatVnpayDate(date: Date): string { return date.toISOString().replace(/[-:T]/g, '').slice(0, 14); } function sortObject(obj: Record): Record { const sorted: Record = {}; for (const key of Object.keys(obj).sort()) { sorted[key] = obj[key]!; } return sorted; }