23-提现申请接口.md 20 KB

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 中自动获取,不需要在请求中传递。


请求示例

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

{
    "payPassword": "222222",
    "withdrawalMoney": 50000
}
curl -X POST "http://localhost:8080/incomeManage/cashOut" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payPassword": "222222",
    "withdrawalMoney": 50000
  }'
// 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));

响应参数

成功响应

{
    "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. 支付密码错误

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

2. 余额不足

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

3. 金额过小

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

4. 未登录或登录过期

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

5. 系统异常

{
    "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:

SELECT * FROM store_user
WHERE store_id = ?
  AND pay_password = ?

说明:

  • 验证支付密码
  • 获取账户余额和用户信息

更新SQL:

UPDATE store_user
SET money = money - ?
WHERE id = ?

说明:

  • 扣减账户余额
  • 使用事务保证数据一致性

2. store_cash_out_record(提现记录表)

插入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

转换代码

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

转换规则:

  • 使用 BigDecimal 进行精确计算
  • 除以100转换为元
  • 保留2位小数
  • 舍入模式:DOWN(向下取整)

业务场景

场景 1: 正常提现

前置条件:

  • 账户余额: 100000分(1000元)
  • 提现金额: 50000分(500元)
  • 支付密码: 正确

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

响应:

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

结果:

  • ✅ 创建提现记录
  • ✅ 账户余额变为50000分(500元)
  • ✅ 提现记录状态为"待审核"

场景 2: 支付密码错误

前置条件:

  • 账户余额: 100000分
  • 提现金额: 50000分
  • 支付密码: 错误

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

响应:

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

结果:

  • ❌ 提现失败
  • ❌ 账户余额不变
  • ❌ 不创建提现记录

场景 3: 余额不足

前置条件:

  • 账户余额: 30000分(300元)
  • 提现金额: 50000分(500元)
  • 支付密码: 正确

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

响应:

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

结果:

  • ❌ 提现失败
  • ❌ 账户余额不变
  • ❌ 不创建提现记录

场景 4: 金额过小

前置条件:

  • 账户余额: 100000分
  • 提现金额: 5分(0.05元,小于最低0.1元)
  • 支付密码: 正确

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

响应:

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

结果:

  • ❌ 提现失败
  • ❌ 账户余额不变
  • ❌ 不创建提现记录

场景 5: 全部提现

前置条件:

  • 账户余额: 100000分(1000元)
  • 提现金额: 100000分(全部余额)
  • 支付密码: 正确

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

响应:

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

结果:

  • ✅ 创建提现记录
  • ✅ 账户余额变为0分
  • ✅ 提现记录状态为"待审核"

事务管理

事务范围

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

@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. 数据库索引

确保以下字段有索引:

-- 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: 正常提现

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

前置条件:

  • 账户余额 >= 50000分
  • 支付密码正确

预期结果:

  • 返回提现记录ID
  • 账户余额减少50000分
  • 提现记录状态为1(待审核)

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

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

预期结果:

{
    "code": 500,
    "msg": "支付密码错误"
}

测试场景3: 余额不足

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

前置条件:

  • 账户余额 < 1000000分

预期结果:

{
    "code": 500,
    "msg": "余额不足"
}

测试场景4: 金额过小

请求:

POST /incomeManage/cashOut
Content-Type: application/json

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

预期结果:

{
    "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