diff --git a/server/like-admin/src/main/java/com/mdd/admin/controller/finance/FinanceRechargerController.java b/server/like-admin/src/main/java/com/mdd/admin/controller/finance/FinanceRechargerController.java index 09b9d105..a378bc27 100644 --- a/server/like-admin/src/main/java/com/mdd/admin/controller/finance/FinanceRechargerController.java +++ b/server/like-admin/src/main/java/com/mdd/admin/controller/finance/FinanceRechargerController.java @@ -1,18 +1,18 @@ package com.mdd.admin.controller.finance; +import com.mdd.admin.LikeAdminThreadLocal; import com.mdd.admin.service.IFinanceRechargerService; +import com.mdd.admin.validate.commons.IdValidate; import com.mdd.admin.validate.commons.PageValidate; import com.mdd.admin.validate.finance.FinanceRechargeSearchValidate; import com.mdd.admin.vo.finance.FinanceRechargeListVo; import com.mdd.common.core.AjaxResult; import com.mdd.common.core.PageResult; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiOperation; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -34,8 +34,19 @@ public class FinanceRechargerController { @PostMapping("/refund") @ApiOperation("发起退款") - public AjaxResult refund() { - iFinanceRechargerService.refund(); + public AjaxResult refund(@Validated @RequestBody IdValidate idValidate) { + Integer adminId = LikeAdminThreadLocal.getAdminId(); + + iFinanceRechargerService.refund(idValidate.getId(), adminId); + return AjaxResult.success(); + } + + @PostMapping("/refundAgain") + @ApiModelProperty("重新退款") + public AjaxResult refundAgain(@Validated @RequestBody IdValidate idValidate) { + Integer adminId = LikeAdminThreadLocal.getAdminId(); + + iFinanceRechargerService.refundAgain(idValidate.getId(), adminId); return AjaxResult.success(); } diff --git a/server/like-admin/src/main/java/com/mdd/admin/service/IFinanceRechargerService.java b/server/like-admin/src/main/java/com/mdd/admin/service/IFinanceRechargerService.java index 4be4c325..b5aebf88 100644 --- a/server/like-admin/src/main/java/com/mdd/admin/service/IFinanceRechargerService.java +++ b/server/like-admin/src/main/java/com/mdd/admin/service/IFinanceRechargerService.java @@ -20,6 +20,22 @@ public interface IFinanceRechargerService { */ PageResult list(PageValidate pageValidate, FinanceRechargeSearchValidate searchValidate); - void refund(); + /** + * 发起退款 + * + * @author fzr + * @param orderId 订单ID + * @param adminId 管理员ID + */ + void refund(Integer orderId, Integer adminId); + + /** + * 重新退款 + * + * @author fzr + * @param recordId 记录ID + * @param adminId 管理员ID + */ + void refundAgain(Integer recordId, Integer adminId); } diff --git a/server/like-admin/src/main/java/com/mdd/admin/service/impl/FinanceRechargerServiceImpl.java b/server/like-admin/src/main/java/com/mdd/admin/service/impl/FinanceRechargerServiceImpl.java index 50f462ec..02ea9bc9 100644 --- a/server/like-admin/src/main/java/com/mdd/admin/service/impl/FinanceRechargerServiceImpl.java +++ b/server/like-admin/src/main/java/com/mdd/admin/service/impl/FinanceRechargerServiceImpl.java @@ -1,5 +1,6 @@ package com.mdd.admin.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.github.yulichang.query.MPJQueryWrapper; @@ -10,12 +11,30 @@ import com.mdd.admin.vo.finance.FinanceRechargeListVo; import com.mdd.common.config.GlobalConfig; import com.mdd.common.core.PageResult; import com.mdd.common.entity.RechargeOrder; +import com.mdd.common.entity.RefundLog; +import com.mdd.common.entity.RefundRecord; +import com.mdd.common.entity.user.User; +import com.mdd.common.enums.LogMoneyEnum; import com.mdd.common.enums.PaymentEnum; +import com.mdd.common.enums.RefundEnum; +import com.mdd.common.exception.OperateException; import com.mdd.common.mapper.RechargeOrderMapper; +import com.mdd.common.mapper.RefundLogMapper; +import com.mdd.common.mapper.RefundRecordMapper; +import com.mdd.common.mapper.log.LogMoneyMapper; +import com.mdd.common.mapper.user.UserMapper; +import com.mdd.common.plugin.wechat.WxPayDriver; +import com.mdd.common.plugin.wechat.request.RefundRequestV3; +import com.mdd.common.util.AmountUtil; import com.mdd.common.util.StringUtils; import com.mdd.common.util.TimeUtils; import com.mdd.common.util.UrlUtils; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; import javax.annotation.Resource; @@ -28,6 +47,24 @@ public class FinanceRechargerServiceImpl implements IFinanceRechargerService { @Resource RechargeOrderMapper rechargeOrderMapper; + @Resource + UserMapper userMapper; + + @Resource + LogMoneyMapper logMoneyMapper; + + @Resource + RefundRecordMapper refundRecordMapper; + + @Resource + RefundLogMapper refundLogMapper; + + @Resource + DataSourceTransactionManager transactionManager ; + + @Resource + TransactionDefinition transactionDefinition; + /** * 充值记录 * @@ -79,10 +116,147 @@ public class FinanceRechargerServiceImpl implements IFinanceRechargerService { /** * 发起退款 + * + * @author fzr + * @param orderId 订单ID + * @param adminId 管理员ID */ @Override - public void refund() { + public void refund(Integer orderId, Integer adminId) { + RechargeOrder rechargeOrder = rechargeOrderMapper.selectById(orderId); + Assert.notNull(rechargeOrder, "充值订单不存在!"); + if (!rechargeOrder.getPayStatus().equals(PaymentEnum.OK_PAID.getCode())) { + throw new OperateException("当前订单不可退款!"); + } + + if (rechargeOrder.getRefundStatus().equals(1)) { + throw new OperateException("订单已发起退款,退款失败请到退款记录重新退款!"); + } + + User user = userMapper.selectById(rechargeOrder.getUserId()); + if (user.getMoney().compareTo(rechargeOrder.getOrderAmount()) < 0) { + throw new OperateException("退款失败:用户余额已不足退款金额!"); + } + + // 开启事务 + TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); + + RefundLog log = null; + try { + // 标记退款状态 + rechargeOrder.setRefundStatus(1); + rechargeOrderMapper.updateById(rechargeOrder); + + // 更新用户余额 + user.setMoney(user.getMoney().subtract(rechargeOrder.getOrderAmount())); + userMapper.updateById(user); + + // 记录余额日志 + logMoneyMapper.dec( + user.getId(), + LogMoneyEnum.UM_DEC_RECHARGE.getCode(), + rechargeOrder.getOrderAmount(), + rechargeOrder.getId(), + rechargeOrder.getOrderSn(), + "充值订单退款", + null + ); + + // 生成退款记录 + String refundSn = refundRecordMapper.randMakeOrderSn("sn"); + RefundRecord refundRecord = new RefundRecord(); + refundRecord.setSn(refundSn); + refundRecord.setUserId(rechargeOrder.getUserId()); + refundRecord.setOrderId(rechargeOrder.getId()); + refundRecord.setOrderSn(rechargeOrder.getOrderSn()); + refundRecord.setOrderType(RefundEnum.getOrderType(RefundEnum.ORDER_TYPE_RECHARGE.getCode())); + refundRecord.setOrderAmount(rechargeOrder.getOrderAmount()); + refundRecord.setRefundAmount(rechargeOrder.getOrderAmount()); + refundRecord.setRefundType(RefundEnum.TYPE_ADMIN.getCode()); + refundRecord.setTransactionId(refundRecord.getTransactionId()); + refundRecord.setRefundWay(rechargeOrder.getPayWay()); + refundRecordMapper.insert(refundRecord); + + // 生成退款日志 + log = new RefundLog(); + log.setSn(refundLogMapper.randMakeOrderSn("sn")); + log.setRecordId(refundRecord.getId()); + log.setUserId(rechargeOrder.getUserId()); + log.setHandleId(adminId); + log.setOrderAmount(rechargeOrder.getOrderAmount()); + log.setRefundAmount(refundRecord.getRefundAmount()); + log.setRefundStatus(RefundEnum.REFUND_ING.getCode()); + refundLogMapper.insert(log); + + // 发起退款请求 + RefundRequestV3 requestV3 = new RefundRequestV3(); + requestV3.setTransactionId(rechargeOrder.getTransactionId()); + requestV3.setOutTradeNo(rechargeOrder.getOrderSn()); + requestV3.setOutRefundNo(refundSn); + requestV3.setTotalAmount(AmountUtil.yuan2Fen(rechargeOrder.getOrderAmount().toString())); + requestV3.setRefundAmount(AmountUtil.yuan2Fen(rechargeOrder.getOrderAmount().toString())); + WxPayDriver.refund(requestV3); + + + log.setRefundStatus(RefundEnum.REFUND_SUCCESS.getCode()); + refundLogMapper.updateById(log); + transactionManager.commit(transactionStatus); + } catch (Exception e) { + // 事务回滚 + transactionManager.rollback(transactionStatus); + + if (StringUtils.isNotNull(log)) { + log.setRefundStatus(RefundEnum.REFUND_ERROR.getCode()); + refundLogMapper.updateById(log); + } + throw new OperateException(e.getMessage()); + } } + /** + * 重新退款 + * + * @author fzr + * @param recordId 记录ID + * @param adminId 管理员ID + */ + @Override + public void refundAgain(Integer recordId, Integer adminId) { + // 开启事务 + TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); + + RefundLog log = null; + try { + RefundRecord refundRecord = refundRecordMapper.selectById(recordId); + RechargeOrder rechargeOrder = rechargeOrderMapper.selectById(refundRecord.getOrderId()); + + log = refundLogMapper.selectOne(new QueryWrapper() + .eq("record_id", recordId) + .last("limit 1")); + + log.setRefundStatus(RefundEnum.REFUND_ING.getCode()); + refundLogMapper.updateById(log); + + // 发起退款请求 + RefundRequestV3 requestV3 = new RefundRequestV3(); + requestV3.setTransactionId(refundRecord.getTransactionId()); + requestV3.setOutTradeNo(refundRecord.getOrderSn()); + requestV3.setOutRefundNo(refundRecord.getSn()); + requestV3.setTotalAmount(AmountUtil.yuan2Fen(rechargeOrder.getOrderAmount().toString())); + requestV3.setRefundAmount(AmountUtil.yuan2Fen(refundRecord.getOrderAmount().toString())); + WxPayDriver.refund(requestV3); + + log.setRefundStatus(RefundEnum.REFUND_SUCCESS.getCode()); + refundLogMapper.updateById(log); + transactionManager.commit(transactionStatus); + } catch (Exception e) { + transactionManager.rollback(transactionStatus); + if (StringUtils.isNotNull(log)) { + log.setRefundStatus(RefundEnum.REFUND_ERROR.getCode()); + refundLogMapper.updateById(log); + } + throw new OperateException(e.getMessage()); + } + } } diff --git a/server/like-common/src/main/java/com/mdd/common/enums/RefundEnum.java b/server/like-common/src/main/java/com/mdd/common/enums/RefundEnum.java new file mode 100644 index 00000000..8d1f4f2f --- /dev/null +++ b/server/like-common/src/main/java/com/mdd/common/enums/RefundEnum.java @@ -0,0 +1,64 @@ +package com.mdd.common.enums; + +public enum RefundEnum { + + // 退款类型 + TYPE_ADMIN(1, "后台退款"), + + // 退款状态 + REFUND_ING(0, "退款中"), + REFUND_SUCCESS(1, "退款成功"), + REFUND_ERROR(2, "退款失败"), + + // 退款订单类型 + ORDER_TYPE_ORDER(1, "普通订单"), + ORDER_TYPE_RECHARGE(2, "充值订单"); + + /** + * 构造方法 + */ + private final int code; + private final String msg; + RefundEnum(int code, String msg) { + this.code = code; + this.msg = msg; + } + + /** + * 获取状态码 + * + * @author fzr + * @return Long + */ + public int getCode() { + return this.code; + } + + /** + * 获取提示 + * + * @author fzr + * @return String + */ + public String getMsg() { + return this.msg; + } + + /** + * 根据编码获取Msg + * + * @author fzr + * @param code 类型 + * @return String + */ + public static String getOrderType(Integer code){ + switch (code) { + case 1: + return "order"; + case 2: + return "recharge"; + } + return "未知"; + } + +} diff --git a/server/like-common/src/main/java/com/mdd/common/mapper/RefundLogMapper.java b/server/like-common/src/main/java/com/mdd/common/mapper/RefundLogMapper.java index b548d75a..c9813a98 100644 --- a/server/like-common/src/main/java/com/mdd/common/mapper/RefundLogMapper.java +++ b/server/like-common/src/main/java/com/mdd/common/mapper/RefundLogMapper.java @@ -1,7 +1,11 @@ package com.mdd.common.mapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mdd.common.core.basics.IBaseMapper; import com.mdd.common.entity.RefundLog; +import com.mdd.common.entity.RefundRecord; +import com.mdd.common.util.TimeUtils; +import com.mdd.common.util.ToolUtils; import org.apache.ibatis.annotations.Mapper; /** @@ -9,4 +13,29 @@ import org.apache.ibatis.annotations.Mapper; */ @Mapper public interface RefundLogMapper extends IBaseMapper { + + /** + * 生成唯一单号 + * + * @author fzr + * @param field 字段名 + * @return String + */ + default String randMakeOrderSn(String field) { + String date = TimeUtils.timestampToDate(System.currentTimeMillis()/1000, "yyyyMMddHHmmss"); + String sn; + while (true) { + sn = date + ToolUtils.randomInt(12); + RefundLog snModel = this.selectOne( + new QueryWrapper() + .select("id") + .eq(field, sn) + .last("limit 1")); + if (snModel == null) { + break; + } + } + return sn; + } + } diff --git a/server/like-common/src/main/java/com/mdd/common/mapper/RefundRecordMapper.java b/server/like-common/src/main/java/com/mdd/common/mapper/RefundRecordMapper.java index dcd3f480..629098d5 100644 --- a/server/like-common/src/main/java/com/mdd/common/mapper/RefundRecordMapper.java +++ b/server/like-common/src/main/java/com/mdd/common/mapper/RefundRecordMapper.java @@ -1,7 +1,11 @@ package com.mdd.common.mapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mdd.common.core.basics.IBaseMapper; +import com.mdd.common.entity.RechargeOrder; import com.mdd.common.entity.RefundRecord; +import com.mdd.common.util.TimeUtils; +import com.mdd.common.util.ToolUtils; import org.apache.ibatis.annotations.Mapper; /** @@ -9,4 +13,29 @@ import org.apache.ibatis.annotations.Mapper; */ @Mapper public interface RefundRecordMapper extends IBaseMapper { + + /** + * 生成唯一单号 + * + * @author fzr + * @param field 字段名 + * @return String + */ + default String randMakeOrderSn(String field) { + String date = TimeUtils.timestampToDate(System.currentTimeMillis()/1000, "yyyyMMddHHmmss"); + String sn; + while (true) { + sn = date + ToolUtils.randomInt(12); + RefundRecord snModel = this.selectOne( + new QueryWrapper() + .select("id") + .eq(field, sn) + .last("limit 1")); + if (snModel == null) { + break; + } + } + return sn; + } + } diff --git a/server/like-common/src/main/java/com/mdd/common/plugin/wechat/WxPayDriver.java b/server/like-common/src/main/java/com/mdd/common/plugin/wechat/WxPayDriver.java index 6166d78f..59ade0c5 100644 --- a/server/like-common/src/main/java/com/mdd/common/plugin/wechat/WxPayDriver.java +++ b/server/like-common/src/main/java/com/mdd/common/plugin/wechat/WxPayDriver.java @@ -115,18 +115,19 @@ public class WxPayDriver { */ public static WxPayRefundV3Result refund(RefundRequestV3 request) throws WxPayException { WxPayRefundV3Request requestObj = new WxPayRefundV3Request(); - request.setTransactionId(request.getTransactionId()); - request.setOutTradeNo(request.getOutRefundNo()); - request.setOutRefundNo(request.getOutRefundNo()); - request.setReason(request.getReason()); - request.setNotifyUrl(request.getNotifyUrl()); - request.setSubMchid(request.getSubMchid()); - request.setGoodsDetails(request.getGoodsDetails()); + requestObj.setTransactionId(request.getTransactionId()); + requestObj.setOutTradeNo(request.getOutTradeNo()); + requestObj.setOutRefundNo(request.getOutRefundNo()); + requestObj.setReason(request.getReason()); + requestObj.setNotifyUrl(request.getNotifyUrl()); + requestObj.setSubMchid(request.getSubMchid()); + requestObj.setGoodsDetails(request.getGoodsDetails()); WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount(); amount.setRefund(request.getRefundAmount()); amount.setTotal(request.getTotalAmount()); amount.setCurrency(StringUtils.isEmpty(request.getCurrency()) ? "CNY" : request.getCurrency()); + requestObj.setAmount(amount); return wxPayService.refundV3(requestObj); } diff --git a/server/like-common/src/main/java/com/mdd/common/plugin/wechat/request/RefundRequestV3.java b/server/like-common/src/main/java/com/mdd/common/plugin/wechat/request/RefundRequestV3.java index 78cfd792..d9b366ed 100644 --- a/server/like-common/src/main/java/com/mdd/common/plugin/wechat/request/RefundRequestV3.java +++ b/server/like-common/src/main/java/com/mdd/common/plugin/wechat/request/RefundRequestV3.java @@ -12,35 +12,35 @@ public class RefundRequestV3 implements Serializable { private static final long serialVersionUID = -1L; - /** 订单流水号 */ + /** 订单流水号: (必须) */ private String transactionId; - /** 订单单号 */ + /** 订单单号: (必须) */ private String outTradeNo; - /** 退款单号 */ + /** 退款单号: (必须) */ @SerializedName("out_refund_no") private String outRefundNo; - /** 退款原因 */ - private String reason; - - /** 通知接口 */ - private String notifyUrl; - - /** 退款金额 */ + /** 退款金额 : (必须)*/ private Integer refundAmount; - /** 订单总额 */ + /** 订单总额: (必须) */ private Integer totalAmount; - /** 退款币种 */ + /** 退款原因: 非必须 */ + private String reason; + + /** 通知接口: 非必须 */ + private String notifyUrl; + + /** 退款币种: 非必须 */ private String currency; - /** 商品信息 */ + /** 商品信息: 非必须 */ private List goodsDetails; - /** 子商户号 */ + /** 子商户号: 非必须 */ private String subMchid; } diff --git a/server/like-front/src/main/java/com/mdd/front/service/impl/PayServiceImpl.java b/server/like-front/src/main/java/com/mdd/front/service/impl/PayServiceImpl.java index fca905e1..ee2f706f 100644 --- a/server/like-front/src/main/java/com/mdd/front/service/impl/PayServiceImpl.java +++ b/server/like-front/src/main/java/com/mdd/front/service/impl/PayServiceImpl.java @@ -116,7 +116,7 @@ public class PayServiceImpl implements IPayService { if (StringUtils.isNotNull(rechargeOrder)) { orderExist = true; vo.setPayStatus(rechargeOrder.getPayStatus()); - vo.setPayWay(PaymentEnum.getMsgByCode(rechargeOrder.getPayWay())); + vo.setPayWay(PaymentEnum.getPayWayMsg(rechargeOrder.getPayWay())); vo.setOrderId(rechargeOrder.getId()); vo.setOrderSn(rechargeOrder.getOrderSn()); vo.setOrderAmount(rechargeOrder.getOrderAmount());