28-快速提现申请接口.md 24 KB

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

模块概述

本模块提供商户快速提现功能,与普通提现(需审核)不同,此接口创建的提现记录直接标记为"已通过"状态(paymentStatus=3),无需人工审核,与app端 applyCashOut 接口保持一致。


接口信息

快速提现申请(免审核)

接口详情

  • 接口名称: 快速提现申请(免审核)
  • 接口路径: POST /incomeManage/applyFastCashOut
  • 请求方式: POST
  • 接口描述: 为当前登录的商户提交快速提现申请,直接创建为"已通过"状态,无需审核流程
  • 登录验证: ✅ 需要(通过 JWT token 自动获取storeId)

与普通提现的区别

项目 普通提现 (/cashOut) 快速提现 (/applyFastCashOut)
提现状态 paymentStatus = 1(待审核) paymentStatus = 3(已通过)
审核流程 需要人工审核 免审核,直接通过
账户扣款 提交时扣款 提交时不扣款
适用场景 Web端常规提现 与app端保持一致的快速提现
后续流程 需管理员审核后打款 可直接进入打款流程

请求参数

请求体(Request Body)

使用 CashOutDTO 对象作为请求体(JSON格式):

参数名 类型 必填 说明 示例值
payPassword String 支付密码 123456
withdrawalMoney Integer 提现金额(单位:分) 50000

参数说明

payPassword(支付密码)

  • 用途: 验证用户身份
  • 格式: 用户设置的支付密码
  • 验证: 必须与数据库中的支付密码匹配
  • 安全: 建议前端加密后传输

withdrawalMoney(提现金额)

  • 单位: 分(1元 = 100分)
  • 最小值: 10分(0.1元)
  • 最大值: 不能超过账户余额
  • 示例:
    • 提现100元 → withdrawalMoney = 10000
    • 提现50元 → withdrawalMoney = 5000
    • 提现0.1元 → withdrawalMoney = 10

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


请求示例

示例1: 提现500元

POST /incomeManage/applyFastCashOut
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN

{
    "payPassword": "123456",
    "withdrawalMoney": 50000
}
curl -X POST "http://localhost:8080/incomeManage/applyFastCashOut" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payPassword": "123456",
    "withdrawalMoney": 50000
  }'

示例2: 提现全部余额

假设账户余额为1000元(100000分):

POST /incomeManage/applyFastCashOut
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN

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

响应参数

成功响应

{
    "code": 200,
    "success": true,
    "data": {
        "id": 1001,
        "storeId": 103,
        "storeUserId": 114,
        "money": 50000,
        "commission": null,
        "cashOutType": 0,
        "paymentStatus": 3,
        "orderNo": null,
        "aliOrderNo": null,
        "paymentDate": null,
        "payDate": null,
        "failReason": null,
        "incomeStartTime": "2025-11-20 14:30:00",
        "incomeEndTime": "2025-11-20 14:30:00",
        "deleteFlag": 0,
        "createdTime": "2025-11-20 14:30:00",
        "updatedTime": "2025-11-20 14:30:00"
    },
    "msg": "操作成功"
}

响应字段说明

外层字段

字段名 类型 说明
code Integer 状态码(200-成功)
success Boolean 是否成功
data Object 提现记录对象(StoreCashOutRecord)
msg String 提示信息

data 字段(StoreCashOutRecord)

字段名 类型 说明
id Long 提现记录ID
storeId Integer 门店ID
storeUserId Integer 用户ID
money Integer 提现金额(单位:分)
commission Integer 手续费(单位:分,可为null)
cashOutType Integer 提现类型(0-全部提现)
paymentStatus Integer ⚠️ 提现状态(3-已通过)
orderNo String 提现订单号(初始为null)
aliOrderNo String 支付宝订单号(初始为null)
paymentDate Date 申请时间(初始为null)
payDate Date 打款时间(初始为null)
failReason String 失败原因(初始为null)
incomeStartTime Date 收入开始时间
incomeEndTime Date 收入结束时间
deleteFlag Integer 删除标识(0-未删除)
createdTime Date 创建时间
updatedTime Date 更新时间

重要说明:

  • 🔔 paymentStatus = 3 表示"已通过",区别于普通提现的"待审核"
  • 🔔 订单号、支付宝订单号等字段需要在打款时填充

失败响应

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. 未登录或token无效

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

5. 系统异常

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

业务逻辑说明

快速提现流程

┌─────────────────────────────────────────────┐
│  1. 接收请求(从DTO获取参数)                 │
│  - payPassword (必填,从DTO)                 │
│  - withdrawalMoney (必填,单位:分,从DTO)   │
│  - JWT token (自动获取storeId)               │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  2. 从 JWT token 中解析门店ID                 │
│  storeId = LoginUserUtil.getCurrentStoreId()│
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  3. 验证支付密码                              │
│  SELECT * FROM store_user                    │
│  WHERE store_id = ? AND pay_password = ?    │
│  IF user == null                             │
│    返回错误: "支付密码错误"                   │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  4. 验证账户余额                              │
│  IF user.money <= withdrawalMoney            │
│    返回错误: "余额不足"                       │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  5. 验证提现金额                              │
│  amountInYuan = withdrawalMoney ÷ 100        │
│  IF amountInYuan < 0.1                       │
│    返回错误: "金额不能小于0.1元"              │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  6. 创建提现记录                              │
│  StoreCashOutRecord record = new ...         │
│  record.paymentStatus = 3  // 已通过         │
│  INSERT INTO store_cash_out_record           │
└─────────────────┬───────────────────────────┘
                  ↓
┌─────────────────────────────────────────────┐
│  7. 返回结果                                  │
│  返回 StoreCashOutRecord 对象                │
└─────────────────────────────────────────────┘

与原接口的对比

原接口(app端)

@GetMapping("/applyCashOut")
public R applyCashOut(Integer storeId, String payPassword, Integer withdrawalMoney)
  • 使用 GET 请求
  • 需要手动传递 storeId
  • URL: /storeIncomeDetailsRecord/applyCashOut?storeId=103&payPassword=123456&withdrawalMoney=50000

新接口(web端)

@PostMapping("/applyFastCashOut")
public R<?> applyFastCashOut(@RequestBody CashOutDTO cashOutDTO)
  • 使用 POST 请求(更安全)
  • 从 JWT token 自动获取 storeId
  • 使用 DTO 接收 JSON 请求体
  • URL: /incomeManage/applyFastCashOut

核心逻辑一致性:

  • ✅ 支付密码验证逻辑相同
  • ✅ 余额验证逻辑相同(money <= withdrawalMoney
  • ✅ 最小金额验证相同(0.1元)
  • ✅ 提现状态相同(paymentStatus = 3)
  • ✅ 返回数据格式相同(StoreCashOutRecord对象)

数据库操作

查询用户SQL

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

插入提现记录SQL

INSERT INTO store_cash_out_record (
    store_id, 
    money, 
    cash_out_type, 
    payment_status, 
    delete_flag, 
    income_start_time, 
    income_end_time, 
    store_user_id,
    created_time,
    updated_time
) VALUES (
    ?,      -- storeId
    ?,      -- withdrawalMoney
    0,      -- cashOutType (0-全部提现)
    3,      -- paymentStatus (3-已通过)
    0,      -- deleteFlag
    NOW(),  -- incomeStartTime
    NOW(),  -- incomeEndTime
    ?,      -- storeUserId
    NOW(),  -- createdTime
    NOW()   -- updatedTime
)

业务场景

场景 1: 正常快速提现

前置条件:

  • 账户余额: 1000元(100000分)
  • 支付密码: 123456

请求:

POST /incomeManage/applyFastCashOut
Content-Type: application/json
Authorization: Bearer VALID_TOKEN

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

响应:

{
    "code": 200,
    "success": true,
    "data": {
        "id": 1001,
        "storeId": 103,
        "money": 50000,
        "paymentStatus": 3,
        ...
    },
    "msg": "操作成功"
}

数据库变化:

  • ✅ 新增一条提现记录(paymentStatus=3)
  • ❌ 账户余额不变(与普通提现不同)

场景 2: 支付密码错误

请求:

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

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

响应:

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

场景 3: 余额不足

前置条件: 账户余额 100元(10000分)

请求:

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

{
    "payPassword": "123456",
    "withdrawalMoney": 20000
}

响应:

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

场景 4: 提现金额过小

请求:

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

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

响应:

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

前端集成示例

Vue.js 示例

export default {
    data() {
        return {
            payPassword: '',
            withdrawalMoney: '',  // 用户输入的元金额
            loading: false
        };
    },
    methods: {
        async applyFastCashOut() {
            // 1. 验证输入
            if (!this.payPassword) {
                this.$message.error('请输入支付密码');
                return;
            }
            
            if (!this.withdrawalMoney || this.withdrawalMoney < 0.1) {
                this.$message.error('提现金额不能小于0.1元');
                return;
            }
            
            // 2. 转换金额单位(元转分)
            const withdrawalMoneyInCents = Math.floor(this.withdrawalMoney * 100);
            
            // 3. 发送请求(使用JSON Body)
            this.loading = true;
            try {
                const response = await this.$axios.post('/incomeManage/applyFastCashOut', {
                    payPassword: this.payPassword,
                    withdrawalMoney: withdrawalMoneyInCents
                });
                
                if (response.data.success) {
                    const cashOutRecord = response.data.data;
                    this.$message.success('快速提现申请成功');
                    console.log('提现记录ID:', cashOutRecord.id);
                    console.log('提现状态:', cashOutRecord.paymentStatus);  // 3-已通过
                    
                    // 刷新列表或跳转
                    this.$router.push('/cashout-list');
                } else {
                    this.$message.error(response.data.msg);
                }
            } catch (error) {
                console.error('快速提现失败:', error);
                this.$message.error('快速提现失败,请稍后重试');
            } finally {
                this.loading = false;
            }
        }
    }
}

React 示例

import { useState } from 'react';
import axios from 'axios';

function FastCashOut() {
    const [payPassword, setPayPassword] = useState('');
    const [withdrawalMoney, setWithdrawalMoney] = useState('');
    const [loading, setLoading] = useState(false);
    
    const handleSubmit = async () => {
        // 1. 验证输入
        if (!payPassword) {
            alert('请输入支付密码');
            return;
        }
        
        const amount = parseFloat(withdrawalMoney);
        if (!amount || amount < 0.1) {
            alert('提现金额不能小于0.1元');
            return;
        }
        
        // 2. 转换金额单位(元转分)
        const withdrawalMoneyInCents = Math.floor(amount * 100);
        
        // 3. 发送请求(使用JSON Body)
        setLoading(true);
        try {
            const response = await axios.post('/incomeManage/applyFastCashOut', {
                payPassword,
                withdrawalMoney: withdrawalMoneyInCents
            });
            
            if (response.data.success) {
                const cashOutRecord = response.data.data;
                alert('快速提现申请成功');
                console.log('提现记录:', cashOutRecord);
                // 跳转或刷新
            } else {
                alert(response.data.msg);
            }
        } catch (error) {
            console.error('快速提现失败:', error);
            alert('快速提现失败,请稍后重试');
        } finally {
            setLoading(false);
        }
    };
    
    return (
        <div>
            <h2>快速提现(免审核)</h2>
            <div>
                <label>支付密码:</label>
                <input 
                    type="password" 
                    value={payPassword}
                    onChange={(e) => setPayPassword(e.target.value)}
                />
            </div>
            <div>
                <label>提现金额(元):</label>
                <input 
                    type="number" 
                    min="0.1"
                    step="0.01"
                    value={withdrawalMoney}
                    onChange={(e) => setWithdrawalMoney(e.target.value)}
                />
            </div>
            <button onClick={handleSubmit} disabled={loading}>
                {loading ? '提交中...' : '确认提现'}
            </button>
            <p className="tip">
                ⚠️ 快速提现无需审核,提现记录将直接标记为"已通过"状态
            </p>
        </div>
    );
}

测试用例

测试场景1: 正常快速提现

前置条件:

  • 账户余额: 1000元
  • 支付密码: 123456

请求:

POST /incomeManage/applyFastCashOut
Content-Type: application/json
Authorization: Bearer VALID_TOKEN

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

预期结果:

  • 返回成功
  • data.paymentStatus = 3(已通过)
  • 数据库新增提现记录
  • 账户余额不变

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

请求:

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

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

预期结果:

  • 返回失败
  • 提示 "支付密码错误"
  • 数据库无变化

测试场景3: 余额不足

前置条件: 账户余额 100元

请求:

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

{
    "payPassword": "123456",
    "withdrawalMoney": 20000
}

预期结果:

  • 返回失败
  • 提示 "余额不足"

测试场景4: 最小金额验证

请求:

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

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

预期结果:

  • 返回失败
  • 提示 "金额不能小于0.1元"

测试场景5: 边界值测试

请求:

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

{
    "payPassword": "123456",
    "withdrawalMoney": 10
}

预期结果:

  • 返回成功
  • 提现金额 0.1元(最小金额)

常见问题

Q1: 快速提现与普通提现有什么区别?

答案:

  • 快速提现 (/applyFastCashOut):
    • 提现记录直接标记为"已通过"(paymentStatus=3)
    • 无需人工审核
    • 账户余额不立即扣减
    • 与app端保持一致
  • 普通提现 (/cashOut):
    • 提现记录标记为"待审核"(paymentStatus=1)
    • 需要人工审核
    • 提交时立即扣减账户余额

Q2: 快速提现会立即扣款吗?

答案: 不会。快速提现只创建提现记录,不扣减账户余额。实际扣款在管理员审批打款时进行。


Q3: paymentStatus=3 是什么意思?

答案:

  • paymentStatus = 3 表示"已通过审核"
  • 区别于:
    • 1 - 待审核
    • 2 - 审核不通过
    • 4 - 已打款
    • 5 - 打款失败

Q4: 提现金额的单位是什么?

答案:

  • 接口参数使用 作为单位
  • 1元 = 100分
  • 例如:提现100元,传 withdrawalMoney=10000

Q5: 为什么使用 POST 而不是 GET?

答案:

  • POST 更安全,密码不会出现在 URL 中
  • 符合 RESTful 规范(数据修改操作)
  • 与 web 端其他接口保持一致

Q6: 快速提现失败会回滚吗?

答案:

  • 会。接口使用 @Transactional 注解
  • 如果创建提现记录失败,事务会回滚
  • 保证数据一致性

注意事项

1. 金额单位

  • ⚠️ 参数单位: 分(Integer)
  • ⚠️ 最小金额: 10分(0.1元)
  • ⚠️ 前端转换: 用户输入元,需转换为分

2. 提现状态

  • ⚠️ 状态值: paymentStatus = 3(已通过)
  • ⚠️ 与普通提现不同: 普通提现为 1(待审核)
  • ⚠️ 后续流程: 管理员可直接进入打款流程

3. 账户余额

  • ⚠️ 验证逻辑: money <= withdrawalMoney 会失败
  • ⚠️ 扣款时机: 创建记录时不扣款,打款时扣款
  • ⚠️ 与普通提现不同: 普通提现提交时立即扣款

4. 安全性

  • ⚠️ 密码保护: 建议前端加密传输
  • ⚠️ 日志脱敏: 日志中密码显示为 ***
  • ⚠️ Token验证: 必须提供有效的JWT token

5. 事务管理

  • ⚠️ 事务保护: 使用 @Transactional 保证原子性
  • ⚠️ 异常回滚: 任何异常都会导致事务回滚
  • ⚠️ 数据一致性: 保证提现记录创建的完整性

迁移说明

原接口(app端)

  • 服务: alien-store
  • 路径: /alienStore/storeIncomeDetailsRecord/applyCashOut
  • Controller: StoreIncomeDetailsRecordController
  • Service: StoreIncomeDetailsRecordService.applyCashOut()
  • 请求方式: GET
  • 参数: storeId, payPassword, withdrawalMoney

新接口(web端)

  • 服务: alien-store-platform
  • 路径: /incomeManage/applyFastCashOut
  • Controller: IncomeManageController
  • Service: IncomeManageService.applyFastCashOut()
  • 请求方式: POST
  • 参数: payPassword, withdrawalMoney(storeId从token获取)

差异说明

项目 app端 web端 说明
请求方式 GET POST POST更安全
请求参数 @RequestParam @RequestBody(DTO) 使用DTO更规范
storeId来源 参数 JWT token 防伪造
账户扣款 不扣款 不扣款 保持一致
提现状态 paymentStatus=3 paymentStatus=3 保持一致
返回值 StoreCashOutRecord StoreCashOutRecord 保持一致
事务管理 @Transactional @Transactional 保持一致

复用的核心逻辑

  1. ✅ 支付密码验证逻辑
  2. ✅ 余额验证逻辑(money <= withdrawalMoney
  3. ✅ 最小金额验证(0.1元)
  4. ✅ 提现状态设置(paymentStatus=3)
  5. ✅ 提现记录创建逻辑
  6. ✅ 返回数据格式

更新日志

2025-11-25

修改内容:

  • ✅ 修改请求参数为 DTO 形式(CashOutDTO
  • ✅ 使用 @RequestBody 接收 JSON 请求体
  • ✅ 移除 @ApiImplicitParams 注解
  • ✅ 更新所有请求示例为 JSON Body 格式
  • ✅ 更新前端集成示例

涉及文件:

  • IncomeManageController.java - 更新
  • 28-快速提现申请接口.md - 更新文档

2025-11-20

新增接口:

  • POST /incomeManage/applyFastCashOut - 快速提现申请(免审核)

核心功能:

  • ✅ 从 JWT token 自动获取 storeId
  • ✅ 支付密码验证
  • ✅ 账户余额验证(money <= withdrawalMoney
  • ✅ 最小金额验证(0.1元)
  • ✅ 创建提现记录(paymentStatus=3,已通过)
  • ✅ 不扣减账户余额(与app端保持一致)
  • ✅ 返回 StoreCashOutRecord 对象
  • ✅ 事务保护

与app端一致性:

  • ✅ 业务逻辑完全相同
  • ✅ 提现状态相同(paymentStatus=3)
  • ✅ 验证规则相同
  • ✅ 返回数据格式相同

涉及文件:

  • IncomeManageController.java - 新增 applyFastCashOut 接口
  • IncomeManageService.java - 新增方法定义
  • IncomeManageServiceImpl.java - 新增方法实现

代码质量:

  • ✅ Linter检查:无错误
  • ✅ 日志记录:详细
  • ✅ 异常处理:完善
  • ✅ 代码注释:完整
  • ✅ 事务管理:正确

开发人员: ssk


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