# 专属优惠充值 + 线下充值 功能说明

> 版本: v7  
> 日期: 2026-06-03  
> 作者: skyline

---

## 一、专属充值优惠活动

### 1.1 功能概述

运营后台创建专属充值配置（如充值 100 元送 20 元），生成小程序二维码。用户扫码进入小程序，在有效期内以专属优惠完成充值。二维码支持多人使用，可限定目标用户范围。

### 1.2 业务流程

```
运营后台                    微信小程序                      后端
   |                          |                             |
   |-- 创建优惠活动 --------->|                             |
   |                          |                             |
   |-- 生成小程序二维码 ----->|                             |
   |   (wxacode.getUnlimited)|                             |
   |                          |                             |
   |                          |-- 用户扫码 --> 小程序启动 --|
   |                          |   App.vue 检测 scene       |
   |                          |   跳转优惠充值页            |
   |                          |                             |
   |                          |-- GET /common/promotionInfo ->
   |                          |   (查询活动详情)            |
   |                          |                             |
   |                          |-- GET /payment/promotionPay ->
   |                          |   (发起微信支付)            |
   |                          |                             |
   |                          |-- 微信支付成功回调 --------->|
   |                          |   wxNotify 检测 promotionId |
   |                          |   使用活动赠费更新余额       |
```

### 1.3 数据库

**新表: `t_recharge_promotion`**

| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键 |
| title | VARCHAR(128) | 活动标题 |
| recharge_amount | INT | 充值金额（分） |
| grants_amount | INT | 赠送金额（分） |
| target_mode | TINYINT | 1=所有人, 2=指定用户 |
| target_user_ids | TEXT | 目标用户ID列表（JSON数组），null=不限制 |
| start_time | DATETIME | 活动开始时间 |
| end_time | DATETIME | 活动结束时间 |
| status | TINYINT | 0=禁用, 1=启用 |
| token | VARCHAR(32) | 二维码场景值（唯一标识） |
| qr_code_url | VARCHAR(512) | 二维码图片URL |

`t_wallet_detail` 新增字段:

| 字段 | 类型 | 说明 |
|------|------|------|
| source | VARCHAR(16) | WX_PAY / MANUAL |
| promotion_id | BIGINT | 关联优惠活动ID，null=非活动充值 |

### 1.4 API 接口

#### 管理后台

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/rechargePromotion/list` | promotion.list | 分页查询活动列表 |
| GET | `/rechargePromotion/detail/{id}` | — | 查询活动详情 |
| POST | `/rechargePromotion/add` | promotion.add | 新增活动（自动生成token） |
| POST | `/rechargePromotion/modify` | promotion.modify | 修改活动 |
| GET | `/rechargePromotion/remove/{id}` | promotion.remove | 删除活动 |
| GET | `/rechargePromotion/generateQR/{id}` | promotion.list | 生成小程序二维码（上传OSS） |

#### 小程序端

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/common/promotionInfo?token=xxx` | 查询优惠活动详情（含有效期校验） |
| GET | `/payment/promotionPay?promotionToken=xxx&stationId=xxx` | 发起优惠活动充值支付，返回JSAPI参数 |

### 1.5 关键逻辑

**支付回调区分优惠活动与普通充值：**

`WxPayServiceImpl.wxNotify()` 中先检查 `walletDetail.promotionId`：
- 不为 null → 用优惠活动的 `grantsAmount`
- 为 null → 走原有站点充值配置逻辑

**用户资格校验：**

`RechargePromotionServiceImpl.checkUserEligible()`：
- `targetMode=1`（所有人）→ 直接通过
- `targetMode=2`（指定用户）→ 从 `targetUserIds` JSON 数组解析，检查当前用户ID是否在列表中

---

## 二、线下充值

### 2.1 功能概述

用户通过微信转账给运营人员，运营人员在管理后台代为充值，直接更新用户钱包余额。钱不经过微信商户，无微信交易记录。

### 2.2 业务流程

```
用户 --(微信转账)--> 运营人员
                        |
                        |-- 登录管理后台 → 线下充值页面
                        |-- 输入用户手机号 → 查询用户
                        |-- 输入充值金额 + 赠送金额 + 备注
                        |-- 确认提交
                        |
后端: 创建 WalletDetail (source=MANUAL, status=已确认)
     更新 Account 余额
     创建 SplitRecord (走V2结算)
     不创建 PayLog (无微信交易)
```

### 2.3 API 接口

#### 管理后台

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | `/finance/offlineRecharge` | offlineRecharge | 线下充值 |

**请求参数 (JSON Body):**

```json
{
  "userId": 10001,
  "amount": 5000,
  "grantsAmount": 500,
  "remark": "微信转账充值，流水号: xxx"
}
```

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userId | Long | 是 | 用户ID |
| amount | Integer | 是 | 充值金额（分） |
| grantsAmount | Integer | 否 | 赠送金额（分），默认0 |
| remark | String | 否 | 备注信息 |

### 2.4 与线上充值的区别

| | 线上充值（微信支付） | 线下充值（人工） |
|------|------|------|
| WalletDetail.source | WX_PAY | MANUAL |
| WalletDetail.status | 先创建待确认→回调后确认 | 直接已确认 |
| PayLog | 创建 | **不创建** |
| SplitRecord | 创建 | 创建 |
| 退款 | 走微信退款流程 | 暂不支持系统退款（需人工处理） |
| 操作日志 | 微信回调日志 | @SysLog "线下充值" |

---

## 三、部署清单

### 3.1 数据库迁移

执行 SQL: `car-wash-entity/src/main/resources/sql/v7_recharge_promotion.sql`

### 3.2 权限分配

在角色管理中为运营人员分配以下权限：

| 权限标识 | 名称 | 菜单 |
|------|------|------|
| promotion.list | 优惠活动管理 | 平台配置 → 专属优惠活动 |
| promotion.add | 优惠活动新增 | 新增按钮 |
| promotion.modify | 优惠活动修改 | 编辑按钮 |
| promotion.remove | 优惠活动删除 | 删除按钮 |
| offlineRecharge | 线下充值 | 财务管理 → 线下充值 |

### 3.3 小程序发布

1. 确保 `pages-user/wallet/promotion` 页面已注册到 `pages.json`
2. 重新发布小程序（新增页面需审核）
3. 确保 `wechat.miniapp.appid` 和 `wechat.miniapp.secret` 配置正确（用于生成小程序码）

---

## 四、注意事项

1. **二维码有效期**：微信小程序码本身无过期时间，有效期由活动的 `start_time` / `end_time` 控制，扫码时后端校验
2. **线下充值不可退款**：线下充值的资金未经过微信商户，系统无法通过微信退款接口原路退回。如需退线下充值款项，需人工线下处理
3. **线下充值的安全性**：依赖 `offlineRecharge` 权限控制，所有操作通过 `@SysLog` 记录审计日志，WalletDetail.remark 字段记录操作备注
4. **活动 token 唯一性**：创建活动时自动生成 16 位随机字符串作为 token，数据库有唯一索引防止冲突，失败时自动重试最多 5 次
5. **小程序码图片存储**：二维码图片上传到阿里云 OSS，URL 存入 `qr_code_url` 字段，重复请求直接返回已有 URL
