票券管理
點數系統
每位會員的點數可以透過:
- 消費自動累積(依「點數參數」的「金額換點數比例」)
- 管理員手動加點
- 活動贈點
- 推薦獎勵
「會員 → 點數紀錄」會顯示所有點數異動。
會員端「集點記錄」頁面
會員從 LIFF 進「會員中心 → 集點記錄」,會看到每一筆點數異動。每筆記錄包含:
- 左側圓形圖示:依異動類型顯示對應圖示
- 中文類別標籤:例如「客服贈點」「消費回饋」「兌換票券」「推薦獎勵」
- 入帳 / 使用 badge:綠色 badge = 點數增加,橘色 badge = 點數扣除
- 異動說明:依類別自動帶出對應描述(例如「POS 消費:訂單 #...」「兌換票券:滿千折百」)
- 時間:年/月/日 時:分
- 點數變化:右側 +N 點 / -N 點,綠色加分、紅色扣分
集點記錄頁面截圖製作中
目前支援的 15 種異動類別
| 類別代碼 | 中文標籤 | 觸發時機 |
|---|---|---|
manual_add | 客服贈點 | 管理員後台手動加點 |
manual_deduct | 客服扣點 | 管理員後台手動扣點 |
purchase | 消費回饋 | POS / 手動消費登記 |
refund | 退款扣回 | 訂單退款回收點數。扣回的點數以會員實際扣得到的餘額為準,不會扣成負數——會員若已把當初賺到的點數用掉一部分,退款只會扣到剩下的餘額,扣不足的差額不會倒扣;扣不足時這筆記錄的說明會多一句「(原 N 點,餘額不足實扣 M)」方便對帳 |
exchange | 兌換票券 | 點數商城兌換 |
invoice_reward | 發票回饋 | 上傳發票通過審核 |
invoice_reward_revoke | 回饋撤回 | 鑑賞期二驗失敗撤回 |
lottery_reward | 抽獎中獎 | 抽獎活動中獎 |
referral | 推薦獎勵 | 推薦新會員達標 |
dispatch_reward | 派案酬金 | 派案模組完成案件 |
beauty_service | 預約完成 | 美業預約完成 |
micro_page | 活動加點 | 微網頁觸發加點 |
marketing | 行銷活動 | 行銷自動化贈點 |
transfer_in | 收到紅包 | 領取點數紅包 / 過期退回 |
transfer_out | 送出紅包 | 建立點數紅包扣點 |
提示:如果未來新增類別代碼,前端會 fallback 成「點數異動」不會炸。
票券管理
「內容管理 → 票券管理」可建立票券,建立時可設定:
- 票券名稱
- 類型:折扣券 / 現金券 / 禮品券(共 3 種)
- 金額或折數(依類型不同意義不同)
- 最低消費金額(選填)
- 庫存(留空=不限)
- 生效日期 / 到期日期
- 允許會員轉贈這張票券(預設關閉,要開放才逐張開啟,見下方「票券轉贈設定」)
- 優惠券圖片(2026-06-17 改為拖曳上傳;2026-06-25 起強制必填):可直接拖檔或點選圖片上傳到 R2,建議 1200×600(2:1 寬幅)。會員收到票券推播時 Flex 卡 hero 區會用這張圖。新建 / 編輯票券時必須上傳 banner 或商品圖至少一張,後端 POST / PUT 會 reject 兩者皆空的請求
票券列表頁可依「類型」「狀態(啟用中 / 已停用 / 已過期)」篩選。
狀態欄一律顯示中文:無論是票券範本(啟用中 / 已暫停 / 未啟用 / 草稿 / 已發佈 / 已撤銷 / 已取消)、會員票券(未使用 / 已使用 / 已核銷 / 已過期 / 轉贈中 / 贈送相關)或 records 事件(已發放 / 處理中),列表 / 詳情 / 記錄頁全走同一份 mapping(lib/voucher-status.ts)—— 後端 enum 內部值 (active/unused/used等) 保持不變,只有顯示層自動翻中文。老闆 / 客戶不會再在截圖看到active/unused生英文。
進入單張票券詳情頁,可執行:發放給特定會員、核銷、編輯。
自動發券時推 LINE Flex 通知(2026-06-17 新加)
行銷劇本 / 抽獎 / 入會禮等發券動作(issue_voucher)會員都會自動收到一張 LINE Flex 圖卡通知:
- 含商品圖(如上有設)、票券名、類型、面額、有效期、收券人姓名
- 底部按鈕「查看我的票券」導向會員 LIFF
/vouchers - 樣式可在「內容管理 → 系統圖卡 → 票券發放通知卡」自訂(變數含
voucher_name/type_label/value_label/image_url/expires_label等) - 沒綁 LINE 的會員照樣發券(DB 寫入 member_vouchers),只是不會收推播
為什麼客戶說沒收到票券?
最常見原因:voucher 沒上傳 banner 圖 → 自動走純文字版 fallback bubble,視覺平淡客戶誤判滑過以為沒收到。
2026-06-25 起新建 / 編輯票券強制圖片必填,舊資料若未補圖會在列表標紅;新建 / 編輯時若 banner 與商品圖都空白,後端會回 400 拒絕儲存。
修法(系統已內建三段 fallback,但前兩段強烈建議都做):
- 每張 voucher 上傳 2:1 橫式 banner(1200×600)— 首選,可做活動專屬視覺(現為必填)
- 在「系統設定 → 票券預設圖」設一張統一品牌 banner — 任何未上傳 banner 的 voucher 自動套用(救援用)
- 兩者皆無 → 走純文字 bubble(safety net,仍可送達但體驗弱;新資料已不會發生此情況)
Admin 警示:
- 票券列表「票券名稱」欄位旁若無 banner / 商品圖 → 顯示 缺圖,無法發送 紅色 chip
- 列表頁頂部會出現整列紅色提示橫條:「本頁有 N 張票券缺圖無法發送,請優先補圖」
- 每張缺圖 row 右側都有「補圖」紅色按鈕,點擊直接跳該 voucher 編輯頁並自動開啟編輯 modal
票券圖片必填規則(2026-06-25 上線)
規則:banner_image_url 或 image_url 至少一個欄位有值。
| 欄位 | 用途 | 建議規格 |
|---|---|---|
banner_image_url | LINE Flex 2:1 主圖(hero) | 1200×600 橫式 |
image_url | 商品圖(banner 沒設時 fallback) | 1:1 或方形皆可 |
驗證點:
- 後端
POST /api/vouchers:兩者皆空回 400「請至少上傳一張票券圖片(banner 或商品圖),建議 2:1 橫式 1200×600」 - 後端
PUT /api/vouchers/:id:若本次 PUT 涉及這兩個欄位,結算後仍兩者皆空回 400「請至少保留一張票券圖片(banner 或商品圖)」(不涉及這兩個欄位的部分更新不會擋) - 前端新建 / 編輯 form:submit 前 inline error「請至少上傳一張票券圖片」
- 既有舊資料:GET / list 不擋,但列表紅 chip + 紅橫條提示,「補圖」按鈕一鍵跳編輯
Flex 發送 fallback 順序(voucher-flex-push.ts):banner_image_url → image_url → 租戶預設圖 → 純文字 bubble
票券預設圖在哪設?
路徑:後台左側 系統設定 → 頁面內第 3 張卡片「票券預設圖(2:1 1200×600)」
規格:
- 比例 2:1 橫式,建議 1200×600
- 檔案 3MB 以內
- PNG / JPG 皆可,以品牌橫式 KV 圖為佳
何時被使用:當某張 voucher 的 banner_image_url 與 image_url 都為空 → 推送 LINE Flex 時自動以本預設圖填入 hero。
寫入位置:settings 表 key=voucher_default_banner_url(jsonb),api 端 voucher-flex-push.ts 三段 fallback 第二層讀此 key。
票券轉贈設定(2026-08-16 新增)
可逐張票券決定「這張券能不能被會員轉贈給 LINE 好友」。
設定位置:
- 新建票券頁 → 「允許會員轉贈這張票券」開關
- 單張票券詳情頁 → 「轉贈設定」卡片(切換後立即儲存,不需再按儲存)
預設值:關閉(不可轉贈)。新建票券一律預設不開放轉贈,要讓客人能把券送給朋友,必須逐張自己開啟。
為什麼預設關閉:票券等於有金額的資產,開放轉贈後就可能流到非本人手上。少開放只要開啟一下即可補上;多開放則券已經轉手出去,事後追不回來。
關閉時:會員票券夾(LIFF「我的票券」)該張票券不會出現「轉贈給朋友」按鈕。已經送出、對方尚未領取的禮物不受影響,仍照原本 24 小時規則走。
只有這一層開關
轉贈只由每張票券自己的設定決定,沒有全店總開關,也不需要先到其他頁面啟用。在票券新建頁或票券詳情頁開啟後立即生效。
2026-08-16 之前的說明曾提到「參數設定 → 商家設定 → 啟用轉贈功能」這顆全店總開關。該開關實際上從未生效(開或關都不影響會員能否轉贈),已一併移除,請以本節為準。
轉贈時朋友收到的卡片(2026-08-16 改版)
會員按「轉贈給朋友」時,LINE 會跳選好友視窗、送出一張「票券禮物分享卡」,對方點「立即領取」開 LIFF 領取頁。
- 圖片改成 2:1 橫式(原本是 1:1 正方形),與「票券發放通知卡」一致,同一張票券圖不用再準備兩種比例
- 圖片取用順序:banner 圖 → 商品圖,與發券通知卡同一套規則(在此之前這張卡根本沒去撈 banner 圖,所以只會顯示商品圖)
- 版型可在
內容管理 → 系統圖卡 → 票券禮物分享卡自訂 - 未領取的禮物24 小時後自動退回原持有者
票券進階設定(2026-06-30 新增 4 欄位)
新建 / 編輯票券 modal 內新增以下欄位:
1. 禮品券(type = gift)不需設金額
票券類型新增 「禮品券」 選項。選了禮品券:
- 金額欄位 disabled(不會傳
value) - 適用情境:免費贈品(如「免費咖啡」「免費小菜」「贈品兌換券」),不對應金額
- LIFF 票券詳情頁顯示「贈品」徽章,沒有面額或折數
- POS 核銷時不會影響訂單金額(純記錄已兌換)
資料層:vouchers.value已 DROP NOT NULL(boot-migration F45 backfill),type='gift'時 value 可為 null;其他類型仍要求value > 0。
2. 使用說明(usage_terms,最多 2000 字)
textarea 欄位,填了之後:
- 顯示在會員 LIFF 票券詳情頁的「使用說明」區塊
- 範例文案:「本券限本店使用 / 一桌限用一張 / 不可與其他優惠合併使用 / 不可折抵現金 / 過期作廢」
- 後端限制 2000 字以內,超過會 reject
3. 領券後 N 天可使用(valid_after_days,0–365)
- 預設 0:發券當下立即可用
- 設 N:發券當下計算
valid_from = NOW() + N days,會員 LIFF 在此日期前看到票券但顯示「N 天後可使用」無法核銷 - 適用情境:
- 新會員禮券要等 7 天驗證期後才可用 - 預售活動:發券當下不可用,活動開跑日才開放
- 由 PostgreSQL BEFORE INSERT trigger 自動算 valid_from,caller 不用算
4. 領券後 N 天失效(valid_for_days,1–3650)
- 預設 null:吃 voucher 主檔的
expires_at為到期日 - 設 N:發券當下計算
expires_at = valid_from + N days,覆寫主檔 expires_at - 適用情境:
- 限時優惠:「領券後 7 天內用完」 - 滾動式發放:不想設固定到期日,每位會員從領到當下算 N 天
- 後端限制 1-3650(10 年內)
注意:valid_after_days + valid_for_days 兩欄位 BEFORE INSERT trigger 在 member_vouchers 表上自動計算 valid_from / expires_at,發券的 7+ 個 caller(手動發、行銷劇本、抽獎、推薦獎勵、購買、兌換、轉贈)全部不需改 code 自動受惠。5. 票券狀態 inline toggle(票券管理列表)
票券列表 狀態 欄位可直接點 cell 切換啟用 / 暫停,無需開編輯 modal:
| 狀態 | DataTable 顯示 | 行為 |
|---|---|---|
active | 啟用中 | 可被發放 / 兌換 / 行銷劇本派送 |
paused | 已暫停 | 列表仍可見 / 已發出的票券不受影響 / 不再被新發送 |
點擊 cell → PUT /api/vouchers/:id/toggle-status → toast「票券已暫停 / 票券已恢復啟用」
提示:暫停不等於刪除:要永久移除請走 刪除 按鈕(會 cascade 刪除已發出的 member_vouchers,僅 tenant_admin 可執行)。6. 每日領取限制 + 額滿推送文字(2026-07-02 新增)
新建 / 編輯票券 modal 內新增「每日領取限制」藍色卡片,兩個上限可個別設或同時設:
| 欄位 | 意義 | 範圍 |
|---|---|---|
| 每人每日限領張數 | 同一會員每天最多可領這張票券幾張 | 0-999,0 或不填 = 不限 |
| 全店每日發放上限 | 這張票券每天全店最多發放幾張(含系統贈送) | 0-9999,0 或不填 = 不限 |
3 條設計原則(拍板規則):
- 系統贈送也算配額 — 新會員禮 / MGM 推薦獎勵 / 行銷劇本派券 / admin 手動發放全部算入當日配額。不會因為「是系統送的」就跳過上限。
- admin 撤銷後不退還配額 — 撤銷票券不會把當天的名額退回去(防 exploit:撤銷再重領)。
- 時間邊界用租戶時區 00:00 — 從「系統設定 → 時區」讀取(預設
Asia/Taipei),00:00 重置當日計數。不用 UTC,也不吃 POS「營業日」設定。
觸發時機:
- 全店上限先檢查(滿了個人一定也拿不到)
- 檢查順序:全店 → 個人;任一 hit 就拒絕發券
額滿時的行為分流:
| 情境 | 系統動作 |
|---|---|
| 會員 LIFF 自主兌換 | 按鈕顯示 disabled + 進度小字「今日 X/Y」;詳情 modal 顯示提示 |
| 會員點兌換按鈕(僥倖點到) | 回 429 錯誤 + toast「本券今日領取名額已滿,請明日再試」 |
| Admin 手動發放 | 回 429 給 admin,會員端不推任何訊息(避免困擾) |
| 系統贈送(MGM / 自動行銷 / 週期任務) | 該張 voucher 不發,改推「額滿推送文字」給會員(見下) |
額滿推送文字(limit_reached_message,最多 500 字)
「每日領取限制」卡片下方新增 textarea,供 admin 自訂系統贈送 hit 上限時推給會員的文字:
- 不填 → 走系統 default:「本券今日領取已達上限,明日再試」
- 有填 → 推 admin 設的文字。範例:
- 「今日領取已達上限,明天早上 8:00 重置,歡迎明日再來」 - 「本活動今日名額已滿,請追蹤下週三新一波開放時間」 - 「感謝支持!本券今日份已發完,明日 00:00 重新開放」
提示:只在系統贈送情境下推(MGM 推薦、行銷自動化派券、週期任務贈送):這些流程原本會推一張 voucher Flex 圖卡,hit 上限後改推此文字讓會員知道今日名額已滿。會員 LIFF 自主兌換 hit 上限時不會推此文字(會員在 UI 上已看到 disabled 提示)。
未加好友的會員:文字進 pending_push 佇列,未來加好友時 webhook follow 事件補推(同 voucher Flex fallback 邏輯)。
會員 LIFF 點數商城顯示(Phase 2)
會員從 LIFF 進「會員中心 → 點數商城」,票券卡片會依當日配額動態顯示:
| 狀態 | 卡片按鈕 | 卡片下方 |
|---|---|---|
| 可領 | 正常「兌換」按鈕 | 無 |
| 個人已達上限 | 灰色 disabled 按鈕 | 「今日 X/Y」(粉紅小字,顯示已領 / 上限) |
| 全店已達上限 | 灰色 disabled 按鈕 | 「今日 X/Y」 |
點卡片開詳情 modal:footer 顯示醒目提示 + admin 設的「額滿推送文字」(若有),並顯示「今日已領取 N / M 張」。會員能一眼看懂「不是壞掉,是名額滿了」。
資料層:vouchers加 3 個新欄位daily_claim_limit_per_member/daily_claim_limit_total/limit_reached_message(boot-migration F48 / F49 自動 backfill)。LIFF list endpoint 每張 voucher 預查evaluateClaimEligibility回can_claim/limit_used_today/limit_total/limit_reason給前端渲染。
核銷後自動觸發行銷劇本(voucher_redeemed trigger)
票券核銷成功後系統自動 fireTrigger('voucher_redeemed'),可在 會員管理 → 自動行銷劇本 建一條規則攔截:
- trigger_type:
voucher_redeemed - config 可選 voucher_id:填了則只該張票券核銷會觸發;不填 = 任何票券核銷都觸發
- action:可串點數、推 LINE、發另一張票券、改標籤等任何 ma_rules 支援的動作
- dedup:每張 member_voucher 終生 fire 一次(即使重複核銷不會多觸發)
不是 voucher 主檔欄位 — 走 ma_rules 規則設定。完整 trigger 規格見 自動行銷規則。
點數商城(兌換票券)
點數商城(會員可用點數兌換的票券)由 points_cost > 0 的票券自動上架到 LIFF「點數商城」。
設定入口:內容管理 → 兌換票券(與「票券管理」分離,本頁聚焦純發放票券;兌換票券完整設定見 兌換票券 說明書)。
2026-06-26 起列表分流:「票券管理」列表只顯示redeemable=false的純發放票券(後台手動贈送 / 行銷劇本派發 / 購買);積分兌換券(points_cost > 0)統一到「兌換票券」頁管理,兩處不會互相混入。