Files
akmon/doc_mall/裂变红包.md
2026-01-20 08:04:15 +08:00

237 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 商品购买裂变红包 / 返现 设计与实现
本文档给出在当前项目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尽量用 PostgresSupabase函数与 scheduled function
- 优点:部署简单、与 DB 紧耦合且事务一致性好。
- 步骤trigger 插入 pending -> 定期运行 RPCpg 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 示例(含队列、事务与幂等)写出来。
告诉我你希望我继续做哪一步,我会直接在仓库里添加/编辑相应文件。