← Tình huống

Đưa webhook bên thứ ba tới trang người dùng đang mở

Thanh toán đã xác nhận, kiện hàng di chuyển, build xong: chuyển webhook từ Stripe, đơn vị vận chuyển hay GitHub tới trang người dùng đang xem, không cần tải lại.

Khách thanh toán trên Stripe Checkout rồi quay lại trang "Cảm ơn" của bạn, nhưng việc xác nhận thanh toán đến qua webhook và có thể trễ vài giây. Trang hiện "Đang xác nhận thanh toán…", khách tải lại trang, hoặc tệ hơn là trả tiền lần nữa. Đơn vị vận chuyển, dịch vụ CI, công cụ ký điện tử và sàn thương mại cũng vậy: tin tức đến máy chủ của bạn, chứ không đến trang người dùng đang xem. Với Hibira, trình xử lý webhook chuyển cập nhật tới trang đó ngay khi nhận được.

Người dùng nhận được gì

  • "Đã nhận thanh toán" hiện ngay trên trang đang xem, không cần tải lại.
  • Cập nhật vận chuyển, build và chữ ký tự xuất hiện.
  • Không còn ai trả tiền hai lần vì tưởng lần đầu bị lỗi.

Xây dựng bằng

  • Kênh riêng tư (private-order-42) mà chỉ chủ đơn hàng được tham gia, do endpoint cấp quyền của bạn kiểm tra.
  • trigger ngay trong trình xử lý webhook đã xác minh chữ ký của nhà cung cấp và lưu thay đổi.
  • pusher:subscription_succeeded để đọc trạng thái hiện tại một lần, nên webhook đến trước khi trang mở cũng không bị bỏ lỡ.
Trình duyệtMáy chủ của bạnStripeHibirathanh toán trên Checkoutchuyển về /orders/42"Đang xác nhận thanh toán…"webhookcheckout.session.completedtrigger("status")private-order-42status: paidhiện biên nhận
Nhà cung cấp báo máy chủ của bạn; máy chủ báo trang qua Hibira

Bước 1: chuyển tiếp webhook sau khi lưu

Xác minh chữ ký, bỏ qua sự kiện đã xử lý (nhà cung cấp sẽ gửi lại), lưu trạng thái mới, rồi phát:

app.post('/stripe/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
  if (await db.webhookEvents.seen(event.id)) return res.sendStatus(200);
  if (event.type === 'checkout.session.completed') {
    const orderId = event.data.object.metadata.orderId;
    await db.orders.update(orderId, { status: 'paid' });
    await pusher.trigger(`private-order-${orderId}`, 'status', { status: 'paid' });
  }
  res.sendStatus(200);
});

Bước 2: chỉ cho chủ đơn lắng nghe

Endpoint cấp quyền kênh của bạn quyết định ai được đăng ký:

app.post('/pusher/auth', requireLogin, express.urlencoded({ extended: false }), async (req, res) => {
  const id = req.body.channel_name.match(/^private-order-(.+)$/)?.[1];
  if (!id || !(await db.orders.ownedBy(id, req.user.id))) return res.sendStatus(403);
  res.json(pusher.authorizeChannel(req.body.socket_id, req.body.channel_name));
});

Bước 3: lắng nghe trên trang và đọc trạng thái một lần

Đăng ký trước, rồi đọc trạng thái hiện tại khi đăng ký sẵn sàng, để webhook đến trước hay sau khi trang tải cũng không sao:

const channel = pusher.subscribe('private-order-42');
channel.bind('status', ({ status }) => show(status));
channel.bind('pusher:subscription_succeeded', async () => {
  const order = await fetch('/api/orders/42').then((r) => r.json());
  show(order.status);
});

Các webhook khác cũng làm như vậy

Mẹo

  • Trả 200 cho nhà cung cấp thật nhanh. Việc chậm hãy làm sau khi phản hồi hoặc trong tác vụ nền, xem tiến độ cho tác vụ dài.
  • Chỉ chuyển những gì trang cần (trạng thái và ID), đừng chuyển payload gốc của nhà cung cấp: nó có thể chứa dữ liệu cá nhân.
  • Nếu người dùng đã đóng trang, gửi thông báo đẩy theo hướng dẫn thông báo đẩy.
  • Với dashboard của cả nhóm, dùng một kênh cho mỗi nhóm thay vì mỗi đơn hàng.