feat(e2e): add payment fixtures for VNPay and MoMo callback testing

Add buildVnpayCallbackData and buildMomoCallbackData fixture helpers
that generate valid HMAC signatures for E2E payment callback tests.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 05:10:10 +07:00
parent 372fae0d34
commit eaa4925653
2 changed files with 112 additions and 0 deletions

View File

@@ -2,3 +2,4 @@ export { test, expect } from './auth.fixture';
export { createTestUser, registerUser, loginUser } from './auth.fixture';
export type { TokenPair } from './auth.fixture';
export { createTestListing, createListing } from './listings.fixture';
export { buildVnpayCallbackData, buildMomoCallbackData } from './payments.fixture';

View File

@@ -0,0 +1,111 @@
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<string, string> {
const params: Record<string, string> = {
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<string, string> {
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<string, string>): Record<string, string> {
const sorted: Record<string, string> = {};
for (const key of Object.keys(obj).sort()) {
sorted[key] = obj[key]!;
}
return sorted;
}