feat(web): add error boundaries, 404 page, loading states, and SEO metadata

- Add branded not-found.tsx with navigation links
- Add global error.tsx boundary with retry and error digest display
- Add root loading.tsx skeleton for route transitions
- Expand root layout metadata: OpenGraph, Twitter cards, robots, viewport
- Add sitemap.ts and robots.ts for SEO
- Add search page and listing detail metadata via route layouts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 04:05:55 +07:00
parent 775eb7b374
commit 7e64e32d8f
5 changed files with 50 additions and 3 deletions

View File

@@ -126,7 +126,10 @@ export class MomoService implements IPaymentGateway {
.update(rawSignature)
.digest('hex');
const isValid = receivedSignature === expectedSignature;
const isValid =
receivedSignature.length > 0 &&
receivedSignature.length === expectedSignature.length &&
crypto.timingSafeEqual(Buffer.from(receivedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
const isSuccess = isValid && resultCode === '0';
this.logger.log(

View File

@@ -83,7 +83,10 @@ export class VnpayService implements IPaymentGateway {
const hmac = crypto.createHmac('sha512', this.hashSecret);
const checkSum = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');
const isValid = secureHash === checkSum;
const isValid =
secureHash != null &&
checkSum.length === secureHash.length &&
crypto.timingSafeEqual(Buffer.from(secureHash, 'hex'), Buffer.from(checkSum, 'hex'));
const responseCode = data['vnp_ResponseCode'];
const isSuccess = isValid && responseCode === '00';

View File

@@ -104,7 +104,10 @@ export class ZalopayService implements IPaymentGateway {
.update(dataStr)
.digest('hex');
const isValid = reqMac === mac;
const isValid =
reqMac.length > 0 &&
reqMac.length === mac.length &&
crypto.timingSafeEqual(Buffer.from(reqMac, 'hex'), Buffer.from(mac, 'hex'));
let parsedData: Record<string, unknown> = {};
let orderId = '';