237 lines
12 KiB
Markdown
237 lines
12 KiB
Markdown
# 商品购买裂变红包 / 返现 设计与实现
|
||
|
||
本文档给出在当前项目(Supabase/Postgres + ak_users + mall_orders 架构)中实现“商品购买裂变红包/返现”的可运行设计。包含表结构建议、触发与结算流程、后端实现(Supabase RPC/触发器或后端任务)、前端接口、幂等与回滚、测试与上线要点,以及示例 SQL/函数。
|
||
|
||
> 假设:项目已有用户表 `ak_users(id uuid)` 与订单表 `mall_orders(id uuid, user_id uuid, total_amount numeric, payment_status text, order_status text, created_at timestamptz, updated_at timestamptz)`。如不同请按实际表名字段映射。
|
||
|
||
---
|
||
|
||
## 1. 核心概念和需求
|
||
- 触发时机:订单支付并最终确认(例如 `payment_status='paid'` 且 `order_status='completed'`)。
|
||
- 奖励分类:
|
||
- 现金返现(cashback):计入用户余额或可提现账户。
|
||
- 裂变红包(red_envelope):作为可使用优惠券/红包发放给邀请人或按规则分发。
|
||
- 支付幂等:每个订单只会触发一次奖励(以 `order_id` 为幂等键)。
|
||
- 结算策略:两阶段(pending -> settled):先写 `pending_reward`,订单稳定后结算(防止支付回滚/退款带来的误发)。
|
||
- 支持多级分润(level 1/2/3)与比例/固定金额配置。
|
||
|
||
### 1.1 典型场景:邀请返现裂变
|
||
- 玩法定义:老用户分享邀请链接/二维码,新用户(被邀请人)完成首单或指定商品购买后,系统按照配置向邀请人发放返现金额、红包余额或实物礼品。
|
||
- 奖励归类:属于裂变红包/推荐返现活动,可选择 `cashback`(直接入账余额)或 `red_envelope`(发放红包券码),也可以组合赠礼。
|
||
- 业务目标:通过“邀请→消费→返利”实现用户增长与复购;常搭配排行榜、任务进度等激励组件。
|
||
- 风控提示:需针对批量刷单、虚假邀请做风控校验,例如限制同设备、同支付账号、同地址的重复奖励。
|
||
|
||
## 2. 推荐的数据表(SQL)
|
||
|
||
以下为最小表集合,便于实现与审计:
|
||
|
||
```sql
|
||
-- 奖励规则(可在后台管理)
|
||
CREATE TABLE mall_referral_rules (
|
||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
name text NOT NULL,
|
||
active boolean DEFAULT true,
|
||
level int DEFAULT 1, -- 支持 1、2、3 等
|
||
pct numeric, -- 百分比(例如 0.05 表示5%),与 amount 二选一
|
||
amount numeric, -- 固定金额
|
||
applies_to jsonb DEFAULT '{}'::jsonb, -- 可按商品/类/活动精细化
|
||
created_at timestamptz DEFAULT now()
|
||
);
|
||
|
||
-- 待发放的奖励记录(幂等,基于 order_id)
|
||
CREATE TABLE mall_pending_rewards (
|
||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
order_id uuid NOT NULL UNIQUE,
|
||
user_id uuid NOT NULL, -- 触发此奖励的订单用户
|
||
total_reward numeric NOT NULL DEFAULT 0,
|
||
payload jsonb DEFAULT '{}'::jsonb, -- 详细分配(各级金额/受益人)
|
||
status text DEFAULT 'pending', -- pending | cancelled | settled
|
||
created_at timestamptz DEFAULT now(),
|
||
settled_at timestamptz NULL
|
||
);
|
||
|
||
-- 已结算奖励(审计/账务)
|
||
CREATE TABLE mall_settled_rewards (
|
||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
pending_id uuid NOT NULL REFERENCES mall_pending_rewards(id),
|
||
user_id uuid NOT NULL, -- 实际被入账的用户(受益人)
|
||
kind text NOT NULL, -- 'cashback' | 'red_envelope' | 'coupon'
|
||
amount numeric NOT NULL,
|
||
meta jsonb DEFAULT '{}'::jsonb,
|
||
created_at timestamptz DEFAULT now()
|
||
);
|
||
|
||
-- 红包/券表(如果发红包或发券)
|
||
CREATE TABLE mall_red_envelopes (
|
||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id uuid NOT NULL, -- 收到红包的用户
|
||
code text NULL, -- 可选券码
|
||
amount numeric NOT NULL,
|
||
expires_at timestamptz NULL,
|
||
used boolean DEFAULT false,
|
||
created_at timestamptz DEFAULT now()
|
||
);
|
||
```
|
||
|
||
说明:业务可根据需要把 `mall_settled_rewards` 作为记账凭证,并在发放现金时同时修改用户余额表(例如 `ak_users_balance`)或调用第三方支付/钱包服务。
|
||
|
||
## 3. 触发与结算流程(建议)
|
||
|
||
流程分三步:记录->延后结算->最终发放。
|
||
|
||
1) 记录(触发):
|
||
- 当 `mall_orders` 的支付状态变为 `paid` 且订单确认时(建议在后端或 DB trigger 触发),执行:
|
||
- 计算规则(读取 `mall_referral_rules` 或活动配置),得出每个受益人应得金额;
|
||
- 写入 `mall_pending_rewards(order_id=..., user_id=order.user_id, total_reward=..., payload=...)`。使用 `order_id` 唯一约束保证幂等。
|
||
|
||
2) 延后结算(稳定期/反欺诈/退货窗口):
|
||
- 为避免退款/争议导致误发,建议设置结算延迟(例如订单完成后 24-72 小时或在订单 `completed` 且不在退款期时)。
|
||
- 使用周期性任务(Postgres cron、Supabase scheduled function 或后端 worker),查找 `mall_pending_rewards` 中 `status='pending'` 且 `created_at` 超过阈值的记录,调用结算逻辑。
|
||
|
||
3) 最终结算:
|
||
- 将 pending 转为 settled:
|
||
- 按 `payload` 把相应金额写入 `mall_settled_rewards`(多行,记录每个受益人/类型);
|
||
- 如果为 cashback:同时在用户余额(例如 `ak_users_balance`)做入账;
|
||
- 如果为 red_envelope:在 `mall_red_envelopes` 创建红包记录并发送通知;
|
||
- 更新 `mall_pending_rewards.status='settled'` 并记录 `settled_at`。
|
||
|
||
4) 退款/回滚场景:
|
||
- 若订单在结算前退款:在退款流程里查找 `mall_pending_rewards` 并把 `status='cancelled'`(并记录原因)。
|
||
- 若已结算但需回滚:必须走人工/自动对账流程,生成负向账务(在 `mall_settled_rewards` 中写入负值记录)并从用户余额或红包池中扣回(并记录审计日志)。
|
||
|
||
## 4. 示例:在 Postgres 中通过触发器记录 pending(示例)
|
||
|
||
下例为简单示例:在订单 `payment_status` 从其它值变为 `paid` 时插入 pending(实际项目请把 business logic 放到后端服务或严格写在 plpgsql 中并做好权限与审计)。
|
||
|
||
```sql
|
||
-- 插入幂等 pending 的 helper
|
||
CREATE OR REPLACE FUNCTION mall_insert_pending_reward_if_paid() RETURNS trigger AS $$
|
||
BEGIN
|
||
IF (TG_OP = 'UPDATE') THEN
|
||
IF (NEW.payment_status = 'paid' AND OLD.payment_status IS DISTINCT FROM 'paid') THEN
|
||
-- 计算 reward 简化示例:按订单 total_amount 的 3% 给上级(假设有 inviter_id 在 ak_users)
|
||
PERFORM 1; -- placeholder
|
||
-- 幂等插入
|
||
INSERT INTO mall_pending_rewards(order_id, user_id, total_reward, payload)
|
||
VALUES (NEW.id, NEW.user_id, 0, jsonb_build_object('note','to calculate'))
|
||
ON CONFLICT (order_id) DO NOTHING;
|
||
END IF;
|
||
END IF;
|
||
RETURN NEW;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
|
||
CREATE TRIGGER trg_orders_paid_pending
|
||
AFTER UPDATE ON mall_orders
|
||
FOR EACH ROW
|
||
WHEN (OLD.payment_status IS DISTINCT FROM NEW.payment_status)
|
||
EXECUTE FUNCTION mall_insert_pending_reward_if_paid();
|
||
```
|
||
|
||
注意:上例仅记录 pending,实际计算建议在后端 microservice 中完成(因为规则可能包含复杂逻辑、外部数据或调用)。
|
||
|
||
## 5. 后端实现建议(两种可选方案)
|
||
|
||
- 方案 A:在后端服务(Node/TS)中实现完整逻辑:
|
||
- 优点:代码易维护、测试、可复用第三方服务;适合复杂规则/风控。可在订单 webhook/异步 worker 中处理。
|
||
- 步骤:订单支付 webhook -> 计算 reward -> 写入 `mall_pending_rewards` -> 调度结算任务或 push 到队列。
|
||
|
||
- 方案 B:尽量用 Postgres(Supabase)函数与 scheduled function:
|
||
- 优点:部署简单、与 DB 紧耦合且事务一致性好。
|
||
- 步骤:trigger 插入 pending -> 定期运行 RPC(pg cron 或 Supabase scheduled)执行结算函数,函数内更新余额/插入 `mall_settled_rewards`。
|
||
|
||
推荐:更复杂的促销与风控建议走后端服务;轻量玩法可用方案 B。
|
||
|
||
## 6. 示例结算函数(伪代码/PLPGSQL)
|
||
|
||
下例演示一个简单的结算过程:
|
||
|
||
```sql
|
||
CREATE OR REPLACE FUNCTION mall_settle_pending_rewards(batch_limit int DEFAULT 100) RETURNS int AS $$
|
||
DECLARE
|
||
rec record;
|
||
cnt int := 0;
|
||
BEGIN
|
||
FOR rec IN SELECT * FROM mall_pending_rewards WHERE status='pending' AND created_at < now() - interval '24 hours' LIMIT batch_limit
|
||
LOOP
|
||
-- 按 payload 计算并发放。此处示例将 total_reward 拆为单个 cashback 发给 user_id
|
||
PERFORM pg_sleep(0); -- placeholder
|
||
INSERT INTO mall_settled_rewards(pending_id, user_id, kind, amount, meta)
|
||
VALUES (rec.id, rec.user_id, 'cashback', rec.total_reward, rec.payload);
|
||
|
||
-- 真实系统要在事务中同时更新用户余额表 ak_users_balance
|
||
UPDATE mall_pending_rewards SET status='settled', settled_at = now() WHERE id = rec.id;
|
||
cnt := cnt + 1;
|
||
END LOOP;
|
||
RETURN cnt;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
```
|
||
|
||
然后通过 Supabase 的 scheduled function 或 dbcron 定期调用:
|
||
|
||
```sql
|
||
SELECT mall_settle_pending_rewards(100);
|
||
```
|
||
|
||
## 7. 前端与 API 设计
|
||
|
||
- 下单端:不直接处理裂变;仅依赖后端/DB 的异步结算。
|
||
- 管理端(运营配置):
|
||
- CRUD 接口:`/api/admin/referral-rules` 管理 `mall_referral_rules`。
|
||
- 查询待结算与已结算记录:`/api/admin/pending-rewards`、`/api/admin/settled-rewards`。
|
||
- 用户端:
|
||
- 查询可用红包/余额:`/api/user/wallet`、`/api/user/red-envelopes`。
|
||
- 接收推送/消息通知:当红包或返现金额发放时推送给目标用户。
|
||
|
||
接口示例(REST):
|
||
|
||
```
|
||
POST /api/admin/referral-rules
|
||
GET /api/admin/pending-rewards?status=pending
|
||
POST /api/admin/settle-pending (触发手动结算)
|
||
|
||
GET /api/user/wallet
|
||
GET /api/user/red-envelopes
|
||
```
|
||
|
||
## 8. 幂等与并发说明
|
||
- 在写入 `mall_pending_rewards` 时加上 `UNIQUE(order_id)`,并在插入时使用 `ON CONFLICT DO NOTHING` 以保证幂等。
|
||
- 结算任务要使用 SELECT ... FOR UPDATE SKIP LOCKED 等模式或分片(按 id 范围)来避免并发重复处理。
|
||
|
||
示例(避免重复处理):
|
||
|
||
```sql
|
||
-- 结算任务应以行锁模式取待处理记录
|
||
WITH to_settle AS (
|
||
SELECT id FROM mall_pending_rewards WHERE status='pending' AND created_at < now() - interval '24 hours' LIMIT 50 FOR UPDATE SKIP LOCKED
|
||
)
|
||
UPDATE mall_pending_rewards SET status='processing' FROM to_settle WHERE mall_pending_rewards.id = to_settle.id RETURNING mall_pending_rewards.*;
|
||
-- 然后在后续进程中处理这些记录并最终设为 settled
|
||
```
|
||
|
||
## 9. 测试计划
|
||
- 单元/集成测试:
|
||
- 用不同规则组合(百分比/固定)构造订单并验证 pending payload 与 settled 结果。
|
||
- 模拟退款:在 pending 未结算时触发退款,确认 pending 被取消。
|
||
- 模拟并发:同时多 worker 调度结算,确保 SKIP LOCKED 能防止重复发放。
|
||
- 手工/跑批测试:在测试库中用 cron 调用 `mall_settle_pending_rewards` 并核对 `mall_settled_rewards` 与用户余额。
|
||
|
||
## 10. 上线与运营注意点
|
||
- 风控:先小规模上线(例如仅 1% 订单或某活动订单)观察异常。记录每笔 reward 的来源、触发时机与受益人以便追溯。
|
||
- 审计:`mall_settled_rewards` 必须作为法务/财务审计凭证,不要删除。对回滚产生的负向条目也要记录。
|
||
- 配置:运营界面要支持对规则的开启/关闭、黑名单(不参与裂变的用户)与白名单。
|
||
|
||
---
|
||
|
||
如果你愿意,我可以:
|
||
|
||
1. 根据你仓库中的真实订单/用户表把上面 SQL 改成精确的字段和外键;
|
||
2. 帮你实现一个 Supabase scheduled function + plpgsql 结算示例并测试;
|
||
3. 或者把完整后端 Node/TS worker 示例(含队列、事务与幂等)写出来。
|
||
|
||
告诉我你希望我继续做哪一步,我会直接在仓库里添加/编辑相应文件。
|
||
|
||
|
||
|