บัตรสะสมแต้ม - หน้าบัตรลูกค้า
ภาพรวม
endpoint ที่ส่งข้อมูล "บัตรทั้งใบ" ให้หน้า LIFF render ได้ภายใน round trip เดียว ประกอบด้วยโครง โปรแกรม (โหมดแสตมป์หรือแต้ม, ธีม, unit label), บัตรใบที่กำลังใช้งาน, บันไดรางวัล (milestone), รางวัลที่ลูกค้าถืออยู่, ยอดคงเหลือ, ระดับชั้น (tier) และระยะที่เหลือถึงชั้นถัดไป พร้อมกับมี endpoint สำหรับดูประวัติบัตรย้อนหลังแยกอีกตัวหนึ่ง
โปรแกรมสะสมแต้มมี 2 โหมดที่พฤติกรรมต่างกันมาก ได้แก่ stamp (บัตรตอกแสตมป์ เมื่อครบใบจะได้รางวัล อัตโนมัติ) และ point (แต้มทำหน้าที่เป็นสกุลเงิน ลูกค้าเลือกแลกรางวัลเอง)
Business Flow
โหลดหน้าบัตร — GET /api/loyalty/:hash
resolve(c)ค้น OA จากพารามิเตอร์:hash(ไม่พบตอบ 404"loyalty card not found") แล้ว verify LIFF token กับ channel ของ OA นั้นผ่าน ladder ชุดเดียวกับฝั่ง bulletin- โหลดโปรแกรมที่ live ของ OA โดยกรณีที่ไม่มีโปรแกรมไม่ถือเป็น error ระบบจะคืน response ที่มีเพียง
ชื่อและรูปของ OA (
milestones: [],rewards: []) เพื่อให้ LIFF แสดงหน้าจอ "ยังไม่เปิดให้บริการ" EnsureAccountสร้างแถวloyalty.accountให้ LINE user รายนี้ถ้ายังไม่มี- โหลดบัตรทั้งหมดของ account แล้วเลือกใบที่มีสถานะ
activeมาเป็นcard - โหลด milestone ของ program id และ version นั้นโดยเฉพาะ เนื่องจากโปรแกรมมีระบบ versioning บันไดรางวัลของเวอร์ชันเก่าจึงไม่ถูกนำมาแสดง
- โหลดรางวัลของ account แล้ว render ผ่าน
NewRewardViews(rewards, storage, now)ซึ่งตัดสินสถานะ หมดอายุจากเวลาปัจจุบัน - คำนวณ
BalanceจากAvailableUnits(lots, now)โดยนับเฉพาะ ledger lot ที่ยังไม่หมดอายุ - ส่วนของ tier (เฉพาะโหมด point ที่เปิด
tier_enabled) คำนวณสดทุกครั้งที่โหลดบัตร ไม่ cache ไว้ บน account เพราะบันไดชั้นถูก re-price ใน CMS ได้ตลอดเวลา และชื่อ tier ที่ cache ไว้จะยังแสดงชั้นที่ ถูกเปลี่ยนชื่อหรือลบไปแล้ว- ชั้นปัจจุบันถูกอ่านด้วย
tier_idโดยตรง เพราะ tier ไม่ได้ version ตามโปรแกรม (ผูกกับ OA) ถ้าไป match กับ rung ของ program version ปัจจุบัน ลูกค้าที่ได้ชั้นมาจากเวอร์ชันก่อนหน้าจะไม่มีชั้นเลย - บันไดทั้งหมดคือ tier ที่ active ของ OA (ข้าม program version) ด้วยเหตุผลเดียวกัน
NextTierคือ rung ที่ active และมี rank สูงกว่าชั้นปัจจุบันโดยใกล้ที่สุดSpendToNextคำนวณจากnextSpendTarget(next)ลบด้วยยอด spend ในหน้าต่างเวลา (ไม่ต่ำกว่า 0) โดยหน้าต่างเวลาเริ่มต้นที่ 3 เดือน ทั้งนี้ tier ที่ไม่มีกฎ metricspendจะได้ target เป็น 0 ซึ่งหมายถึงไม่มีตัวเลขบาทให้นับถอยหลัง ระบบจะไม่คิดตัวเลขขึ้นมาเอง
- ชั้นปัจจุบันถูกอ่านด้วย
- รูปทั้งหมด (cover, logo, รูปรางวัล, รูปพื้นหลังของ tier) ถูก resolve เป็น public URL ผ่าน
publicURLOfหาก path ว่างหรือไม่มี storage ระบบจะไม่ส่งรูปออกไป แทนที่จะส่ง URL ที่ชี้ไปยัง bucket root
ประวัติบัตร — GET /api/loyalty/:hash/cards
เรียก EnsureAccount แล้วคืน {cards: [...]} ทั้งหมด เป็นหน้าประวัติบัตรที่ LINE เองไม่มีให้
สิ่งที่ไม่อยู่ใน response
โค้ดคูปองของรางวัลไม่ถูกส่งมากับหน้าบัตรโดยเจตนา ดูรายละเอียดที่ loyalty-reward-redeem เพราะหากใส่มาใน card response ภาพหน้าจอที่ ถูกแคปไว้จะใช้แลกได้ตลอดกาล
ไฟล์และฟังก์ชันหลัก
| Route | Handler |
|---|---|
GET /api/loyalty/:hash | internal/loyalty/handler.go → (*Handler).GetCard |
GET /api/loyalty/:hash/cards | (*Handler).ListCards |
internal/loyalty/register.go—Register(r, deps)พร้อมappguard.AppEnabledGuard(db,"loyalty")internal/loyalty/handler.go—(*Handler).resolveและ typecallerinternal/loyalty/auth.go— ladder สำหรับ verify token (สำเนาของฝั่ง bulletin)internal/loyalty/view.go—CardResponse,NewProgramView,NewCardView,NewCardViews,NewMilestoneViews,NewRewardViews,NewTierView,NewBranchViews,publicURLOfinternal/loyalty/repository.go—FindOaByHash,FindLiveProgram,EnsureAccount,ListCards,ListMilestones,ListRewards,Lots,GetTierByID,ListTiersByLineOa,SpendInWindowinternal/loyalty/entity.go—Program,Account,CardInstance,Milestone,Reward,AvailableUnits,PlanBurn,CardExpiry,CooldownBlocks,UnitsForSpendและค่าคงที่ModeStamp/ModePoint,CardActive/CardComplete/CardExpired,RewardUnclaimed/RewardClaimed/RewardExpired,MaxCardSize=20,MaxEarnUnits=10000,TokenTTL=60s
จุดเชื่อมต่อกับ Service อื่น
- ตารางฐานข้อมูล
loyalty.program,loyalty.account,loyalty.card_instance,loyalty.milestone,loyalty.reward,loyalty.transaction(ledger lot),loyalty.tier,line_oa internal/storagexสำหรับสร้าง public URL ของรูปทั้งหมด- app-enabled-guard (appID
loyalty) และ liff-authentication - loyalty-tier เป็นแหล่งกฎการคำนวณชั้น ส่วน loyalty-earn และ loyalty-reward-redeem เป็นเส้นทางที่เปลี่ยนแปลงข้อมูลบนหน้าบัตร
- ตรงกับ client-web feature:
loyalty-card