# ส่ง webhook จากบริการภายนอกไปยังหน้าที่ผู้ใช้เปิดอยู่

> ชำระเงินสำเร็จ พัสดุเคลื่อนที่ บิลด์เสร็จ: ส่งต่อ webhook จาก Stripe บริษัทขนส่ง หรือ GitHub ไปยังหน้าที่ผู้ใช้กำลังดู โดยไม่ต้องรีโหลด

Source: https://hibira.io/th/use-cases/webhooks-to-browser

ลูกค้าชำระเงินบน Stripe Checkout แล้วกลับมาที่หน้า "ขอบคุณ" ของคุณ แต่การยืนยันการชำระเงินมาทาง webhook ซึ่งอาจมาช้ากว่าไม่กี่วินาที หน้าเว็บจึงแสดง "กำลังยืนยันการชำระเงิน…" ลูกค้าก็รีโหลด หรือแย่กว่านั้นคือจ่ายซ้ำ บริษัทขนส่ง บริการ CI เครื่องมือเซ็นเอกสาร และมาร์เก็ตเพลสก็ทำงานแบบเดียวกัน ข่าวไปถึงเซิร์ฟเวอร์ของคุณ ไม่ใช่หน้าที่ผู้ใช้กำลังดู เมื่อใช้ Hibira ตัวจัดการ webhook จะส่งต่ออัปเดตไปยังหน้านั้นทันทีที่ได้รับ

## ผู้ใช้ของคุณได้อะไร

- เห็น "ได้รับการชำระเงินแล้ว" บนหน้าที่กำลังดูอยู่ โดยไม่ต้องรีโหลด
- อัปเดตการขนส่ง การ build และการเซ็นเอกสารแสดงขึ้นเอง
- ไม่มีการจ่ายซ้ำเพราะคิดว่าครั้งแรกล้มเหลว

## สร้างด้วย

- **ช่องส่วนตัว** (`private-order-42`) ที่เฉพาะเจ้าของคำสั่งซื้อเข้าได้ ตรวจสอบโดยปลายทางอนุญาตของคุณ
- **`trigger`** จากตัวจัดการ webhook เดียวกันที่ตรวจลายเซ็นของผู้ให้บริการและบันทึกการเปลี่ยนแปลง
- **`pusher:subscription_succeeded`** เพื่ออ่านสถานะปัจจุบันหนึ่งครั้ง webhook ที่มาก่อนหน้าเว็บเปิดจึงไม่ตกหล่น

```text
sequence: ผู้ให้บริการแจ้งเซิร์ฟเวอร์ของคุณ เซิร์ฟเวอร์แจ้งหน้าเว็บผ่าน Hibira
เบราว์เซอร์ | เซิร์ฟเวอร์ของคุณ | Stripe | *Hibira
1 > 3: ชำระเงินบน Checkout
3 >> 1: เปลี่ยนเส้นทางไป /orders/42 | "กำลังยืนยันการชำระเงิน…"
3 > 2: webhook | checkout.session.completed
2 > 4: trigger("status") | private-order-42
4 > 1: status: paid | แสดงใบเสร็จ
```

## ขั้นตอนที่ 1: ส่งต่อ webhook หลังบันทึก

ตรวจลายเซ็น ข้ามอีเวนต์ที่จัดการไปแล้ว (ผู้ให้บริการจะส่งซ้ำ) บันทึกสถานะใหม่ แล้วจึงส่ง:

```js
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);
});
```

## ขั้นตอนที่ 2: ให้เฉพาะเจ้าของรับฟัง

ปลายทางอนุญาตช่องของคุณเป็นผู้ตัดสินว่าใครสมัครได้:

```js
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));
});
```

## ขั้นตอนที่ 3: รอฟังบนหน้าเว็บ และอ่านสถานะหนึ่งครั้ง

สมัครก่อน แล้วอ่านสถานะปัจจุบันเมื่อการสมัครพร้อม ไม่ว่า webhook จะมาก่อนหรือหลังหน้าเว็บโหลดก็ไม่เป็นปัญหา:

```js
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);
});
```

## webhook อื่นก็ใช้วิธีเดียวกัน

| แหล่งที่มา | Webhook | สิ่งที่หน้าที่เปิดอยู่แสดง |
| --- | --- | --- |
| Stripe, PayPal, Adyen | ชำระเงินสำเร็จหรือล้มเหลว | ใบเสร็จ หรือปุ่มลองใหม่ |
| บริษัทขนส่ง | อัปเดตการติดตามพัสดุ | ขั้นตอนใหม่บนหน้าคำสั่งซื้อ |
| GitHub, GitLab, Vercel | build หรือ deploy เสร็จ | ป้ายสถานะและลิงก์พรีวิว |
| เครื่องมือเซ็นเอกสาร | เซ็นเอกสารแล้ว | "ทั้งสองฝ่ายเซ็นแล้ว" |
| Shopify, มาร์เก็ตเพลส | สร้างหรือชำระคำสั่งซื้อ | แถวใหม่บนแดชบอร์ดผู้ขาย |

## เคล็ดลับ

- ตอบผู้ให้บริการด้วย 200 ให้เร็ว งานที่ช้าให้ทำหลังตอบหรือในงานเบื้องหลัง ดูที่[ความคืบหน้าของงานที่ใช้เวลานาน](https://hibira.io/th/use-cases/job-progress)
- ส่งต่อเฉพาะสิ่งที่หน้าเว็บต้องใช้ (สถานะและ ID) อย่าส่งข้อมูลดิบของผู้ให้บริการ เพราะอาจมีข้อมูลส่วนบุคคล
- ถ้าผู้ใช้ปิดหน้าไปแล้ว ให้ส่งพุชตาม[คู่มือการแจ้งเตือนแบบพุช](https://hibira.io/th/guides/push-notifications)
- แดชบอร์ดที่ทั้งทีมใช้ร่วมกัน ใช้หนึ่งช่องต่อทีมแทนหนึ่งช่องต่อคำสั่งซื้อได้
