Web 忠诚度与礼品卡模块 — CUJ 关键用户旅程

Loyalty Program · Gift Cards · Membership Tiers · Points | 更新时间: 2026-02-08

TOUCHES 追溯

📄 Pages (11): admin/loyalty/config/page.tsx (忠诚度配置), admin/loyalty/tiers/page.tsx (等级配置), admin/loyalty/points/page.tsx (积分管理), admin/loyalty/gift-cards/page.tsx (礼品卡管理), admin/gift-cards/page.tsx (礼品卡列表), admin/gift-cards/[id]/page.tsx (礼品卡详情), gift-cards/page.tsx (我的礼品卡), gift-cards/purchase/page.tsx (购买礼品卡), gift-cards/claim/[token]/page.tsx (兑换礼品卡), account/gift-cards/page.tsx (账户礼品卡), account/gift-cards/purchase/page.tsx (账户内购买)
🧩 Components (11): GiftCardBalance.tsx, GiftCardPurchaseForm.tsx, GiftCardRedeemInput.tsx, GiftCardDetailModal.tsx, GiftCardInput.tsx (支付集成), GiftCardPaymentOption.tsx, LoyaltyPaymentOption.tsx, PointsAdjustModal.tsx, PointsBalance.tsx, TierBadge.tsx, TierProgress.tsx
🪝 Hooks: useLoyaltyApi.ts (tiers + points + gift cards mutations), useGiftCard.ts (claim + verify), useLoyalty.ts (points utilities)
⚙️ Backend: api/gift-cards.js (purchase), api/admin/gift-cards.js (admin listing), api/loyalty.js (points + membership + gift card ops), api/reports/loyalty.js (5 report endpoints), services/loyalty/loyaltyService.js (Facade), services/loyalty/giftCardService.js, services/giftCard/giftCardService.js (claim tokens)

架构概览

忠诚度系统架构

erDiagram
    guests ||--o| customer_memberships : "has membership"
    customer_memberships }o--|| membership_tiers : "at tier"
    guests ||--o| points_accounts : "has points"
    points_accounts ||--o{ points_transactions : "earns/redeems"
    guests ||--o{ gift_cards : "owns"
    gift_cards ||--o{ gift_card_transactions : "transactions"
    gift_cards ||--o{ gift_card_claims : "claim attempts"

    membership_tiers {
        uuid id PK
        string name "Bronze/Silver/Gold/Platinum"
        string code "tier code"
        string color "Hex color"
        decimal upgrade_threshold "Spend threshold"
        decimal discount_percent "Member discount"
        decimal points_multiplier "Points earn multiplier"
        boolean priority_booking "Priority flag"
        int birthday_bonus_points "Birthday bonus"
        boolean is_default "Default tier for new members"
    }

    gift_cards {
        uuid id PK
        string card_number "6017 prefix + Luhn"
        enum card_type "electronic | physical"
        decimal initial_amount "$10-500"
        decimal balance "Current balance"
        enum status "pending | active | used | expired | disabled"
        string claim_token "JWT 30-day"
        enum delivery_method "email | sms | both"
    }

    points_accounts {
        string customer_id FK
        int total_points "Lifetime"
        int available_points "Spendable"
        int frozen_points "Pending"
        int lifetime_earned "Total earned"
        int lifetime_redeemed "Total redeemed"
    }

VIP 状态 vs 会员等级:两套系统

⚠️ 重要发现: 系统中存在两套独立的等级系统
1. 会员等级 (Membership Tiers)membership_tiers + customer_memberships 表,基于消费金额自动升降级,带折扣/积分倍率/专属权益。这是 017-loyalty-program 实现的完整系统。
2. VIP 状态guests.vip_status 字段 (none/bronze/silver/gold/platinum) + vip_points。来自 012-profile-management,是早期遗留系统。
当前状态: 两套系统独立运行,未同步。Membership Tiers 是正式的忠诚度系统,VIP 状态可能会逐步淘汰或同步。

礼品卡卡号规则

属性
前缀6017 (QQ Nails 标识)
总长度16 位 (4 前缀 + 11 随机 + 1 校验)
校验算法Luhn (信用卡式校验)
生成方式PostgreSQL 函数,数据库级唯一性约束

权限矩阵

操作所需权限
查看礼品卡列表giftcards:view
管理礼品卡giftcards:manage
管理忠诚度设置loyalty:manage
查看忠诚度报告reports:view_loyalty

目录

CUJ 总览与优先级矩阵

CUJ优先级描述触发点业务价值E2E 状态
M1 P1 忠诚度系统配置 Admin → Loyalty → Config 积分规则、礼品卡规则、折扣策略统一配置 缺失
M2 P1 会员等级管理 Admin → Loyalty → Tiers 分层会员体系,差异化权益 缺失
M3 P1 积分管理与手动调整 Admin → Loyalty → Points 积分运营灵活性 缺失
M4 P0 礼品卡购买(客户端) /gift-cards/purchase 收入增长 — 预付费模式 部分覆盖
M5 P0 礼品卡兑换与使用 /gift-cards/claim/[token] 客户体验 — 收礼→激活→支付 部分覆盖
M6 P1 礼品卡后台管理 Admin → Gift Cards 运营管理 — 查询/充值/取消 缺失
M7 P2 忠诚度报告分析 Reports → Loyalty 留存/流失/积分负债分析 缺失

CUJ-M1: 忠诚度系统配置

P1 全局配置 — 积分规则、礼品卡规则、折扣策略

TOUCHES: admin/loyalty/config/page.tsx, loyalty:manage 权限

配置项

分组配置项默认值
积分系统points_enabledtrue
points_earn_rate (每$1赚几分)1
points_redeem_rate (兑换$1需几分)100
points_min_redeem (最低兑换)100
points_expire_days (0=永不过期)365
礼品卡gift_cards_enabledtrue
gift_card_min_amount$10
gift_card_max_amount$500
gift_card_expire_months (0=永不)0
会员tier_period_months (评估周期)12
downgrade_protection_days (降级保护)30
discount_stacking"best" (best/stack/member_first)
跨店cross_store_pointstrue
cross_store_gift_cardstrue

BDD 场景

场景 M1.1: 配置积分规则

Given 用户已登录并拥有对应功能所需权限与范围(scope),导航到 /admin/loyalty/config
When 修改积分赚取率为 2 (每消费$1赚2分)
  And 设置积分过期天数为 180
  And 保存
Then 所有后续消费按新规则赚取积分
  And 已有积分的过期倒计时不受追溯影响

场景 M1.2: 配置折扣叠加策略

Given 客户同时有会员折扣 10% 和促销折扣 15%
When discount_stacking = "best"
Then 仅应用最优折扣 15%
When discount_stacking = "stack"
Then 两个折扣叠加: 10% + 15% = 25%
When discount_stacking = "member_first"
Then 先应用会员折扣,再在折后价上应用促销

CUJ-M2: 会员等级管理

P1 分层会员体系 — 等级创建、阈值、权益配置

TOUCHES: admin/loyalty/tiers/page.tsx, TierBadge.tsx, TierProgress.tsx, membership_tiers 表, customer_memberships

等级生命周期

flowchart LR
    A["新客户"] --> B["默认等级 (is_default)"]
    B --> C{"评估周期内消费 >= threshold?"}
    C -->|"是"| D["自动升级"]
    D --> E["享受折扣 + 积分倍率 + 专属权益"]
    C -->|"否"| F{"降级保护期内?"}
    F -->|"是"| G["保持当前等级"]
    F -->|"否"| H["自动降级到匹配等级"]

BDD 场景

场景 M2.1: 创建会员等级

Given 用户已登录并拥有对应功能所需权限与范围(scope),在 /admin/loyalty/tiers
When 点击"新建等级"
  And 填写: 名称 "Gold"、编码 "gold"、颜色 #FFD700
  And 消费阈值 $1500、折扣 10%、积分倍率 1.5x
  And 权益: priority_booking=true, birthday_bonus=200
Then 等级创建,显示在等级卡片列表中
  And 颜色编码的卡片显示阈值、折扣、倍率摘要

场景 M2.2: 停用等级(自动重分配)

Given "Silver" 等级有 20 名会员
When 用户停用 Silver 等级
Then 20 名会员自动重分配到下一个合适的活跃等级
  And 等级标记为 is_active=false(软删除)

CUJ-M3: 积分管理与手动调整

P1 积分运营 — 查看余额、手动增减、过期管理

TOUCHES: admin/loyalty/points/page.tsx, PointsAdjustModal.tsx, PointsBalance.tsx, GET /api/loyalty/points/account/:customer_id

BDD 场景

场景 M3.1: 手动调整积分

Given 用户已登录并拥有对应功能所需权限与范围(scope),搜索到客户 "Jane Doe",当前 480 积分
When 点击"调整积分" → PointsAdjustModal 打开
  And 选择 +100、输入原因 "投诉补偿"
  And 确认
Then 积分变为 580
  And 创建 points_transaction (type='adjust', reason='投诉补偿')
  And 如果 580 >= Silver 阈值(500),触发等级评估

场景 M3.2: 查看即将过期积分

Given 积分过期策略: 365 天
When 查看客户积分详情
Then 显示: 可用积分、冻结积分、累计赚取、累计使用
  And 显示即将过期的积分 (30 天内),鼓励使用

CUJ-M4: 礼品卡购买(客户端)

P0 收入增长 — 客户在线购买电子礼品卡

TOUCHES: gift-cards/purchase/page.tsx, account/gift-cards/purchase/page.tsx, GiftCardPurchaseForm.tsx, POST /api/gift-cards/purchase, services/loyalty/giftCardService.js

购买流程

flowchart TD
    A["选择金额"] --> B["选择配送方式"]
    B --> C["填写收件人信息"]
    C --> D["个性化 (From/To/Message)"]
    D --> E["确认购买"]
    E --> F["生成卡号 6017-xxxx-xxxx-x"]
    F --> G{"配送方式"}
    G -->|"Email"| H["发送邮件 + 兑换链接"]
    G -->|"SMS"| I["发送短信 + 兑换链接"]
    G -->|"Both"| J["邮件 + 短信"]
    H --> K["成功页面 (遮蔽卡号)"]
    I --> K
    J --> K

    style E fill:#4caf50,stroke:#388e3c,color:#fff

BDD 场景

场景 M4.1: 购买电子礼品卡

Given 客户在 /gift-cards/purchase
When 选择金额 $50(预设按钮: $25/$50/$75/$100/$150/$200)
  And 配送方式: Email
  And 收件人邮箱: friend@example.com
  And 个性化: From "Jane", To "Sarah", Message "Happy Birthday!"
  And 确认购买
Then 生成 16 位卡号 (6017 前缀 + Luhn 校验)
  And 创建 gift_card 记录 (status='pending')
  And 生成 JWT claim_token (30 天有效)
  And 发送邮件包含兑换链接
  And 成功页面显示遮蔽卡号

场景 M4.2: 自定义金额购买

Given 预设金额不满足需求
When 输入自定义金额 $75.50
Then 验证金额范围: $10 – $500
  And 正则验证: /^\d*\.?\d*$/

场景 M4.3: 批量购买

Given 企业客户需要批量礼品卡
When 设置数量为 10 张 × $50
Then 验证数量: 1 – 100
  And 生成 10 张独立卡号
  And 列表显示所有卡号

CUJ-M5: 礼品卡兑换与使用

P0 客户体验 — 收到礼品卡 → 激活 → 在结账时使用

TOUCHES: gift-cards/claim/[token]/page.tsx, gift-cards/page.tsx, account/gift-cards/page.tsx, GiftCardPaymentOption.tsx, GiftCardInput.tsx, services/giftCard/giftCardService.js

兑换状态机

flowchart TD
    A["收到兑换链接"] --> B["打开 /gift-cards/claim/token"]
    B --> V{"验证 JWT Token"}
    V -->|"无效"| E1["错误: 链接无效"]
    V -->|"过期"| E2["错误: 链接已过期"]
    V -->|"已兑换"| E3["提示: 已兑换"]
    V -->|"有效"| C["显示卡面: 金额、发送者、消息"]
    C --> D{"需要登录?"}
    D -->|"已登录"| F["点击 Claim"]
    D -->|"未登录"| G["提示登录/注册"]
    G --> F
    F --> H["卡绑定到账户, status → active"]
    H --> I["成功: 卡已添加"]

    style E1 fill:#c62828,stroke:#b71c1c,color:#fff
    style E2 fill:#c62828,stroke:#b71c1c,color:#fff
    style H fill:#4caf50,stroke:#388e3c,color:#fff

BDD 场景

场景 M5.1: 兑换礼品卡

Given Sarah 收到邮件,包含兑换链接
When 点击链接 → /gift-cards/claim/{token}
Then 验证 JWT claim_token
  And 显示: 卡面金额 $50、发送者 "Jane"、消息 "Happy Birthday!"
When 点击 "Claim Gift Card"
Then 卡绑定到 Sarah 的账户 (owner_id 设置)
  And status 从 pending → active
  And 创建 gift_card_claims 记录 (status='success')

场景 M5.2: 在结账时使用礼品卡

Given Sarah 有一张 $50 余额的活跃礼品卡
  And 预约结账总额 $35
When 在结账页选择 "Gift Card" 支付方式
  And 输入卡号或选择已绑定的卡
Then GiftCardPaymentOption 显示可用余额 $50
  And 扣除 $35,余额变为 $15
  And 创建 gift_card_transaction (type='redeem', amount=35)

场景 M5.3: 查看我的礼品卡

Given Sarah 有 3 张礼品卡
When 导航到 /gift-cards/account/gift-cards
Then 显示卡片列表: 卡号(遮蔽)、余额、初始金额、状态、过期日
  And 余额进度条显示剩余百分比
  And 支持状态筛选 (All/Active/Pending/Used/Expired)
  And 点击可显示/复制完整卡号

CUJ-M6: 礼品卡后台管理

P1 运营管理 — 搜索、查看详情、充值、取消

TOUCHES: admin/gift-cards/page.tsx, admin/gift-cards/[id]/page.tsx, admin/loyalty/gift-cards/page.tsx, GiftCardDetailModal.tsx, GET /api/gift-cards (admin)

BDD 场景

场景 M6.1: 搜索和管理礼品卡

Given 用户已登录并拥有 giftcards:view 权限,且具备对应范围(scope)
When/admin/gift-cards 搜索卡号/邮箱/手机号
Then 显示匹配结果: 卡号、余额、初始金额、收件人、状态、过期日
  And 支持按状态/门店/日期/余额范围筛选
  And 操作按钮: View / Reload / Cancel

场景 M6.2: 充值和取消礼品卡

Given 用户已登录并拥有对应功能所需权限与范围(scope),查看某张卡详情
When 点击 "Reload" → 输入充值金额 $25
Then 余额增加,创建 transaction (type='reload')
When 点击 "Cancel" → 确认
Then 状态变为 disabled,记录 disabled_by + disabled_reason

CUJ-M7: 忠诚度报告分析

P2 数据驱动 — 留存/流失/积分负债/礼品卡分析

TOUCHES: reports/loyalty/page.tsx, GET /api/reports/loyalty/* (5 endpoints)

报告 Tab

TabAPI内容
Retention/retention留存率、留存趋势图、到访频率分布
Churn/churn流失风险汇总、预警列表(可直接联系)
Membership/membership等级分布饼图、趋势、Top 会员
Points/points积分趋势、余额分布、负债卡(outstanding risk)、Top 赚取
Gift Cards/giftcards销售/兑换趋势、分布、即将过期提醒、负债卡

BDD 场景

场景 M7.1: 查看忠诚度报告

Given 用户已登录并拥有 reports:view_loyalty 权限,且具备对应范围(scope)
When 导航到 /reports/loyalty
  And 选择日期范围和门店筛选
Then 默认显示 Retention Tab
  And React Query 预加载相邻 Tab 数据
  And 支持导出 Excel/PDF

跨模块链接

关联模块关联点说明
CUJ-F (Payments) 礼品卡支付 + 积分抵扣 结账页面的 GiftCardPaymentOption 和 LoyaltyPaymentOption 组件
CUJ-E (Guests) VIP 状态 + 会员等级 客户详情页的 VipBadge/VipProgress 与 TierBadge/TierProgress 两套系统
CUJ-H (Reports) 忠诚度报告 reports/loyalty 5 个 Tab 整合在报告模块中
CUJ-K (Account) 客户端礼品卡管理 account/gift-cards 页面供客户查看和购买礼品卡

业务规则

#规则实现位置
1 礼品卡金额范围: $10 – $500,批量 1-100 张,消息 ≤500 字符 POST /api/gift-cards/purchase 验证
2 Claim Token: JWT 30 天有效,密钥 GIFT_CARD_SECRET (fallback JWT_SECRET) giftCardService.generateClaimToken()
3 等级自动评估: 评估周期内消费 ≥ threshold → 升级;保护期外消费不足 → 降级 loyaltyService.membership.evaluateTier()
4 折扣叠加: 3 种策略 — best (取最优)、stack (叠加)、member_first (会员优先) admin/loyalty/config
5 积分 ACID: 使用 tenantDb.transactionWithTenant 保证积分操作原子性 giftCardService.purchaseGiftCard()
6 跨店使用: 积分和礼品卡可配置是否允许跨门店使用,默认允许 loyalty config
7 两套等级系统共存: guests.vip_status (遗留) 与 membership_tiers (正式) 独立运行,未同步 guests 表 vs customer_memberships