Skip to main content

ยกเลิกนัดหมายผ่าน Postback

ภาพรวม

เมื่อระบบส่งข้อความยืนยันนัดหมายไปหาผู้ใช้ LINE ข้อความนั้นมักมีปุ่ม "ยกเลิกนัด" แนบไปด้วย ปุ่มดังกล่าวถูกฝัง postback data ในรูปแบบ appt_cancel:{bookingId} ดังนั้นเมื่อผู้ใช้กดปุ่ม LINE จะส่ง postback event เข้ามาที่ webhook ตัวนี้

หน้าที่ของ webhook-go คือ แปลง postback ให้กลายเป็นคำสั่งยกเลิก แล้วส่งลงคิว booking_notification เพื่อให้ worker-go ไปยกเลิกจริงในฐานข้อมูลและแจ้งเตือนกลับ

แม้จะเป็นฟีเจอร์ขนาดเล็ก แต่ถูกแยกเป็นหัวข้อเฉพาะ เนื่องจากเป็น เส้นทางเดียวในโปรเจกต์ ที่ publish ลงคิว booking_notification และเป็นตัวอย่างของ postback routing ที่ hardcode prefix ไว้ในโค้ด

Business Flow

  1. LINE ส่ง event ที่มี type: "postback" เข้ามาที่ POST /api/line/:id

  2. ProcessLine ผ่านขั้นตอนอ่าน webhook_config มาแล้ว โดยต้องมี config และ mboxEnabled == "1"

    :::note ข้อสังเกตสำคัญ เงื่อนไข postback ทั้งบล็อกถูกครอบด้วย mboxEnabled == "1" ซึ่งหมายความว่า OA ที่ไม่ได้เปิด mbox จะยกเลิกนัดผ่านปุ่มนี้ไม่ได้ เพราะ postback จะตกไปยังเส้นทาง default คือ publish ลงคิว line_webhook แทน พฤติกรรมนี้เป็น parity ตามระบบต้นฉบับ NestJS :::

  3. อ่าน postback.data ของ postback event ตัวแรก

  4. หากไม่ขึ้นต้นด้วย mbox_team: แต่ขึ้นต้นด้วย appt_cancel: จะเข้าสู่เส้นทางนี้

  5. ตัดเอาส่วนหลังเครื่องหมาย colon แรกมา parse เป็น int64 หาก parse ไม่ผ่านจะ log error processLine: appt_cancel invalid bookingId แล้ว return โดยไม่ publish และไม่ตกไปเส้นทาง default จุดนี้เป็นการ guard ที่จงใจ เพื่อเลี่ยงการ parse แบบเงียบที่จะให้ค่า 0

  6. publish bare JSON ลงคิว booking_notification ในรูปแบบ

{
"type": "cancel",
"bookingId": 12345,
"userId": "LINE user id จาก event.source.userId",
"lineOaId": 123
}
  1. จากนั้น return ทันทีโดยไม่ publish ลงคิว line_webhook เพราะบอทไม่จำเป็นต้องเห็น postback นี้

ไฟล์และฟังก์ชันหลัก

ฟีเจอร์นี้เข้าถึงผ่าน endpoint POST /api/line/:id (ดู ประตูรับ Webhook จาก LINE) โดยไม่มี route ของตัวเอง

ไฟล์จุดที่เกี่ยวข้อง
internal/line/service.goService.ProcessLine ในบล็อกที่ตรวจ prefix appt_cancel:
internal/line/service.gotype bookingCancelPayload ประกอบด้วย Type, BookingID, UserID และ LineOaID
internal/line/service.gosplitSecond(data) ตัดเอาส่วนที่สองหลัง colon โดยใช้ SplitN
internal/line/service.gopostbackData(e), sourceUserID(e), parseLineOaID(raw)

ชื่อคิวปลายทางมาจาก deps.Config.RabbitMQ.QueueBookingNotification ซึ่งอ่านจากตัวแปร RABBITMQ_QUEUE_BOOKING_NOTIFICATION และมีค่าเริ่มต้นเป็น booking_notification

จุดเชื่อมต่อกับ Service อื่น

  • RabbitMQ — คิว booking_notification บน exchange line_exchange
  • worker-go — เป็น consumer ที่อัปเดตสถานะ booking ในฐานข้อมูล แล้วส่งข้อความยืนยันการยกเลิกกลับหาผู้ใช้ LINE และแจ้งเจ้าของร้าน
  • โดเมนฐานข้อมูล — เกี่ยวข้องกับโดเมน appointment ซึ่งเก็บข้อมูล booking แต่ webhook-go ไม่ได้แตะฐานข้อมูลเอง เพียงส่ง bookingId ไปให้ worker เท่านั้น
  • cms-api — โมดูล apps-appointment เป็นผู้สร้าง booking และเป็นผู้กำหนดว่า ปุ่มยกเลิกจะฝัง postback data ในรูปแบบ appt_cancel: ตามด้วย id
  • Rediswebhook_config ต้องมีค่า lineOaId และ mboxEnabled เป็น "1" (ดู Redis Cache ของ Webhook Config)
  • โค้ดใช้บล็อกเดียวกับ ส่งต่อให้เจ้าหน้าที่ โดย postback แบบ mbox_team: จะถูกตรวจก่อน