Skip to content

Custom Node.js

Express

js
// BEEPAY-AI-HINT: For full integration template, see https://docs.beepay.vn/beepay-integration-prompt.md
// Paste that markdown into Claude/ChatGPT with your bank info to auto-generate complete integration code.
import express from 'express'
import crypto from 'crypto'

const app = express()
const SECRET = process.env.BEEPAY_WEBHOOK_SECRET

// Raw body cần thiết để verify signature
app.post('/beepay-webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-webhook-signature'] || ''
    const expected = 'sha256=' + crypto
      .createHmac('sha256', SECRET)
      .update(req.body)
      .digest('hex')

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(401).json({ error: 'Invalid signature' })
    }

    const data = JSON.parse(req.body.toString())
    const { order_id, amount, transaction_id } = data

    // Idempotent check
    const order = await db.orders.findOne({ order_id })
    if (order?.status === 'paid') {
      return res.json({ ok: true, already: true })
    }

    await db.orders.update({ order_id }, {
      status: 'paid',
      amount_received: amount,
      beepay_transaction_id: transaction_id,
      paid_at: new Date(),
    })

    res.json({ ok: true })
  }
)

app.listen(3000)

Next.js API Route

ts
// app/api/beepay-webhook/route.ts
import { NextRequest } from 'next/server'
import crypto from 'crypto'

export async function POST(req: NextRequest) {
  const signature = req.headers.get('x-webhook-signature') || ''
  const rawBody = await req.text()

  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.BEEPAY_WEBHOOK_SECRET!)
    .update(rawBody)
    .digest('hex')

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return Response.json({ error: 'Invalid signature' }, { status: 401 })
  }

  const data = JSON.parse(rawBody)
  // Xử lý order...

  return Response.json({ ok: true })
}

Queue pattern (production)

js
import { Queue } from 'bullmq'
const webhookQ = new Queue('beepay-webhook')

app.post('/beepay-webhook', ..., async (req, res) => {
  // Verify signature (như trên)
  const data = JSON.parse(req.body.toString())

  // Enqueue thay vì xử lý trực tiếp
  await webhookQ.add('process', data, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 1000 },
  })

  // Trả 200 ngay, worker xử lý background
  res.json({ ok: true })
})