feat(order): 分离订单退款功能到独立接口并优化水票统计

- 将订单退款逻辑从update方法中分离到独立的refund接口
- 添加退款相关操作权限控制和参数验证
- 实现申请退款和同意退款两种状态的分别处理
- 新增水票总数统计功能,包括service、mapper和controller层实现
- 修改佣金注释文本从"第3级佣金"为"分润收入"
- 优化订单更新逻辑,禁止通过普通更新接口进行退款操作
This commit is contained in:
2026-02-04 17:38:00 +08:00
parent 51d3a029cc
commit c5da6f371b
7 changed files with 165 additions and 110 deletions

View File

@@ -1,13 +1,11 @@
package com.gxwebsoft.glt.controller;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.param.GltUserTicketParam;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.annotation.OperationLog;
import io.swagger.v3.oas.annotations.Operation;
@@ -16,7 +14,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 我的水票控制器
@@ -38,6 +38,20 @@ public class GltUserTicketController extends BaseController {
return success(gltUserTicketService.pageRel(param));
}
@Operation(summary = "我的水票总数")
@GetMapping("/my-total")
public ApiResult<?> myTotal() {
Integer userId = getLoginUserId();
if (userId == null) {
return fail("未登录");
}
Integer totalQty = gltUserTicketService.sumTotalQtyByUserId(userId);
Map<String, Object> data = new HashMap<>();
data.put("userId", userId);
data.put("totalQty", totalQty);
return success(data);
}
@PreAuthorize("hasAuthority('glt:gltUserTicket:list')")
@Operation(summary = "查询全部我的水票")
@GetMapping()

View File

@@ -34,4 +34,12 @@ public interface GltUserTicketMapper extends BaseMapper<GltUserTicket> {
*/
List<GltUserTicket> selectListRel(@Param("param") GltUserTicketParam param);
/**
* 统计用户水票总数量sum(total_qty)
*
* @param userId 用户ID
* @return 总数量
*/
Integer sumTotalQtyByUserId(@Param("userId") Integer userId);
}

View File

@@ -86,4 +86,12 @@
<include refid="selectSql"></include>
</select>
<!-- 我的水票总数sum(total_qty) -->
<select id="sumTotalQtyByUserId" resultType="java.lang.Integer">
SELECT IFNULL(SUM(total_qty), 0)
FROM glt_user_ticket
WHERE user_id = #{userId}
AND deleted = 0
</select>
</mapper>

View File

@@ -39,4 +39,12 @@ public interface GltUserTicketService extends IService<GltUserTicket> {
*/
GltUserTicket getByIdRel(Integer id);
/**
* 统计指定用户水票总数量sum(total_qty)
*
* @param userId 用户ID
* @return 总数量无记录返回0
*/
Integer sumTotalQtyByUserId(Integer userId);
}

View File

@@ -44,4 +44,10 @@ public class GltUserTicketServiceImpl extends ServiceImpl<GltUserTicketMapper, G
return param.getOne(baseMapper.selectListRel(param));
}
@Override
public Integer sumTotalQtyByUserId(Integer userId) {
Integer totalQty = baseMapper.sumTotalQtyByUserId(userId);
return totalQty == null ? 0 : totalQty;
}
}

View File

@@ -261,18 +261,17 @@ public class ShopOrderController extends BaseController {
@Operation(summary = "修改订单")
@PutMapping()
public ApiResult<?> update(@RequestBody ShopOrder shopOrder) throws Exception {
// 1. 验证订单是否可以退款
if (shopOrder == null) {
return fail("订单不存在");
return fail("订单不存在");
}
// 退款相关操作单独走退款接口,便于做财务权限隔离
if (Objects.equals(shopOrder.getOrderStatus(), 4) || Objects.equals(shopOrder.getOrderStatus(), 6)) {
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
}
ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId());
if (shopOrderNow == null) {
return fail("订单不存在");
}
// 申请退款
if (Objects.equals(shopOrder.getOrderStatus(), 4)) {
shopOrder.setRefundApplyTime(LocalDateTime.now());
}
// 发货状态从“未发货(10)”变更为“已发货(20)”时,记录发货信息
if (Objects.equals(shopOrderNow.getDeliveryStatus(), 10) && Objects.equals(shopOrder.getDeliveryStatus(), 20)) {
ShopOrderDelivery shopOrderDelivery = new ShopOrderDelivery();
@@ -287,113 +286,118 @@ public class ShopOrderController extends BaseController {
shopOrderDeliveryService.setExpress(getLoginUser(), shopOrderDelivery, shopOrder);
}
// 退款操作
if (Objects.equals(shopOrder.getOrderStatus(), 6)) {
// 当订单状态更改为6已退款执行退款操作
try {
// 检查订单是否已支付
if (!Boolean.TRUE.equals(shopOrderNow.getPayStatus())) {
return fail("订单未支付,无法退款");
}
// 检查是否已经退款过了
if (StrUtil.isNotBlank(shopOrderNow.getRefundOrder())) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), shopOrderNow.getRefundOrder());
return fail("订单已退款,请勿重复操作");
}
// 2. 生成退款单号
String refundNo = "RF" + IdUtil.getSnowflakeNextId();
// 3. 确定退款金额(默认全额退款)
BigDecimal refundAmount = shopOrder.getRefundMoney();
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
// 如果没有指定退款金额,使用订单实付金额
refundAmount = shopOrderNow.getTotalPrice();
}
// 验证退款金额不能大于订单金额
if (refundAmount.compareTo(shopOrderNow.getTotalPrice()) > 0) {
return fail("退款金额不能大于订单金额");
}
// 4. 确定支付类型默认为微信Native支付
PaymentType paymentType = PaymentType.WECHAT_NATIVE;
if (shopOrderNow.getPayType() != null) {
// 根据订单的支付类型确定
// 支付方式0余额支付1微信支付2支付宝支付3银联支付4现金支付5POS机支付6免费7积分支付
paymentType = PaymentType.getByCode(shopOrderNow.getPayType());
// 如果是微信支付,需要根据微信支付子类型确定具体的支付方式
if (paymentType == PaymentType.WECHAT) {
// 目前统一使用WECHAT_NATIVE进行退款
paymentType = PaymentType.WECHAT_NATIVE;
}
}
// 5. 调用统一支付服务的退款接口
logger.info("开始处理订单退款 - 订单号: {}, 退款单号: {}, 退款金额: {}, 支付方式: {}",
shopOrderNow.getOrderNo(), refundNo, refundAmount, paymentType);
PaymentResponse refundResponse = paymentService.refund(
shopOrderNow.getOrderNo(), // 原订单号
refundNo, // 退款单号
paymentType, // 支付方式
shopOrderNow.getTotalPrice(), // 订单总金额
refundAmount, // 退款金额
shopOrder.getRefundReason() != null ? shopOrder.getRefundReason() : "用户申请退款", // 退款原因
shopOrderNow.getTenantId() // 租户ID
);
// 6. 处理退款结果
if (refundResponse.getSuccess()) {
// 退款成功,更新订单信息
shopOrder.setRefundOrder(refundNo);
shopOrder.setRefundMoney(refundAmount);
shopOrder.setRefundTime(LocalDateTime.now());
shopOrder.setOrderStatus(6); // 退款成功
// 根据退款状态决定订单状态
// 如果微信返回退款处理中则设置订单状态为5退款处理中
// 如果微信返回退款成功则保持状态为6退款成功
// if (refundResponse.getPaymentStatus() != null) {
// switch (refundResponse.getPaymentStatus()) {
// case REFUNDING:
// shopOrder.setOrderStatus(5); // 退款处理中
// logger.info("订单退款处理中,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// break;
// case REFUNDED:
// shopOrder.setOrderStatus(6); // 退款成功
// logger.info("订单退款成功,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// break;
// case REFUND_FAILED:
// logger.error("订单退款失败,订单号: {}, 退款单号: {}", shopOrderNow.getOrderNo(), refundNo);
// return fail("退款失败,请联系管理员");
// default:
// shopOrder.setOrderStatus(5); // 默认为退款处理中
// }
// }
logger.info("订单退款请求成功 - 订单号: {}, 退款单号: {}, 微信退款单号: {}",
shopOrderNow.getOrderNo(), refundNo, refundResponse.getTransactionId());
} else {
// 退款失败
logger.error("订单退款失败 - 订单号: {}, 错误: {}", shopOrderNow.getOrderNo(), refundResponse.getErrorMessage());
return fail("退款失败: " + refundResponse.getErrorMessage());
}
} catch (Exception e) {
logger.error("处理订单退款异常 - 订单号: {}, 错误: {}", shopOrderNow.getOrderNo(), e.getMessage(), e);
return fail("退款处理异常: " + e.getMessage());
}
}
if (shopOrderService.updateById(shopOrder)) {
return success("修改成功");
}
return fail("修改失败");
}
@PreAuthorize("hasAuthority('shop:shopOrder:refund')")
@Operation(summary = "订单退款操作(申请退款/同意退款)", description = "orderStatus=4 申请退款orderStatus=6 同意退款并发起原路退款")
@PutMapping("/refund")
public ApiResult<?> refund(@RequestBody ShopOrder req) {
if (req == null || req.getOrderId() == null || req.getOrderStatus() == null) {
return fail("orderId 和 orderStatus 不能为空");
}
if (!Objects.equals(req.getOrderStatus(), 4) && !Objects.equals(req.getOrderStatus(), 6)) {
return fail("orderStatus 仅支持 4(申请退款) 或 6(同意退款)");
}
ShopOrder current = shopOrderService.getById(req.getOrderId());
if (current == null) {
return fail("订单不存在");
}
// 申请退款:只记录申请时间/原因/金额(如有)
if (Objects.equals(req.getOrderStatus(), 4)) {
ShopOrder patch = new ShopOrder();
patch.setOrderId(req.getOrderId());
patch.setOrderStatus(4);
patch.setRefundApplyTime(LocalDateTime.now());
if (StrUtil.isNotBlank(req.getRefundReason())) {
patch.setRefundReason(req.getRefundReason());
}
if (req.getRefundMoney() != null) {
patch.setRefundMoney(req.getRefundMoney());
}
if (shopOrderService.updateById(patch)) {
return success("申请退款成功");
}
return fail("申请退款失败");
}
// 同意退款:发起原路退款并更新退款信息
try {
if (!Boolean.TRUE.equals(current.getPayStatus())) {
return fail("订单未支付,无法退款");
}
if (StrUtil.isNotBlank(current.getRefundOrder())) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", current.getOrderNo(), current.getRefundOrder());
return fail("订单已退款,请勿重复操作");
}
String refundNo = "RF" + IdUtil.getSnowflakeNextId();
BigDecimal refundAmount = req.getRefundMoney();
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
refundAmount = current.getTotalPrice();
}
if (refundAmount.compareTo(current.getTotalPrice()) > 0) {
return fail("退款金额不能大于订单金额");
}
PaymentType paymentType = PaymentType.WECHAT_NATIVE;
if (current.getPayType() != null) {
// 支付方式0余额支付1微信支付2支付宝支付3银联支付4现金支付5POS机支付6免费7积分支付
paymentType = PaymentType.getByCode(current.getPayType());
if (paymentType == PaymentType.WECHAT) {
paymentType = PaymentType.WECHAT_NATIVE;
}
}
logger.info("开始处理订单退款 - 订单号: {}, 退款单号: {}, 退款金额: {}, 支付方式: {}",
current.getOrderNo(), refundNo, refundAmount, paymentType);
PaymentResponse refundResponse = paymentService.refund(
current.getOrderNo(),
refundNo,
paymentType,
current.getTotalPrice(),
refundAmount,
StrUtil.isNotBlank(req.getRefundReason()) ? req.getRefundReason() : "用户申请退款",
current.getTenantId()
);
if (!Boolean.TRUE.equals(refundResponse.getSuccess())) {
logger.error("订单退款失败 - 订单号: {}, 错误: {}", current.getOrderNo(), refundResponse.getErrorMessage());
return fail("退款失败: " + refundResponse.getErrorMessage());
}
ShopOrder patch = new ShopOrder();
patch.setOrderId(req.getOrderId());
patch.setRefundOrder(refundNo);
patch.setRefundMoney(refundAmount);
patch.setRefundTime(LocalDateTime.now());
patch.setOrderStatus(6);
if (StrUtil.isNotBlank(req.getRefundReason())) {
patch.setRefundReason(req.getRefundReason());
}
if (!shopOrderService.updateById(patch)) {
logger.error("退款已成功但订单更新失败 - orderId={}, orderNo={}, refundNo={}", current.getOrderId(), current.getOrderNo(), refundNo);
return fail("退款成功,但订单状态更新失败,请联系管理员");
}
logger.info("订单退款请求成功 - 订单号: {}, 退款单号: {}, 微信退款单号: {}",
current.getOrderNo(), refundNo, refundResponse.getTransactionId());
return success("退款成功");
} catch (Exception e) {
logger.error("处理订单退款异常 - 订单号: {}, 错误: {}", current.getOrderNo(), e.getMessage(), e);
return fail("退款处理异常: " + e.getMessage());
}
}
@Operation(summary = "删除订单")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
@@ -415,6 +419,13 @@ public class ShopOrderController extends BaseController {
@Operation(summary = "批量修改订单")
@PutMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody BatchParam<ShopOrder> batchParam) {
if (batchParam != null && batchParam.getData() != null) {
Integer status = batchParam.getData().getOrderStatus();
// 退款相关操作单独走退款接口,避免绕过财务权限
if (Objects.equals(status, 4) || Objects.equals(status, 6)) {
return fail("退款相关操作请使用退款接口: PUT /api/shop/shop-order/refund");
}
}
if (batchParam.update(shopOrderService, "order_id")) {
return success("修改成功");
}

View File

@@ -234,7 +234,7 @@ public class DealerOrderSettlement10584Task {
thirdMoney,
order,
simpleDealerId,
buildCommissionComment("第3级佣金", commissionConfig.commissionType, commissionConfig.dealerThirdValue, goodsQty)
buildCommissionComment("分润收入", commissionConfig.commissionType, commissionConfig.dealerThirdValue, goodsQty)
);
return new DealerRefereeCommission(directDealerId, directMoney, simpleDealerId, simpleMoney, thirdDealerId, thirdMoney);