# Web端商户优惠券核销接口文档 ## 模块概述 本模块提供优惠券的核销功能,包括核销前效验和核销订单两个接口,支持代金券和团购券的核销处理。 --- ## 接口列表 1. [核销订单前效验](#接口一核销订单前效验) - 验证券是否可用 2. [核销订单](#接口二核销订单) - 实际执行核销操作 --- ## 接口一:核销订单前效验 ### 接口信息 - **接口名称**:核销订单前效验 - **接口路径**:`GET /couponManage/verifyOrder` - **请求方式**:GET - **接口描述**:在核销订单前进行效验,验证券是否满足核销条件(有效期、不可用日期、使用时间段等) ### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | orderCode | String | 是 | 劵code(券码) | ### 请求示例 ```http GET /couponManage/verifyOrder?orderCode=ABC123456789 ``` ### 响应参数 #### 成功响应(效验通过) ```json { "code": 200, "success": true, "data": "效验通过", "msg": "效验通过" } ``` #### 失败响应示例 ```json { "code": 500, "success": false, "data": null, "msg": "该劵不在有效期内" } ``` ### 业务规则 详见**核销前效验规则**章节。 --- ## 接口二:核销订单 ### 接口信息 - **接口名称**:核销订单 - **接口路径**:`GET /couponManage/verifyCoupon` - **请求方式**:GET - **接口描述**:执行券的核销操作,更新券状态、计算收入、更新店铺余额 ### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | storeId | String | 是 | 门店ID | | quanCode | String | 是 | 券码 | ### 请求示例 ```http GET /couponManage/verifyCoupon?storeId=102&quanCode=ABC123456789 ``` ### 响应参数 #### 成功响应(核销成功) ```json { "code": 200, "success": true, "data": { "code": "true", "message": "核销成功" } } ``` #### 失败响应示例 ##### 1. 重复核销 ```json { "code": 200, "success": true, "data": { "code": "false", "message": "请勿重复核销" } } ``` ##### 2. 券不存在或状态不正确 ```json { "code": 200, "success": true, "data": { "code": "false", "message": "核销失败" } } ``` ##### 3. 订单不存在 ```json { "code": 200, "success": true, "data": { "code": "false", "message": "订单不存在" } } ``` ##### 4. 门店不存在 ```json { "code": 200, "success": true, "data": { "code": "false", "message": "门店不存在" } } ``` --- ## 核销业务流程 ### 完整核销流程图 ``` ┌─────────────────────────────────────────────┐ │ 客户端调用核销接口 │ │ /couponManage/verifyCoupon │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 1. Redis分布式锁 │ │ 检查 coupon:use:{quanCode} 是否存在 │ │ 存在 → 返回"请勿重复核销" │ │ 不存在 → 设置锁并继续 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 2. 查询订单券中间表 │ │ - 根据券码查询 │ │ - 状态必须为:待使用(1) 或 退款失败(8) │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 3. 更新订单券状态 │ │ - status → 已使用(2) │ │ - usedTime → 当前时间 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 4. 查询用户订单信息 │ │ - 根据 orderId 查询 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 5. 检查平台优惠券 │ │ - 查询 quanId │ │ - 如果 type=3,标记为平台券 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 6-7. 查询订单下所有券 │ │ - 检查是否全部已核销 │ │ - 筛选未完成的券 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 8. 订单完成判断 │ │ 如果所有券都已核销 或 无未完成券 │ │ → 更新订单状态为已完成(3) │ │ → 设置完成时间 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 9. 查询门店信息 │ │ - 获取抽成比例 commissionRate │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 10. 计算抽成和收入 │ │ 抽成 = 券价 × 100 × 抽成比例 │ │ 收入 = 券价 × 100 - 抽成 │ │ (如有平台券,需加上平均优惠价格) │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 11. 插入收入明细记录 │ │ - storeId, userOrderId │ │ - incomeType, businessId │ │ - commission, money │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 12. 更新店铺账户余额 │ │ - store_user.money += 收入金额 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 13. 返回核销成功 │ │ { code: "true", message: "核销成功" } │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 14. 释放分布式锁 (finally) │ │ 删除 coupon:use:{quanCode} │ └─────────────────────────────────────────────┘ ``` --- ## 核销前效验规则 ### 效验内容 核销前效验(`verifyOrder`)会根据券类型进行不同的验证: #### 代金券验证(couponType=1) 1. **有效期验证** - 指定天数:支付时间 + 天数 > 当前时间 - 指定时间段:开始时间 ≤ 当前时间 ≤ 结束时间 2. **不可用日期验证** - 限制星期:检查当前是否为不可用星期 - 限制节日:检查当前是否在节日范围 - 自定义日期:检查当前是否在自定义不可用日期 3. **使用时间段验证** - 检查当前小时是否在允许使用的时间段内 - 支持跨天时间段(如22点-3点) 4. **单次核销数量验证** - 检查今日已核销次数是否达到上限 #### 团购券验证(couponType=2) 1. **有效期验证** - 指定天数:支付时间 + 天数 > 当前时间 - 指定时间段:开始日期 ≤ 当前日期 ≤ 结束日期 2. **不可用日期验证** - 限制星期:检查当前是否为不可用星期 - 限制节日:检查当前是否在节日范围 - 自定义日期:检查当前是否在自定义不可用日期 --- ## 金额计算规则 ### 基础计算公式 #### 1. 抽成金额计算 ``` 抽成比例(小数) = storeInfo.commissionRate ÷ 100 抽成金额(分) = 券价(元) × 100 × 抽成比例 ``` **示例**: ``` 券价 = 100元 抽成比例 = 4% 抽成金额 = 100 × 100 × 0.04 = 400分(4元) ``` #### 2. 实际收入计算 ##### 普通订单(无平台优惠券) ``` 实际收入(分) = 券价(元) × 100 - 抽成金额(分) ``` **示例**: ``` 券价 = 100元 抽成 = 400分 实际收入 = 100 × 100 - 400 = 9600分(96元) ``` ##### 使用平台优惠券的订单 ``` 实际收入(分) = (券价(元) + 平均优惠券价格(元)) × 100 - 抽成金额(分) ``` **示例**: ``` 券价 = 100元 平台优惠 = 10元 抽成 = 400分 实际收入 = (100 + 10) × 100 - 400 = 10600分(106元) ``` ### 金额单位说明 | 存储单位 | 显示单位 | 转换关系 | |----------|----------|----------| | 分(数据库) | 元(前端) | 1元 = 100分 | **注意**: - 所有金额计算使用 `BigDecimal`,精度为2位小数 - 金额存储到数据库时转换为整数(分) - 显示给用户时转换为元(除以100) --- ## 并发控制 ### Redis分布式锁机制 #### 锁的生命周期 ``` ┌─────────────────────────────────────────────┐ │ 1. 检查锁 │ │ getString("coupon:use:{quanCode}") │ │ 如果返回值不为空 → 锁已存在 → 拒绝核销 │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 2. 设置锁 │ │ setListRight("coupon:use:{quanCode}", │ │ quanCode) │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 3. 执行核销业务逻辑 │ │ (数据库操作、金额计算等) │ └─────────────────┬───────────────────────────┘ ↓ ┌─────────────────────────────────────────────┐ │ 4. 释放锁 (finally块) │ │ delete("coupon:use:{quanCode}") │ │ 确保无论成功失败都会释放 │ └─────────────────────────────────────────────┘ ``` #### 锁的作用 - **防止重复核销**:同一券码在处理过程中不允许再次核销 - **并发安全**:多个核销请求同时到达时,只有一个能获取锁 - **自动释放**:finally块确保锁一定会被释放 --- ## 数据库操作 ### 涉及的表 #### 1. order_coupon_middle(订单券中间表) **查询操作**: ```sql SELECT * FROM order_coupon_middle WHERE coupon_code = ? AND status IN (1, 8) -- 待使用或退款失败 ``` **更新操作**: ```sql UPDATE order_coupon_middle SET status = 2, -- 已使用 used_time = NOW() WHERE id = ? ``` #### 2. life_user_order(用户订单表) **查询操作**: ```sql SELECT * FROM life_user_order WHERE id = ? ``` **更新操作**(订单完成时): ```sql UPDATE life_user_order SET status = 3, -- 已完成 finish_time = NOW(), created_time = NOW() WHERE id = ? ``` #### 3. life_discount_coupon(优惠券表) **查询操作**(检查平台券): ```sql SELECT * FROM life_discount_coupon WHERE id = ? AND type = 3 -- 平台券 ``` #### 4. store_info(门店信息表) **查询操作**: ```sql SELECT * FROM store_info WHERE id = ? ``` #### 5. store_income_details_record(收入明细表) **插入操作**: ```sql INSERT INTO store_income_details_record (store_id, user_order_id, income_type, business_id, commission, money) VALUES (?, ?, ?, ?, ?, ?) ``` #### 6. store_user(门店用户表) **更新操作**: ```sql UPDATE store_user SET money = money + ? WHERE store_id = ? AND delete_flag = 0 ``` --- ## 订单状态说明 ### order_coupon_middle 状态 | Status | 枚举常量 | 说明 | |--------|----------|------| | 1 | WAIT_USE | 待使用 | | 2 | USED | 已使用(核销完成) | | 3 | REFUND | 已退款 | | 8 | REFUND_FAILED | 退款失败 | ### life_user_order 状态 | Status | 枚举常量 | 说明 | |--------|----------|------| | 1 | WAIT_PAY | 待支付 | | 2 | WAIT_USE | 待使用 | | 3 | COMPLETE | 已完成 | | 4 | REFUND | 已退款 | | 5 | CANCEL | 已取消 | | 6 | EXPIRE | 已过期 | --- ## 收入明细记录 ### StoreIncomeDetailsRecord 字段说明 | 字段名 | 类型 | 说明 | 示例 | |--------|------|------|------| | storeId | Integer | 门店ID | 102 | | userOrderId | Integer | 订单券ID | 12345 | | incomeType | Integer | 收入类型(券类型) | 1-代金券, 2-团购 | | businessId | Integer | 业务ID(券ID) | 67890 | | commission | Integer | 抽成金额(分) | 400 | | money | Integer | 实际收入(分) | 9600 | --- ## 事务管理 ### 事务范围 核销订单接口使用 `@Transactional` 注解,确保以下操作的原子性: 1. 更新订单券状态 2. 更新用户订单状态(如需要) 3. 插入收入明细记录 4. 更新店铺账户余额 ### 回滚机制 ```java @Transactional(rollbackFor = Exception.class) public Map verifyCoupon(String storeId, String quanCode) { // 任何异常都会触发回滚 } ``` --- ## 异常处理 ### 异常场景 | 场景 | 返回code | 返回message | 处理方式 | |------|----------|-------------|----------| | 重复核销 | "false" | "请勿重复核销" | 检测到锁存在 | | 券不存在 | "false" | "核销失败" | 查询结果为空 | | 订单不存在 | "false" | "订单不存在" | 订单查询失败 | | 门店不存在 | "false" | "门店不存在" | 门店查询失败 | | 系统异常 | "false" | "核销失败:{异常信息}" | 捕获Exception | --- ## 迁移说明 ### 原接口(app端) - **路径**:`/alienStore/coupon/newVerify` - **Controller**:`LifeCouponController` - **Service**:`LifeCouponService.newCouponVerify()` ### 新接口(web端) - **路径**:`/couponManage/verifyCoupon` - **Controller**:`CouponManageController` - **Service**:`CouponManageService.verifyCoupon()` ### 差异说明 | 项目 | app端 | web端 | 说明 | |------|-------|-------|------| | 好友券发放 | ✅ 支持 | ⚠️ 暂不支持 | 需通过Feign调用实现 | | 其他逻辑 | ✅ | ✅ | 完全一致 | --- ## 更新日志 ### 2025-11-14 **新增接口**: - ✅ `GET /couponManage/verifyOrder` - 核销订单前效验 - ✅ `GET /couponManage/verifyCoupon` - 核销订单 **核心功能**: - ✅ Redis分布式锁防止重复核销 - ✅ 券状态更新(待使用→已使用) - ✅ 订单状态更新(所有券核销完成→订单完成) - ✅ 平台优惠券识别和处理 - ✅ 抽成计算(从门店信息获取比例) - ✅ 收入记录插入 - ✅ 店铺余额更新 - ✅ 完整的事务管理 - ✅ 详细的日志记录 - ✅ 异常处理和回滚机制 **涉及文件**: - `CouponManageController.java` - 更新 - `CouponManageService.java` - 更新 - `CouponManageServiceImpl.java` - 更新(新增200行) --- **文档版本**:v1.0 **最后更新**:2025-11-14 **维护人员**:ssk