# Web端商户提现申请接口文档

## 模块概述

本模块提供提现申请功能，商户可以通过本接口提交提现申请，系统将验证支付密码、账户余额等信息后创建提现记录。Web端采用人工审核模式，提现申请提交后需等待管理员审核。

---

## 接口信息

### 提现申请

#### 接口详情

- **接口名称**: 提现申请
- **接口路径**: `POST /incomeManage/cashOut`
- **请求方式**: POST
- **接口描述**: 商户提交提现申请，扣减账户余额并创建待审核的提现记录
- **登录验证**: ✅ 需要（使用 `@LoginRequired` 注解）

---

## 请求参数

### 请求体（Request Body）

使用 `CashOutDTO` 对象作为请求体（JSON格式）：

| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|--------|------|------|------|--------|
| payPassword | String | 是 | 支付密码（MD5加密） | 222222 |
| withdrawalMoney | Integer | 是 | 提现金额（单位：分） | 50000 |

### 参数说明

#### withdrawalMoney（提现金额）
- **单位**: 分（Integer）
- **最小金额**: 10分（0.1元）
- **最大金额**: 不超过账户余额
- **示例**: 50000分 = 500.00元

#### payPassword（支付密码）
- **加密方式**: MD5
- **说明**: 需要与数据库中存储的支付密码一致

**注意**: `storeId` 从登录用户的 Token 中自动获取，不需要在请求中传递。

---

## 请求示例

```http
POST /incomeManage/cashOut
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
    "payPassword": "222222",
    "withdrawalMoney": 50000
}
```

```bash
curl -X POST "http://localhost:8080/incomeManage/cashOut" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payPassword": "222222",
    "withdrawalMoney": 50000
  }'
```

```javascript
// JavaScript
fetch('/incomeManage/cashOut', {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer YOUR_TOKEN',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        payPassword: '222222',
        withdrawalMoney: 50000
    })
})
.then(res => res.json())
.then(data => console.log(data));
```

---

## 响应参数

### 成功响应

```json
{
    "code": 200,
    "success": true,
    "data": {
        "cashOutRecordId": 1001,
        "withdrawalMoney": "500.00",
        "status": "pending",
        "message": "提现申请已提交，等待审核"
    },
    "msg": "操作成功"
}
```

### 响应字段说明

| 字段名 | 类型 | 说明 |
|--------|------|------|
| data.cashOutRecordId | Long | 提现记录ID |
| data.withdrawalMoney | String | 提现金额（单位：元） |
| data.status | String | 提现状态（pending-待审核） |
| data.message | String | 提示信息 |

---

### 失败响应

#### 1. 支付密码错误

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "支付密码错误"
}
```

#### 2. 余额不足

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "余额不足"
}
```

#### 3. 金额过小

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "金额不能小于0.1元"
}
```

#### 4. 未登录或登录过期

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "请先登录"
}
```

#### 5. 系统异常

```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "提现失败：{异常信息}"
}
```

---

## 业务逻辑说明

### 处理流程

```
┌─────────────────────────────────────────────┐
│  1. 接收提现参数（从JWT Token获取storeId）    │
│  - payPassword（从DTO）                      │
│  - withdrawalMoney（从DTO）                  │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  2. 验证支付密码                              │
│  根据 storeId 和 payPassword 查询用户        │
│  IF 用户不存在                               │
│    RETURN "支付密码错误"                     │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  3. 验证账户余额                              │
│  IF storeUser.money < withdrawalMoney        │
│    RETURN "余额不足"                         │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  4. 验证提现金额                              │
│  amountInYuan = withdrawalMoney ÷ 100        │
│  IF amountInYuan < 0.1                       │
│    RETURN "金额不能小于0.1元"                │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  5. 创建提现记录                              │
│  storeCashOutRecord.storeId = storeId        │
│  storeCashOutRecord.money = withdrawalMoney  │
│  storeCashOutRecord.paymentStatus = 1（待审核）│
│  storeCashOutRecord.storeUserId = userId     │
│  INSERT INTO store_cash_out_record           │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  6. 扣减账户余额                              │
│  newMoney = storeUser.money - withdrawalMoney│
│  UPDATE store_user SET money = newMoney      │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  7. 返回提现记录信息                          │
│  返回提现记录ID、金额、状态等                 │
└─────────────────────────────────────────────┘
```

### Web端审核模式

**与App端差异**:
- **App端**: 直接调用支付宝API，实时转账
- **Web端**: 创建待审核记录，人工审核后再打款

**审核流程**:
1. 商户提交提现申请
2. 系统扣减账户余额，创建待审核记录
3. 管理员审核提现申请
4. 审核通过后调用支付宝API打款
5. 打款成功后更新提现状态

---

## 数据库操作

### 涉及的表

#### 1. store_user（店铺用户表）

**查询SQL**:
```sql
SELECT * FROM store_user
WHERE store_id = ?
  AND pay_password = ?
```

**说明**:
- 验证支付密码
- 获取账户余额和用户信息

**更新SQL**:
```sql
UPDATE store_user
SET money = money - ?
WHERE id = ?
```

**说明**:
- 扣减账户余额
- 使用事务保证数据一致性

#### 2. store_cash_out_record（提现记录表）

**插入SQL**:
```sql
INSERT INTO store_cash_out_record (
    store_id,
    money,
    cash_out_type,
    payment_status,
    delete_flag,
    store_user_id,
    pay_date
) VALUES (?, ?, 0, 1, 0, ?, NOW())
```

**字段说明**:
- `store_id`: 门店ID
- `money`: 提现金额（分）
- `cash_out_type`: 提现类型，0-全部提现
- `payment_status`: 支付状态，1-待审核
- `delete_flag`: 删除标识，0-未删除
- `store_user_id`: 用户ID
- `pay_date`: 申请时间

---

## 提现状态说明

### payment_status 状态值

| Status | 说明 | 描述 |
|--------|------|------|
| 1 | 待审核 | 提现申请已提交，等待管理员审核 |
| 2 | 审核不通过 | 管理员拒绝提现申请 |
| 3 | 已通过 | 管理员审核通过，等待打款 |
| 4 | 已打款 | 支付宝转账成功 |
| 5 | 打款失败 | 支付宝转账失败 |

**Web端流程**:
```
1(待审核) -> 3(已通过) -> 4(已打款)
         -> 2(审核不通过)
         -> 5(打款失败)
```

---

## 金额计算规则

### 金额单位转换

| 数据库存储（分） | 显示（元） | 转换公式 |
|------------------|-----------|----------|
| 50000 | 500.00 | money ÷ 100 |
| 10 | 0.10 | money ÷ 100 |
| 100 | 1.00 | money ÷ 100 |

### 转换代码

```java
// 提现金额转换（分 -> 元）
BigDecimal amountInYuan = new BigDecimal(withdrawalMoney)
    .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
```

**转换规则**:
- 使用 `BigDecimal` 进行精确计算
- 除以100转换为元
- 保留2位小数
- 舍入模式：`DOWN`（向下取整）

---

## 业务场景

### 场景 1: 正常提现

**前置条件**:
- 账户余额: 100000分（1000元）
- 提现金额: 50000分（500元）
- 支付密码: 正确

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 50000
}
```

**响应**:
```json
{
    "code": 200,
    "success": true,
    "data": {
        "cashOutRecordId": 1001,
        "withdrawalMoney": "500.00",
        "status": "pending",
        "message": "提现申请已提交，等待审核"
    }
}
```

**结果**:
- ✅ 创建提现记录
- ✅ 账户余额变为50000分（500元）
- ✅ 提现记录状态为"待审核"

---

### 场景 2: 支付密码错误

**前置条件**:
- 账户余额: 100000分
- 提现金额: 50000分
- 支付密码: **错误**

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "wrongpassword",
    "withdrawalMoney": 50000
}
```

**响应**:
```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "支付密码错误"
}
```

**结果**:
- ❌ 提现失败
- ❌ 账户余额不变
- ❌ 不创建提现记录

---

### 场景 3: 余额不足

**前置条件**:
- 账户余额: 30000分（300元）
- 提现金额: 50000分（500元）
- 支付密码: 正确

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 50000
}
```

**响应**:
```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "余额不足"
}
```

**结果**:
- ❌ 提现失败
- ❌ 账户余额不变
- ❌ 不创建提现记录

---

### 场景 4: 金额过小

**前置条件**:
- 账户余额: 100000分
- 提现金额: 5分（0.05元，小于最低0.1元）
- 支付密码: 正确

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 5
}
```

**响应**:
```json
{
    "code": 500,
    "success": false,
    "data": null,
    "msg": "金额不能小于0.1元"
}
```

**结果**:
- ❌ 提现失败
- ❌ 账户余额不变
- ❌ 不创建提现记录

---

### 场景 5: 全部提现

**前置条件**:
- 账户余额: 100000分（1000元）
- 提现金额: 100000分（全部余额）
- 支付密码: 正确

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 100000
}
```

**响应**:
```json
{
    "code": 200,
    "success": true,
    "data": {
        "cashOutRecordId": 1002,
        "withdrawalMoney": "1000.00",
        "status": "pending",
        "message": "提现申请已提交，等待审核"
    }
}
```

**结果**:
- ✅ 创建提现记录
- ✅ 账户余额变为0分
- ✅ 提现记录状态为"待审核"

---

## 事务管理

### 事务范围

接口使用 `@Transactional` 注解保证数据一致性：

```java
@Transactional
@Override
public R<?> cashOut(Integer storeId, String payPassword, Integer withdrawalMoney) {
    // 1. 查询用户
    // 2. 验证余额
    // 3. 创建提现记录
    // 4. 扣减账户余额
}
```

### 事务保证

如果任何一步操作失败，所有操作都会回滚：
- ✅ 创建提现记录失败 -> 回滚
- ✅ 扣减余额失败 -> 回滚（提现记录也回滚）
- ✅ 抛出异常 -> 回滚

### 隔离级别

使用数据库默认隔离级别（MySQL: REPEATABLE_READ）

---

## 安全考虑

### 1. 支付密码验证

- ✅ 支付密码必须正确
- ✅ 密码存储使用MD5加密
- ✅ 密码错误不返回详细信息

### 2. 金额验证

- ✅ 提现金额必须大于等于0.1元
- ✅ 提现金额不能超过账户余额
- ✅ 使用BigDecimal避免精度问题

### 3. 并发控制

- ✅ 使用事务保证数据一致性
- ✅ 乐观锁或悲观锁防止并发问题
- ⚠️ 建议：高并发场景下使用分布式锁

### 4. 日志记录

- ✅ 记录所有提现请求
- ✅ 记录失败原因
- ⚠️ 不记录支付密码明文

---

## 性能优化建议

### 1. 数据库索引

确保以下字段有索引:

```sql
-- store_user表
ALTER TABLE store_user 
ADD INDEX idx_store_pay (store_id, pay_password);

-- store_cash_out_record表
ALTER TABLE store_cash_out_record 
ADD INDEX idx_store_status (store_id, payment_status);
```

### 2. 查询优化

- ✅ 使用复合索引加速查询
- ✅ 一次查询获取所有需要的字段
- ✅ 避免不必要的JOIN操作

### 3. 事务优化

- ✅ 事务范围尽量小
- ✅ 避免在事务中调用外部服务
- ✅ 快速失败原则

---

## 测试用例

### 测试场景1: 正常提现

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 50000
}
```

**前置条件**:
- 账户余额 >= 50000分
- 支付密码正确

**预期结果**:
- 返回提现记录ID
- 账户余额减少50000分
- 提现记录状态为1（待审核）

---

### 测试场景2: 支付密码错误

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "wrongpassword",
    "withdrawalMoney": 50000
}
```

**预期结果**:
```json
{
    "code": 500,
    "msg": "支付密码错误"
}
```

---

### 测试场景3: 余额不足

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 1000000
}
```

**前置条件**:
- 账户余额 < 1000000分

**预期结果**:
```json
{
    "code": 500,
    "msg": "余额不足"
}
```

---

### 测试场景4: 金额过小

**请求**:
```http
POST /incomeManage/cashOut
Content-Type: application/json

{
    "payPassword": "222222",
    "withdrawalMoney": 5
}
```

**预期结果**:
```json
{
    "code": 500,
    "msg": "金额不能小于0.1元"
}
```

---

### 测试场景5: 并发提现

**测试方法**:
- 同时发起多个提现请求
- 总金额超过账户余额

**预期结果**:
- 只有部分请求成功
- 成功的请求总金额不超过账户余额
- 数据一致性得到保证

---

## 注意事项

### 1. 提现金额限制

- ⚠️ **最小金额**: 0.1元（10分）
- ⚠️ **最大金额**: 账户余额
- ⚠️ **单位**: 必须使用分（Integer）

### 2. 支付密码

- ⚠️ **加密方式**: MD5
- ⚠️ **验证**: 必须与数据库中的密码完全一致
- ⚠️ **安全**: 不在日志中记录密码

### 3. 审核流程

- ⚠️ Web端采用人工审核模式
- ⚠️ 提现申请提交后立即扣减余额
- ⚠️ 审核不通过需退还余额

### 4. 事务回滚

- ⚠️ 任何步骤失败都会回滚所有操作
- ⚠️ 账户余额和提现记录保持一致
- ⚠️ 建议配置事务超时时间

### 5. 与App端差异

- ⚠️ App端直接调用支付宝API
- ⚠️ Web端创建待审核记录
- ⚠️ 业务逻辑相同，但审核流程不同

---

## 迁移说明

### 原接口（app端）

- **服务**: `alien-store`
- **路径**: `/alienStore/storeIncomeDetailsRecord/cashOut`
- **Controller**: `StoreIncomeDetailsRecordController`
- **Service**: `StoreIncomeDetailsRecordService.cashOut()`
- **特点**: 直接调用支付宝API实时转账

### 新接口（web端）

- **服务**: `alien-store-platform`
- **路径**: `/incomeManage/cashOut`
- **Controller**: `IncomeManageController`
- **Service**: `IncomeManageService.cashOut()`
- **特点**: 创建待审核记录，人工审核后打款

### 差异说明

| 项目 | app端 | web端 | 说明 |
|------|-------|-------|------|
| 接口路径 | `/cashOut` | `/cashOut` | 保持一致 |
| 请求方式 | GET | POST | POST更符合规范 |
| 请求参数 | @RequestParam | @RequestBody(DTO) | 使用DTO更规范 |
| storeId | 请求参数传递 | JWT Token获取 | 更安全 |
| 打款方式 | 实时调用支付宝API | 人工审核后打款 | Web端更安全 |
| 提现状态 | 1-待审核, 4-已打款 | 1-待审核 | Web端初始状态固定为1 |
| 登录验证 | 无 | ✅ 有（@LoginRequired） | 增加登录验证 |
| 事务管理 | ✅ | ✅ | 都使用@Transactional |
| 日志记录 | 基础 | 详细 | 增强日志记录 |

### 复用的核心组件

1. **Mapper**: 
   - `StoreUserMapper`
   - `StoreCashOutRecordMapper`
2. **Entity**: 
   - `StoreUser`
   - `StoreCashOutRecord`
3. **Util**: `DateUtils`

---

## 常见问题

### Q1: 为什么Web端不直接调用支付宝API？

**答案**: Web端采用人工审核模式，更安全可控。管理员可以审核每笔提现申请，避免风险。

---

### Q2: 提现申请提交后能否取消？

**答案**: 可以。如果提现状态为"待审核"，管理员可以拒绝提现申请，系统会退还余额。

---

### Q3: 支付密码忘记了怎么办？

**答案**: 需要联系管理员重置支付密码，或通过"忘记密码"功能重置。

---

### Q4: 提现金额为什么使用分而不是元？

**答案**: 使用分（Integer）可以避免浮点数精度问题，确保金额计算准确。

---

### Q5: 并发提现会有问题吗？

**答案**: 使用了事务保证数据一致性。高并发场景建议使用分布式锁进一步保证。

---

### Q6: 提现失败余额会退还吗？

**答案**: 
- 验证失败（密码错误、余额不足）：不扣减余额
- 创建记录失败：事务回滚，余额不变
- 审核不通过：需要管理员手动退还余额

---

## 更新日志

### 2025-11-25

**修改内容**:
- ✅ 修改请求参数为 DTO 形式（`CashOutDTO`）
- ✅ 使用 `@RequestBody` 接收 JSON 请求体
- ✅ 移除 `@ApiImplicitParams` 注解
- ✅ `storeId` 从 JWT Token 中自动获取

**涉及文件**:
- `IncomeManageController.java` - 更新
- `CashOutDTO.java` - 新建
- `23-提现申请接口.md` - 更新文档

---

### 2025-11-19

**新增接口**:
- ✅ `POST /incomeManage/cashOut` - 提现申请

**核心功能**:
- ✅ 验证支付密码
- ✅ 验证账户余额
- ✅ 验证提现金额
- ✅ 创建提现记录（待审核）
- ✅ 扣减账户余额
- ✅ 事务管理保证一致性
- ✅ 详细日志记录
- ✅ 完善异常处理

**涉及文件**:
- `IncomeManageController.java` - 更新
- `IncomeManageService.java` - 更新
- `IncomeManageServiceImpl.java` - 更新

**代码质量**:
- ✅ Linter检查：无错误
- ✅ 日志记录：详细
- ✅ 异常处理：完善
- ✅ 代码注释：完整
- ✅ 事务管理：严格

**与原接口差异**:
- ✅ 请求方式改为POST
- ✅ 采用人工审核模式
- ✅ 不直接调用支付宝API
- ✅ 业务逻辑保持一致

**开发人员**: ssk

---

**文档版本**: v1.0  
**最后更新**: 2025-11-19  
**维护人员**: ssk





