แคมเปญติดตามการเพิ่มเพื่อน
ภาพรวม
ฟีเจอร์นี้ใช้วัดผลแคมเปญหาเพื่อนใหม่ให้กับ LINE OA หลักการคือเมื่อผู้ใช้แตะลิงก์แคมเปญ ระบบจะบันทึกการเข้าชมพร้อมระบุตัวตนผู้ใช้ผ่าน LINE แล้วจึงพาผู้ใช้ไปยังหน้าเพิ่มเพื่อนของ OA ผลลัพธ์คือฝั่ง CMS สามารถรายงานได้ว่าเพื่อนใหม่แต่ละรายมาจากแคมเปญไหน และมาจากช่องทางย่อยใดภายในแคมเปญนั้นผ่านค่า referral
ฟีเจอร์นี้มีสองเส้นทางเข้า คือแบบระบุ token ของแคมเปญมาตรง ๆ และแบบให้ระบบค้นหาแคมเปญที่กำลังใช้งานอยู่จาก hash ของ OA
Business Flow
เส้นทางที่ระบุแคมเปญ (/:hash/friend-track/:token)
- ระบบดึงข้อมูล OA จาก hash และข้อมูลแคมเปญจาก token ของแคมเปญ
- หน้านี้เรียก
liff.init()เองโดยไม่ผ่าน hook กลาง เพื่อควบคุมลำดับการทำงานเอง หากผู้ใช้ยังไม่ได้ล็อกอิน ระบบจะพาไปยัง LIFF URL เมื่ออยู่ในแอป LINE หรือเรียกฟังก์ชันล็อกอินเมื่ออยู่นอกแอป - ดึง ID token โดยลองซ้ำได้ 3 ครั้ง และดึง access token ไว้เป็นตัวสำรอง หากไม่ได้ทั้งสองค่า ระบบจะ logout แล้วเริ่มล็อกอินใหม่
- ดึงโปรไฟล์ผู้ใช้เพื่อเอาชื่อที่แสดงและรูปโปรไฟล์ ซึ่งเป็นข้อมูลเสริมไม่ใช่ข้อมูลบังคับ
- บันทึกการเข้าชมด้วย
POST /friend-track/:token/visitโดยแนบทั้งx-liff-tokenและx-liff-access-tokenพร้อมส่งชื่อที่แสดง รูปโปรไฟล์ และค่า referral ไปใน body - ตัดสินใจตามผลลัพธ์ที่ได้กลับมาว่าผู้ใช้เป็นเพื่อนกับ OA อยู่แล้วหรือยัง
- เป็นเพื่อนอยู่แล้ว หน้าจอแจ้งให้ผู้ใช้ทราบ แล้วพาไปเปิดห้องแชทของ OA อัตโนมัติภายใน 2 วินาที โดยมีปุ่มเปิดแชทให้กดเองด้วยหากไม่ต้องการรอ
- ยังไม่เป็นเพื่อน ระบบพาไปยังหน้าเพิ่มเพื่อนของ OA ทันที
- ค่า referral อ่านได้ทั้งจาก query parameter โดยตรง และจากภายใน
liff.stateเนื่องจาก query parameter เดิมหายไปในขั้นตอน redirect ของการล็อกอิน
เส้นทางที่ไม่ระบุแคมเปญ (/:hash/friend-track)
เส้นทางนี้ใช้ในกรณีที่ endpoint ของ LIFF app ถูกตั้งให้ชี้มาที่ path นี้ ขั้นตอนโดยรวมเหมือนกัน แต่มีความต่างสามข้อ
- ระบบพยายามอ่าน token ของแคมเปญจาก
liff.stateก่อน - หากไม่พบ ระบบจะเรียก
GET /friend-track/by-hash/:hashเพื่อค้นหาแคมเปญที่กำลังใช้งานอยู่ของ OA นั้น - หลังบันทึกการเข้าชม ระบบจะพาไปหน้าเพิ่มเพื่อนทันที โดยไม่มีสถานะ "เป็นเพื่อนอยู่แล้ว"
จุดเข้าอีกทาง: ปุ่ม CTA ในบทความ
ปุ่ม CTA ที่แสดงในหน้าอ่านบทความจะเก็บ token ของแคมเปญไว้ใน sessionStorage แล้วพาผู้ใช้ไปยัง LIFF URL ของแคมเปญนั้นพร้อมค่า referral
การเลือกใช้โดเมน liff.line.me แทน custom protocol แบบ line://app/ เป็นการตัดสินใจโดยเจตนา เนื่องจาก protocol แบบหลังไม่ส่ง liff.state มาเป็น query parameter เมื่อเปิดจาก in-app browser ของ LINE ซึ่งจะทำให้ระบบสูญเสียข้อมูลแคมเปญและ referral ไป
หน้าจอและองค์ประกอบหลัก
หน้าเพจ
- หน้าแคมเปญที่ระบุ token (
src/app/[hash]/friend-track/[token]/page.tsx) จัดการสถานะครบทุกช่วง ตั้งแต่กำลังโหลด กำลังประมวลผล เป็นเพื่อนอยู่แล้ว กำลังพาไปปลายทาง และเกิดข้อผิดพลาด - หน้าแคมเปญแบบไม่ระบุ token (
src/app/[hash]/friend-track/page.tsx) รองรับกรณีที่ LIFF app ชี้ endpoint มาที่ path นี้
Hook และ service
- Hook ดึงข้อมูลแคมเปญ (
src/hooks/use-friend-track.ts) มีหน้าที่พิเศษคือตัด query string ที่ LIFF ต่อท้าย token ออกก่อนนำไปใช้ โดยรองรับทั้งกรณีที่เครื่องหมายคำถามถูก encode และไม่ถูก encode - Friend track service (
src/service/friend-track.service.ts) ให้ฟังก์ชันดึงแคมเปญตาม token ดึงแคมเปญตาม hash ของ OA และบันทึกการเข้าชม
คอมโพเนนต์ CTA
- ปุ่ม CTA แบบลอย (
src/components/content-viewer/FriendTrackCta.tsx) แสดงในหน้าอ่านบทความ พร้อมแอนิเมชันดึงความสนใจ
Endpoint ที่ใช้
| Method | Path | คืนค่า |
|---|---|---|
| GET | /friend-track/:token | ข้อมูลแคมเปญ ได้แก่ ชื่อ คำอธิบาย รหัสบอทของ OA และสถานะใช้งาน |
| GET | /friend-track/by-hash/:hash | แคมเปญที่ใช้งานอยู่ของ OA นั้น พร้อม token |
| POST | /friend-track/:token/visit | สถานะว่าเป็นเพื่อนอยู่แล้วหรือยัง พร้อมข้อความประกอบ |
จุดเชื่อมต่อกับฟีเจอร์อื่น
- @line/liff ถูกเรียกใช้โดยตรงในหน้านี้ ทั้งการ init การล็อกอิน การดึง ID token การดึง access token และการดึงโปรไฟล์
- ใช้ deep link ของ LINE สองรูปแบบ คือลิงก์หน้าเพิ่มเพื่อน และลิงก์เปิดห้องแชทของ OA
- เชื่อมกับ หน้าอ่านบทความ ผ่านข้อมูลแคมเปญที่ผูกไว้กับบทความ
- รหัสบอทของ OA ที่ใช้ประกอบ deep link มาจาก payload ของแคมเปญเอง ไม่ได้มาจาก endpoint ข้อมูล OA
- ระบบส่งทั้ง ID token และ access token ไปพร้อมกัน เนื่องจากฝั่ง API จะตรวจ ID token ก่อนแล้วจึง fallback ไปใช้ access token (ดู การล็อกอิน LINE ผ่าน LIFF)
รายละเอียดฝั่ง Backend (Client API)
การ verify token ที่ต่างจากฟีเจอร์อื่น
- backend ตรวจ ID token ก่อนเสมอ โดยตรวจกับ primary channel ของ OA — ถ้า OA ยังไม่ได้ตั้งค่า LINE Login จะได้ 401
LINE Login not configured for this channel - ถ้าไม่มี ID token จึงใช้ access token แทน; ไม่มีทั้งคู่ → 401
No authentication token provided(นี่คือเหตุผลที่หน้านี้ส่งทั้งสองค่ามาพร้อมกัน) - จุดที่ต่างจากฟีเจอร์อื่นและตั้งใจ: endpoint นี้เรียกตัว verify แบบดิบๆ ไม่ผ่านเส้นทางกลางที่ auto-provision guest ดังนั้น การเปิดหน้าแคมเปญจะไม่สร้างแถวผู้ใช้ใหม่ในระบบ ต่างจากหน้าฟอร์มหรือหน้าเมนู — เป็นการรักษาพฤติกรรมเดิมไว้ และมีผลตามมาที่สำคัญในเรื่อง custom attribute (ดูด้านล่าง)
กฎ dedup — ส่วนที่ละเอียดที่สุดของฟีเจอร์นี้
การยิง visit ซ้ำ ไม่ได้ถูกกันแบบตายตัว แต่มีเงื่อนไขที่ยอมให้นับใหม่ได้:
- backend หา event การเข้าชมครั้งล่าสุดของคู่ (แคมเปญ, ผู้ใช้)
- ถ้าเคยมี → หา event การเลิกติดตามครั้งล่าสุดของคู่ (ผู้ใช้, OA) แล้วเทียบเวลากัน
- ไม่มีการเลิกติดตามหลังการเข้าชมครั้งนั้น → ถือเป็น funnel เดิม คืน
alreadyTracked: trueโดยไม่เขียนอะไรลงฐานข้อมูลเลย - มีการเลิกติดตามหลังการเข้าชม → ถือเป็น funnel ใหม่ ไปต่อตามปกติ เพราะคนที่เลิกติดตามแล้วกลับมาอีกครั้งควรถูกนับใหม่
- ไม่มีการเลิกติดตามหลังการเข้าชมครั้งนั้น → ถือเป็น funnel เดิม คืน
ผลที่หน้าเว็บควรรู้: alreadyTracked: true ไม่ได้แปลว่าผิดพลาด แต่แปลว่าผู้ใช้อยู่ใน funnel เดิมอยู่แล้ว — การ refresh หน้าหรือกดลิงก์ซ้ำในวันเดียวกันจะได้ค่านี้เป็นปกติ และตัวเลขแคมเปญจะไม่ถูกปั่น
สิ่งที่บันทึกและผลข้างเคียง
- บันทึก event การเข้าชม โดย ชื่อที่แสดงและรูปโปรไฟล์ใช้ค่าจาก body ก่อน แล้ว fallback ไปใช้ค่าจาก token — ถ้าไม่มีทั้งคู่จะเก็บเป็น null การส่งค่าจากโปรไฟล์มาใน body จึงช่วยให้รายงานฝั่ง CMS อ่านง่ายขึ้น แต่ไม่บังคับ
- ค่า referral ที่ว่างจะถูกเก็บเป็น null ไม่ใช่สตริงว่าง
- ส่งงาน trigger เข้าคิวแบบ best-effort เพื่อให้ worker ประเมินกฎอัตโนมัติต่อ — ล้มเหลวก็ถูกกลืน ไม่กระทบผลลัพธ์ที่หน้าเว็บได้รับ
- merge custom attribute ให้ผู้ใช้ เมื่อแคมเปญตั้งค่า attribute ไว้ หรือมีการกำหนดคีย์สำหรับเก็บค่า referral พร้อมกับที่ผู้ใช้ส่ง referral มาจริง
- ข้อสำคัญ: การ merge นี้ทำเฉพาะเมื่อมีแถวผู้ใช้อยู่แล้วเท่านั้น ไม่มีการสร้างแถวใหม่ ดังนั้นผู้ใช้ที่ยังไม่เคยมีปฏิสัมพันธ์กับ OA มาก่อน (ยังไม่มีแถวในระบบ) จะไม่ได้รับ attribute จากแคมเปญ แม้จะส่ง referral มาถูกต้อง — เป็น edge case ที่มีผลจริงกับการทำ segmentation จากแคมเปญ
การชดเชย event สำหรับคนที่เป็นเพื่อนอยู่แล้ว
- backend เช็คว่าผู้ใช้เป็นเพื่อนกับ OA อยู่แล้วหรือไม่ แล้วส่งสถานะนี้กลับมาให้หน้าเว็บตัดสินใจ (พาไปหน้าเพิ่มเพื่อน หรือพาไปห้องแชท)
- ถ้าเป็นเพื่อนอยู่แล้ว backend จะบันทึก event การติดตามให้เองด้วย (ถ้ายังไม่มี) เหตุผลคือ LINE จะไม่ยิง webhook การติดตามสำหรับคนที่เป็นเพื่อนอยู่แล้ว ถ้าไม่ชดเชยตรงนี้ funnel ของแคมเปญจะค้างอยู่ที่ขั้น "เข้าชม" ตลอดไปและดูเหมือนแคมเปญไม่มีผลลัพธ์เลย
- event การติดตามและเลิกติดตามตัวจริงมาจาก service webhook แยกต่างหาก — endpoint นี้เขียนเฉพาะ event การเข้าชม และ event การติดตามในกรณีชดเชยนี้เท่านั้น
รายละเอียดของ endpoint ค้นแคมเปญ
GET /api/friend-track/by-hash/:hashค้น OA จาก hash โดยไม่ตรวจสถานะหรือการลบเลย (parity กับระบบเดิม) ซึ่ง หลวมกว่าทุก endpoint อื่นในระบบ — OA ที่ถูกปิดหรือลบไปแล้วก็ยัง resolve ได้- แต่ถ้าไม่มีแคมเปญที่ active อยู่จะได้ 404
No active campaign found for this LINE OAและถ้าไม่พบ OA จะได้ 404LINE OA not found - เมื่อ OA มีแคมเปญ active หลายตัว ระบบเลือกตัวล่าสุดเสมอ — เส้นทางที่ไม่ระบุ token จึงชี้ไปยังแคมเปญใหม่สุดโดยอัตโนมัติ ซึ่งเป็นสิ่งที่ควรรู้ตอนสร้างแคมเปญใหม่ทับของเดิม
GET /api/friend-track/:tokenคืน LIFF ID และ hash ของ OA มาด้วย หน้าเว็บจึงเริ่ม LIFF ได้จากข้อมูลชุดนี้ตรงๆ;descriptionที่ไม่มีค่าจะมาเป็นnullไม่ใช่ key ที่หายไป
Edge case และข้อควรระวัง
- endpoint บันทึกการเข้าชมมี rate limit เฉพาะ route คือ 10 ครั้งต่อ 60 วินาทีต่อ IP เข้มกว่า rate limit ระดับแอป — ผู้ใช้หลายคนที่ออกเน็ตผ่าน IP เดียวกัน (เช่น Wi-Fi ในงานอีเวนต์ที่แจก QR แคมเปญ) อาจชนเพดานนี้ได้จริง เป็นสถานการณ์ที่ควรคำนึงเมื่อวางแผนแคมเปญ on-ground
- ทุก field ใน body เป็น optional และ ข้อผิดพลาดจากการ parse body ถูกละเลยทั้งหมด เพราะตัวตนผู้ใช้มาจาก header เท่านั้น — body ที่รูปแบบผิดจะไม่ทำให้ request ล้ม แต่จะทำให้ชื่อ รูป และ referral หายไปเงียบๆ
- แคมเปญที่ถูกปิดใช้งานจะได้ 404
Campaign not foundแม้ token จะถูกต้อง — ลิงก์เก่าที่แจกไปแล้วจะหยุดทำงานทันทีที่แอดมินปิดแคมเปญ