343 lines
9.2 KiB
Markdown
343 lines
9.2 KiB
Markdown
# 租客成长等级与主动短信触达方案
|
||
|
||
## 一、租客成长等级
|
||
|
||
### 功能范围
|
||
|
||
- 只统计租号订单,即 `rental_orders`。
|
||
- 不统计撞车订单,即 `mohong_orders`。
|
||
- 积分只按已完成租号订单的实付租金累计,不含押金。
|
||
- 等级三档:铂金、钻石、巅峰。
|
||
- 下单租号时按用户当前等级使用折扣。
|
||
- 折扣和升级门槛后台可配置。
|
||
- 历史订单保存价格、等级、折扣快照,后台修改配置不影响历史订单。
|
||
|
||
### 积分规则
|
||
|
||
推荐规则:
|
||
|
||
```text
|
||
成长积分 = 已完成租号订单实付租金金额
|
||
1 元实付租金 = 1 积分
|
||
```
|
||
|
||
不建议统计押金,原因是押金可能退款、免押、暂扣或赔付,直接计入成长积分会让财务口径变复杂。
|
||
|
||
积分发放时机建议放在租号订单完成后,而不是支付成功后。这样可以避免用户付款后退款、取消或关闭订单导致刷积分。
|
||
|
||
### 后台配置
|
||
|
||
使用现有 `system_configs`,新增配置项:
|
||
|
||
```text
|
||
renter.growth_level_rules
|
||
```
|
||
|
||
建议默认值:
|
||
|
||
```json
|
||
{
|
||
"enabled": true,
|
||
"points_per_yuan": 1,
|
||
"levels": [
|
||
{ "code": "platinum", "name": "铂金", "min_points": 300, "discount_bps": 9900 },
|
||
{ "code": "diamond", "name": "钻石", "min_points": 1000, "discount_bps": 9800 },
|
||
{ "code": "peak", "name": "巅峰", "min_points": 5000, "discount_bps": 9500 }
|
||
]
|
||
}
|
||
```
|
||
|
||
`discount_bps` 使用万分比,避免小数误差。例如 `9800` 表示 9.8 折。
|
||
|
||
### 数据库字段
|
||
|
||
`users` 建议新增:
|
||
|
||
- `renter_growth_points`:租客成长积分。
|
||
- `renter_growth_level`:当前租客成长等级。
|
||
|
||
`rental_orders` 建议新增:
|
||
|
||
- `rent_original_amount_cent`:原始租金。
|
||
- `rent_discount_amount_cent`:租金优惠金额。
|
||
- `renter_growth_level`:下单时命中的等级编码。
|
||
- `renter_growth_level_name`:下单时命中的等级名称。
|
||
- `renter_discount_bps`:下单时命中的折扣。
|
||
- `growth_points_awarded`:本单已发放积分,用于防重复。
|
||
|
||
### 下单折扣
|
||
|
||
在订单创建阶段应用折扣,支付阶段不重新计算。
|
||
|
||
现有租号订单创建位置:
|
||
|
||
```text
|
||
backend/internal/modules/order/lifecycle.go
|
||
```
|
||
|
||
现有支付金额来自:
|
||
|
||
```text
|
||
backend/internal/modules/payment/payment_start.go
|
||
amountCent := row.RentAmountCent + row.DepositAmountCent
|
||
```
|
||
|
||
因此订单创建时应直接把 `RentAmountCent` 写成折后租金,后续支付、退款、结算、仲裁都沿用订单金额。
|
||
|
||
推荐折扣范围:
|
||
|
||
- 只折扣租金。
|
||
- 押金不参与折扣。
|
||
- 折扣优先扣平台费,不影响号主收入。
|
||
- 如果折扣金额超过平台费,第一版建议限制为最多扣到平台费为 0,避免平台费出现负数。
|
||
|
||
### 自动升级
|
||
|
||
租号订单首次完成时:
|
||
|
||
1. 校验 `growth_points_awarded = 0`,避免重复发放。
|
||
2. 根据折后租金计算本单积分。
|
||
3. 累加到 `users.renter_growth_points`。
|
||
4. 根据 `min_points` 自动计算用户当前等级。
|
||
5. 更新 `users.renter_growth_level`。
|
||
6. 回写 `rental_orders.growth_points_awarded`。
|
||
|
||
默认只升级不降级。若后台未来调整门槛,可以选择只影响后续升级判断,不主动批量降级。
|
||
|
||
### 前端范围
|
||
|
||
后台系统配置页:
|
||
|
||
- 配置是否启用。
|
||
- 配置每元积分。
|
||
- 配置三个等级的门槛积分。
|
||
- 配置三个等级的折扣。
|
||
|
||
后台用户管理:
|
||
|
||
- 展示成长等级。
|
||
- 展示成长积分。
|
||
- 支持后台调整积分或等级,操作写审计日志。
|
||
|
||
用户端订单页:
|
||
|
||
- 展示租金原价。
|
||
- 展示等级折扣。
|
||
- 展示优惠金额。
|
||
- 展示折后租金。
|
||
- 展示押金。
|
||
- 展示应付合计。
|
||
|
||
### 测试范围
|
||
|
||
- 铂金、钻石、巅峰不同等级折扣计算正确。
|
||
- 押金不打折。
|
||
- 折扣不影响号主收入。
|
||
- 支付金额等于折后租金加实付押金。
|
||
- 撞车订单完成不增加成长积分。
|
||
- 租号订单完成只发放一次成长积分。
|
||
- 积分达到门槛后自动升级。
|
||
- 后台修改配置不影响历史订单快照。
|
||
|
||
## 二、主动短信触达
|
||
|
||
### 业务目标
|
||
|
||
用户完成一笔租号订单后,平台可以主动发送短信,告知用户本月已经累计完成多少笔租号订单,以及距离领取奖励还差多少笔。
|
||
|
||
示例文案:
|
||
|
||
```text
|
||
尊敬的 用户名:
|
||
您好,您已经在 大锤商行 累计完成 1 笔租号订单,再完成 x 笔即可领取 xxxx。
|
||
```
|
||
|
||
### 统计口径
|
||
|
||
- 只统计 `rental_orders`。
|
||
- 只统计状态为 `completed` 的租号订单。
|
||
- 不统计 `mohong_orders` 撞车订单。
|
||
- 按自然月统计,建议使用订单完成时间 `settled_at`。
|
||
- 月统计区间为当前月第一天 00:00:00 到下月第一天 00:00:00。
|
||
|
||
统计 SQL 口径示例:
|
||
|
||
```sql
|
||
SELECT COUNT(*)
|
||
FROM rental_orders
|
||
WHERE renter_id = ?
|
||
AND status = 'completed'
|
||
AND settled_at >= ?
|
||
AND settled_at < ?;
|
||
```
|
||
|
||
### 触发时机
|
||
|
||
推荐触发点:租号订单完成后。
|
||
|
||
现有完成入口:
|
||
|
||
```text
|
||
backend/internal/modules/order/checkout_finalize.go
|
||
```
|
||
|
||
`finalizeCheckout` 会把订单状态改为 `completed`。短信不建议在事务内直接发送,避免短信服务失败导致订单完成失败。
|
||
|
||
推荐流程:
|
||
|
||
1. 订单完成事务内写入短信待发送记录。
|
||
2. 事务提交后由后台任务发送短信。
|
||
3. 发送成功、失败、重试次数都落库。
|
||
|
||
### 短信通道
|
||
|
||
项目已有短信集成:
|
||
|
||
```text
|
||
backend/internal/integrations/sms
|
||
```
|
||
|
||
当前 `Provider` 只支持登录验证码:
|
||
|
||
```go
|
||
SendLoginCode(ctx context.Context, phone string, code string) error
|
||
```
|
||
|
||
主动短信需要扩展为模板短信能力,例如:
|
||
|
||
```go
|
||
SendTemplate(ctx context.Context, phone string, templateCode string, params map[string]string) error
|
||
```
|
||
|
||
阿里云短信实际发送不能随意拼接任意文案,必须使用已审核的短信模板。因此后台可配置“模板 code、奖励规则、预览文案”,真实发送时只传模板变量。
|
||
|
||
### 后台配置
|
||
|
||
使用 `system_configs`,新增配置项:
|
||
|
||
```text
|
||
sms.rental_completion_reminder
|
||
```
|
||
|
||
建议默认值:
|
||
|
||
```json
|
||
{
|
||
"enabled": false,
|
||
"brand_name": "大锤商行",
|
||
"template_code": "",
|
||
"monthly_user_limit": 4,
|
||
"cooldown_hours": 24,
|
||
"rewards": [
|
||
{ "target_orders": 3, "reward_name": "成长礼包", "enabled": true },
|
||
{ "target_orders": 5, "reward_name": "专属优惠券", "enabled": true }
|
||
],
|
||
"preview_template": "尊敬的 {{nickname}}:您好,您已经在 {{brand_name}} 累计完成 {{completed_count}} 笔租号订单,再完成 {{remaining_count}} 笔即可领取 {{reward_name}}。"
|
||
}
|
||
```
|
||
|
||
规则说明:
|
||
|
||
- `enabled`:总开关。
|
||
- `brand_name`:短信里展示的品牌名。
|
||
- `template_code`:阿里云审核通过的营销或通知短信模板 code。
|
||
- `monthly_user_limit`:单用户单月最多发送次数。
|
||
- `cooldown_hours`:同一用户两次主动短信最小间隔。
|
||
- `rewards`:奖励门槛,从小到大配置。
|
||
- `preview_template`:后台预览使用,真实发送以短信供应商模板为准。
|
||
|
||
计算 `x` 的规则:
|
||
|
||
1. 获取用户本月完成租号订单数 `completed_count`。
|
||
2. 找到第一个 `target_orders > completed_count` 的奖励。
|
||
3. `remaining_count = target_orders - completed_count`。
|
||
4. 如果没有下一个奖励,默认不发送,或后续配置为发送达成提醒。
|
||
|
||
### 数据库表
|
||
|
||
建议新增短信发送记录表:
|
||
|
||
```text
|
||
sms_messages
|
||
```
|
||
|
||
字段建议:
|
||
|
||
- `id`
|
||
- `user_id`
|
||
- `phone`
|
||
- `biz_type`:例如 `rental_completion_reminder`
|
||
- `biz_id`:租号订单 ID。
|
||
- `template_code`
|
||
- `template_params`
|
||
- `content_preview`
|
||
- `status`:`pending`、`sent`、`failed`、`cancelled`
|
||
- `retry_count`
|
||
- `last_error`
|
||
- `sent_at`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
建议加唯一索引:
|
||
|
||
```text
|
||
uk_sms_messages_biz_type_biz_id_user
|
||
```
|
||
|
||
用于保证同一笔完成订单不会重复创建短信任务。
|
||
|
||
### 发送任务
|
||
|
||
新增后台任务,例如:
|
||
|
||
```text
|
||
backend/internal/jobs/smsdispatch
|
||
```
|
||
|
||
任务逻辑:
|
||
|
||
1. 查询 `pending` 且重试次数未超限的短信。
|
||
2. 调用短信 provider 发送模板短信。
|
||
3. 成功后标记 `sent`,写 `sent_at`。
|
||
4. 失败后记录 `last_error`,增加 `retry_count`。
|
||
5. 超过最大重试次数后标记 `failed`。
|
||
|
||
### 限流与防打扰
|
||
|
||
发送前需要校验:
|
||
|
||
- 系统配置已启用。
|
||
- 用户手机号有效。
|
||
- 本订单尚未创建过同类短信。
|
||
- 用户本月主动短信次数未超过 `monthly_user_limit`。
|
||
- 用户距离上次主动短信超过 `cooldown_hours`。
|
||
- 找得到下一个奖励门槛。
|
||
|
||
第一版可以先不做用户退订,但如果短信属于营销性质,建议后续加用户短信订阅状态或退订名单。
|
||
|
||
### 前端范围
|
||
|
||
后台系统配置页:
|
||
|
||
- 开关。
|
||
- 品牌名。
|
||
- 模板 code。
|
||
- 单用户月发送上限。
|
||
- 冷却时间。
|
||
- 奖励门槛和奖励名称。
|
||
- 文案预览。
|
||
|
||
后台短信记录页可以作为二期,第一版可先通过数据库和日志排查。
|
||
|
||
### 测试范围
|
||
|
||
- 租号订单完成后创建短信待发送记录。
|
||
- 撞车订单完成不创建短信。
|
||
- 本月完成订单数统计正确。
|
||
- 根据奖励门槛计算 `remaining_count` 正确。
|
||
- 找不到下一奖励时不发送。
|
||
- 单用户月上限生效。
|
||
- 冷却时间生效。
|
||
- 同一订单不会重复创建短信任务。
|
||
- 短信发送失败不影响订单完成。
|