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:
@@ -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';
|
||||
|
||||
111
e2e/fixtures/payments.fixture.ts
Normal file
111
e2e/fixtures/payments.fixture.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user