增加退款接口

This commit is contained in:
TinyAnts 2023-03-31 12:05:34 +08:00
parent 649b7f9bcb
commit 2cb2bd3cb1
9 changed files with 354 additions and 30 deletions

View File

@ -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<Object> refund() {
iFinanceRechargerService.refund();
public AjaxResult<Object> refund(@Validated @RequestBody IdValidate idValidate) {
Integer adminId = LikeAdminThreadLocal.getAdminId();
iFinanceRechargerService.refund(idValidate.getId(), adminId);
return AjaxResult.success();
}
@PostMapping("/refundAgain")
@ApiModelProperty("重新退款")
public AjaxResult<Object> refundAgain(@Validated @RequestBody IdValidate idValidate) {
Integer adminId = LikeAdminThreadLocal.getAdminId();
iFinanceRechargerService.refundAgain(idValidate.getId(), adminId);
return AjaxResult.success();
}

View File

@ -20,6 +20,22 @@ public interface IFinanceRechargerService {
*/
PageResult<FinanceRechargeListVo> 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);
}

View File

@ -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<RefundLog>()
.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());
}
}
}

View File

@ -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 "未知";
}
}

View File

@ -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<RefundLog> {
/**
* 生成唯一单号
*
* @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<RefundLog>()
.select("id")
.eq(field, sn)
.last("limit 1"));
if (snModel == null) {
break;
}
}
return sn;
}
}

View File

@ -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<RefundRecord> {
/**
* 生成唯一单号
*
* @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<RefundRecord>()
.select("id")
.eq(field, sn)
.last("limit 1"));
if (snModel == null) {
break;
}
}
return sn;
}
}

View File

@ -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);
}

View File

@ -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<WxPayRefundV3Request.GoodsDetail> goodsDetails;
/** 子商户号 */
/** 子商户号: 非必须 */
private String subMchid;
}

View File

@ -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());