# 租客成长等级与主动短信触达方案 ## 一、租客成长等级 ### 功能范围 - 只统计租号订单,即 `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": 0, "discount_bps": 9800 }, { "code": "diamond", "name": "钻石", "min_points": 1000, "discount_bps": 9500 }, { "code": "peak", "name": "巅峰", "min_points": 5000, "discount_bps": 9000 } ] } ``` `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` 正确。 - 找不到下一奖励时不发送。 - 单用户月上限生效。 - 冷却时间生效。 - 同一订单不会重复创建短信任务。 - 短信发送失败不影响订单完成。