# 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