|
| 1 | +import { PaymentStatus } from '@base/server/db/schemas' |
| 2 | +import { z } from 'zod' |
| 3 | + |
| 4 | +export default defineEventHandler(async (event) => { |
| 5 | + try { |
| 6 | + // { |
| 7 | + // gateway: "ACB", |
| 8 | + // transactionDate: "2025-05-23 23:34:40", |
| 9 | + // accountNumber: "17228427", |
| 10 | + // subAccount: null, |
| 11 | + // code: "SPN8NHOSTING123", |
| 12 | + // content: "MBVCB.9604208212.518683.SPN8NHOSTING123.CT tu 0041000331568 NGUYEN HUU NGUYEN Y toi 17228427 NGUYEN HUU NGUYEN Y tai ACB GD 518683-052325 23:34:40", |
| 13 | + // transferType: "in", |
| 14 | + // description: "BankAPINotify MBVCB.9604208212.518683.SPN8NHOSTING123.CT tu 0041000331568 NGUYEN HUU NGUYEN Y toi 17228427 NGUYEN HUU NGUYEN Y tai ACB GD 518683-052325 23:34:40", |
| 15 | + // transferAmount: 2000, |
| 16 | + // referenceCode: "3165", |
| 17 | + // accumulated: 0, |
| 18 | + // id: 13425123, |
| 19 | + // } |
| 20 | + const body = await readValidatedBody( |
| 21 | + event, |
| 22 | + payload => z.object({ |
| 23 | + accountNumber: z.string(), |
| 24 | + accumulated: z.number(), |
| 25 | + code: z.string(), |
| 26 | + content: z.string(), |
| 27 | + description: z.string(), |
| 28 | + gateway: z.string(), |
| 29 | + id: z.number(), |
| 30 | + referenceCode: z.string(), |
| 31 | + subAccount: z.string().optional().nullable(), |
| 32 | + transactionDate: z.string(), |
| 33 | + transferAmount: z.number(), |
| 34 | + transferType: z.string(), |
| 35 | + }).partial().parse(payload), |
| 36 | + ) |
| 37 | + |
| 38 | + if (process.env.SEPAY_WEBHOOK_SIGNING_KEY !== getHeader(event, 'Authorization')?.match(/Apikey (.*)/)?.[1]) { |
| 39 | + logger.error('[SePay Webhook] Invalid webhook authentication') |
| 40 | + throw createError({ |
| 41 | + statusCode: 401, |
| 42 | + message: 'Invalid webhook authentication', |
| 43 | + }) |
| 44 | + } |
| 45 | + |
| 46 | + logger.log('[SePay Webhook] Verified webhook data:', body) |
| 47 | + |
| 48 | + // SePay Webhook always success (if not, it will not call this endpoint anyway) |
| 49 | + |
| 50 | + const transactionStatus = PaymentStatus.RESOLVED |
| 51 | + |
| 52 | + const { updatePaymentStatus, updateProviderTransactionStatus, getProviderTransactionByOrderCode } = usePayment() |
| 53 | + |
| 54 | + const paymentTransactionOfProvider = await getProviderTransactionByOrderCode(String(body.code)) |
| 55 | + |
| 56 | + if (!paymentTransactionOfProvider?.payment.order.package) { |
| 57 | + logger.warn(`[SePay Webhook] Transaction not found or invalid: code=${body.code}`) |
| 58 | + return { success: true } |
| 59 | + } |
| 60 | + |
| 61 | + logger.log(`[SePay Webhook] Processing transaction: code=${body.code}, status=${transactionStatus}`) |
| 62 | + |
| 63 | + const priceDiscount = Number(paymentTransactionOfProvider.payment.order.package.price_discount) |
| 64 | + const price = Number(paymentTransactionOfProvider.payment.order.package.price) |
| 65 | + |
| 66 | + if (priceDiscount !== Number(body.transferAmount) && price !== Number(body.transferAmount)) { |
| 67 | + logger.error(`[SePay Webhook] Amount mismatch, transaction [${paymentTransactionOfProvider.id}]: expected=${price}, received=${body.transferAmount}`) |
| 68 | + |
| 69 | + throw createError({ |
| 70 | + statusCode: 400, |
| 71 | + message: 'Amount mismatch!', |
| 72 | + }) |
| 73 | + } |
| 74 | + |
| 75 | + const creditAmount = Number(paymentTransactionOfProvider.payment.order.package.amount) |
| 76 | + const userId = paymentTransactionOfProvider.payment.order.user_id |
| 77 | + |
| 78 | + // The userId is already the UUID from our database since we've updated |
| 79 | + // our schemas to use UUID references between tables |
| 80 | + logger.log(`[SePay Webhook] Adding credits: userId=${userId}, amount=${creditAmount}`) |
| 81 | + |
| 82 | + await addCreditToUser(userId, creditAmount) |
| 83 | + |
| 84 | + logger.log(`[SePay Webhook] Credits added successfully: userId=${userId}, amount=${creditAmount}`) |
| 85 | + |
| 86 | + if (!paymentTransactionOfProvider?.payment.order.package) { |
| 87 | + logger.error(`[SePay Webhook] No product found for transaction: ${body.code}`) |
| 88 | + throw createError({ |
| 89 | + statusCode: 400, |
| 90 | + message: 'No product found for this transaction!', |
| 91 | + }) |
| 92 | + } |
| 93 | + |
| 94 | + logger.log(`[SePay Webhook] Updating transaction ${paymentTransactionOfProvider.id} to status: ${transactionStatus}`) |
| 95 | + |
| 96 | + await updateProviderTransactionStatus(paymentTransactionOfProvider.id, transactionStatus, body.transactionDate!) |
| 97 | + |
| 98 | + await updatePaymentStatus(paymentTransactionOfProvider.payment.id, transactionStatus) |
| 99 | + |
| 100 | + logger.log(`[SePay Webhook] Transaction updated successfully: id=${paymentTransactionOfProvider.id}, status=${transactionStatus}`) |
| 101 | + |
| 102 | + logger.log('[SePay Webhook] Webhook processing completed successfully') |
| 103 | + return { success: true } |
| 104 | + } |
| 105 | + catch (error: any) { |
| 106 | + logger.error('[SePay Webhook] Error processing webhook:', error) |
| 107 | + |
| 108 | + throw parseError(error) |
| 109 | + } |
| 110 | +}) |
0 commit comments