定时任务查单、关单、退款功能实战

摘要

在《一个正确的支付系统应如何设计?支付及回调》一文中,完成了主动支付、定时任务回调功能。这样虽然能基本完成了支付,但是并不完善,尤其是当商户侧迟迟无法收到微信平台的通知时,就会导致订单的状态无法更新,用户一直不清楚自己是否支付成功了。为了避免由于网络异常引发的问题,所以我们采用如下方案来弥补:

定时任务每隔30秒启动一次,找出最近10分钟内创建并且未支付的订单,调用《微信支付查单接口》核实订单状态。

系统记录订单查询的次数,在10次查询之后状态还是未支付成功,则停止后续查询,并调用《关单接口 》关闭订单。(轮询时间间隔和次数,商户可以根据自身业务场景灵活设置)

定时任务查单实现

定时任务使用轻量级的springtask,springtask 不需要单独的jar包,它的api封装到spring-context包中,如下图所示:
定时任务查单、关单、退款功能实战

它使用起来比较简单,对于定时处理订单这种场景非常适合。在方案中我们提到:每隔30秒启动一次,找出最近10分钟内创建并且未支付的订单。先来创建一个定时任务。定时任务的主要内容主要包括以下几条:


(1)查询订单表记录,获取10分钟内创建且未支付的订单,这里可以根据业务变更,我这里改成了,每隔10s获取5分钟前创建的、并且未处理的订单,其实道理都是一样的,目的是检查未处理订单的状态。

(2)循环这些记录调用微信查单接口,判断订单的实际状态。并根据实际的订单状态更新订单信息。

(3)如果订单的实际状态为成功,则更新订单的实际状态为成功,并且处理业务数据(保证金余额信息)。

(4)如果5分钟前的订单还没返回状态就调用关闭订单接口关闭订单。

每隔10s获取5分钟前创建的、并且未处理的订单:
1、在应用的启动类上添加注解 
@EnableScheduling 表示开启任务调度
2、创建task类如下,并添加定时任务查单方法,并在方法中添加
@Scheduled(cron = "0/5 * * * * ?") 注解

@Slf4j
@Component
public class WxPayTask {
/**
* 从第0秒开始每隔10秒执行1次,查询创建超过2min的订单,并且未支付的订单
*/

@Scheduled(cron = "0/10 * * * * ?")
public void orderConfirm() throws Exception {
log.info("orderConfirm 被执行......");
List<DepositOrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(300L);
for (DepositOrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用微信支付查单接口
wxPayService.checkOrderStatus(orderNo);
}
}
}

这里再重点说明一下 cron表达式的含义:

 /**
* 格式:* * * * * * *
* 秒 分 时 日 月 周 年(可选)
* 以秒为例
* *:每秒都执行
* 1-3:从第1秒开始执行,到第3秒结束执行
* 0/3:从第0秒开始,每隔3秒执行1次
* 1,2,3:在指定的第1、2、3秒执行
* ?:不指定
* 日和周不能同时制定,指定其中之一,则另一个设置为?
*/

提供corn表达式在线生成器 : https://cron.qqe2.com/

wxPayService.checkOrderStatus(orderNo); 代码如下

/**
* 核实订单状态
* 供定时任务调用,频率:每隔10s 检查一次,考虑在某段时间内开启定时任务
* 1、用户发起支付了(微信平台是成功),但是迟迟没有收到微信平台的通知,数据库的支付状态未更新,还是待支付状态(也是1分钟的订单)。
* 2、用户确实一直没有支付,这种情况要关闭订单,此情况也包括prepayId超过2小时的订单(1分钟如果没有支付算超时订单,根据业务规定)
* 如果订单已支付,则更新商户端订单状态,并记录支付日志
* 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
*
* @param orderNo
*/

@Transactional(rollbackFor = Exception.class)
@Override
public void checkOrderStatus(String orderNo) throws Exception {
Gson gson = new Gson();
log.warn("根据订单号核实订单状态 ===> {}", orderNo);
RLock lockPayNotify = redisson.getLock(LockType.PAY_NOTIFY_LOCK.getType());

try {
//5s内拿不到锁无法执行,如果业务产生异常,30s后自动释放锁,避免业务异常发生死锁
if (lockPayNotify.tryLock(5, 30, TimeUnit.SECONDS)) {
//调用微信支付查单接口
Transaction transaction = this.queryOrder(orderNo);
if (transaction == null) {
log.warn("查单失败");
return;
}
String plainText = gson.toJson(transaction);
//获取微信支付端的订单状态
String tradeState = transaction.getTradeState().name();

//判断订单状态
if (WxTradeState.SUCCESS.getType().equals(tradeState)) {

log.warn("核实订单已支付 ===> {}", orderNo);

//处理重复的通知
//接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。防止其他地方进行更新操作
DepositOrderInfo orderInfo = depositOrderInfoService.getOrderStatus(orderNo);
if (orderInfo == null) {
return;
}
if (!OrderStatus.NOTPAY.getType().equals(orderInfo.getOrderStatus())) {
return;
}
//如果确认订单已支付则更新本地订单状态
depositOrderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
//记录支付日志
depositPaymentRecordService.createPaymentRecord(plainText, transaction, "定时查单");
// int i= 1/0;
//处理业务数据
//构造交易数据
TradeInfoInVo tradeInfoInVo = new TradeInfoInVo();
//订单号
tradeInfoInVo.setPayTradeNo(transaction.getOutTradeNo());
//交易金额
tradeInfoInVo.setTradeAmount(transaction.getAmount().getPayerTotal());
tradeInfoInVo.setEventType(TradeLogType.PAY.getCode());
tradeInfoInVo.setUserId(orderInfo.getUserId());
this.updateBusiDataByPayInfo(tradeInfoInVo);

}

//确实是未支付,就关闭订单,不处理支付记录和其他业务数据
if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
log.warn("核实订单未支付 ===> {}", orderNo);

//如果订单未支付,则调用关单接口
this.closeOrder(orderNo);
//等待2s查一下订单
// TimeUnit.SECONDS.sleep(1);
//再次查单确认是否关闭
Transaction transactionClosed = this.queryOrder(orderNo);

//获取微信支付端的订单状态
String tradeState1 = transactionClosed.getTradeState().toString();
if (tradeState1.equals(WxTradeState.CLOSED.getType())) {
//更新本地订单状态
depositOrderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
}
}
}
} catch (Exception e) {
log.error("定时任务查单出现异常" + e.getMessage());
throw new RuntimeException("定时任务查单出现异常");
} finally {
//由于锁可重入,需要判断当前现存是否还持有该锁,如果是就释放锁
if (lockPayNotify.isHeldByCurrentThread()) {
log.info("支付通知回调锁被释放");
//要主动释放锁
lockPayNotify.unlock();
}
}

}

queryOrder方法如下: 此方法调用微信的查单接口

  @Override
public Transaction queryOrder(String orderNo) throws ServiceException {
log.info("查单接口调用 ===> {}", orderNo);
if (config == null){
config = WxInitUtils.getInstance(wxPayConfig);
}
Gson gson = new Gson();

QueryOrderByOutTradeNoRequest queryOrderByIdRequest = new QueryOrderByOutTradeNoRequest();
queryOrderByIdRequest.setMchid(wxPayConfig.getMchId());
queryOrderByIdRequest.setOutTradeNo(orderNo);

JsapiService service = new JsapiService.Builder().config(config).build();
Transaction result = null;
try {
result = service.queryOrderByOutTradeNo(queryOrderByIdRequest);
log.info("查单返回结果===》{}", gson.toJson(result));
log.info("查单返回的订单状态为==》{}", result.getTradeState());
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
log.error("code=[{}], message=[{}]n", e.getErrorCode(), e.getErrorMessage());
log.error("reponse body=[{}]n", e.getResponseBody());
} finally {

}
return result;
}

closeOrder方法 调用微信关闭订单接口

/**
* 微信关单接口
* 关闭订单,以下情况需要调用关单接口:
* 1、商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
* 2、系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
* 由于没有返回值,这时需要调用查单接口来确认订单的状态。这时需要在客户端的代码中调用查单接口来看看状态
* 成功后来刷新订单列表的状态。
*
* @param orderNo
*/

private void closeOrder(String orderNo) {
log.info("关单接口的调用,订单号 ===> {}", orderNo);
if (config == null){
config = WxInitUtils.getInstance(wxPayConfig);
}
//创建远程请求对象
CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
closeOrderRequest.setMchid(wxPayConfig.getMchId());
closeOrderRequest.setOutTradeNo(orderNo);
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
try {
//成功返回204 无内容
service.closeOrder(closeOrderRequest);
} catch (Exception e) {
log.error("关单失败");
} finally {

}
}

到此定时任务查单,关单就完成了。整个支付也就完善了。这样前端、后端结合起来就完成了整个支付的功能,即使网络出现问题也能及时保证系统订单的状态。

提现(退款)

以保证金提现业务为例。保证金提现的业务是当用户缴纳保证金后,如果想把保证金的钱退回来,就需要用到微信退款的接口。整个逻辑主要为:小程序发起退款申请->经理审批->财务审批->调用微信退款,具体的流程图如下所示:

定时任务查单、关单、退款功能实战

退款的业务流程比较简单,但是在处理业务细节方面需要注意,退款时需要按订单去申请退款,由于涉及部分退款 每笔订单都对应着已退金额和订单的总金额,退款之前首先判断当前订单是否够退,保证金的可用余额是否够退,此外还要判断当前用户是否预退了一部分金额,也就是进行中的退款申请单还没有处理完,同一个订单如果有未完成的申请单无法重复申请退款。所以,在保证金退款时我们要做如下校验:

(1)退款金额本身的校验(非空、金额是否输入正确,是否满足某个业务规定的值)。
(2)当前订单的金额是否够退(退款金额要<=订单金额-已退金额)。
(3)由于退款需要一定的过程,同一个订单如果有未完成的申请单无法重复申请退款。
(4)退款的金额是否在可用余额范围内。
(5)对于当前用户的所有申请单,可用余额的计算方式为(当前可用余额-所有进行中的申请单的退款金额总和)。

当校验通过后,会提交退款申请单,此时会创建退款申请单(REFUND_APPLICATION),审批过程会产生审批记录,如果财务同意退款要发起调用微信退款接口,同意的动作和发起退款这里设计成了一个原子操作,目的是,如果调用退款接口失败了,则财务可重新点同意退款。

但是,这样做并不能清楚退款的动作是否真的成功了,因为调用退款接口只是调用成功了,微信端发起退款成功了,但是系统并不清楚退款的状态,这和支付过程是一样的,所以我们还需要一个退款的回调接口、定时查询退款单的接口。这两个接口都是异步的操作。在回调和定时查询退款单的业务最后要更新业务数据(保证金的金额、操作记录等)。

退款申请

主要逻辑:1、创建退款记录(REFUND_INFO) 2、调用微信退款申请接口。代码块如下:

退款申请Controller示例:

    @ApiOperation("申请退款")
@PostMapping("/refunds" )
@RequiresAuthentication
public R refunds(@RequestBody RefundInVo refundInfo) throws Exception {

if (refundInfo == null){
throw new RuntimeException("申请信息为null");
}
/**
* 退款申请逻辑校验:
* 1、退款金额<可用余额 ,不满足:不可退款
* 2、退款金额是否<订单金额-已退金额 不满足:不可退款
*/

log.info("申请退款");

// 获取订单表信息 以及用户可用金额
DepositOrderInfo depositOrderInfo = depositOrderInfoService.getDepositOrderInfoByUserId(refundInfo);
// ----------
if(depositOrderInfo!=null){
refundInfo.setSuccessRefund(depositOrderInfo.getRefundFee());
refundInfo.setOrderTotal(depositOrderInfo.getTotalFee());
refundInfo.setAvailMoney(DataTypeUtils.yuanToFen(depositOrderInfo.getAvailMoney()));
//判断是否满足退款条件
if(wxPayService.checkRefundInfo(refundInfo).getCode() == 0){
//满足退款条件,发起微信退款申请。
wxPayService.refund(refundInfo);
}
}
return R.ok();
}

service示例:

 /**
* 退款(按订单退款,一笔订单可退多笔)
* 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。
* 申请退款总金额不能超过订单金额。
* 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
*
* @param refundInVo
* @throws IOException
*/

@Transactional(rollbackFor = Exception.class)
@Override
public PayVo refund(RefundInVo refundInVo) throws Exception {

String orderNo = refundInVo.getOrderNo();
String userId = refundInVo.getUserId();
String reason = refundInVo.getReason();
long appAmount = refundInVo.getAppAmount();

log.info("创建退款单记录");
//根据订单编号创建退款单,一笔订单对应个退款单,这里不做限制,直接插入。
DepositRefundInfo refundsInfo = depositRefundInfoService.createRefundByOrderNo(refundInVo);

log.info("调用退款API");

if (config == null){
config = WxInitUtils.getInstance(wxPayConfig);
}

//调用退款单api
RefundService refundService = new RefundService.Builder().config(config).build();
CreateRequest request = new CreateRequest();
//商户订单号
request.setOutTradeNo(orderNo);
//退款单编号
request.setOutRefundNo(refundsInfo.getRefundNo());
//退款原因
request.setReason(reason);
//退款通知url地址
request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));

//金额
AmountReq amountReq = new AmountReq();
amountReq.setCurrency("CNY");
//原订单金额
amountReq.setTotal((long) refundsInfo.getTotalFee());
//退款金额
amountReq.setRefund((long) refundsInfo.getRefund());
request.setAmount(amountReq);

// 请求body参数
Gson gson = new Gson();
String jsonParams = gson.toJson(request);
log.info("请求参数 ===> {}" + jsonParams);

Refund refundResult = null;
PayVo payVo = new PayVo();
try {
//发起退款申请
refundResult = refundService.create(request);
if (refundResult == null) {
log.error("退款申请失败,返回结果为null");
throw new RuntimeException("退款申请失败,返回结果为null");
}

//获取退款单状态
Status status = refundResult.getStatus();
if ("SUCCESS".equals(status.name()) || "PROCESSING".equals(status.name())) {
log.info("退款申请成功,返回的结果 ==》{}", gson.toJson(refundResult));

//更新订单状态为退款中(每一笔退款都更新订单的状态为退款中/转为退款,更新为此状态证明本次可以退款并且还未处理状态)
depositOrderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
//更新退款单
depositRefundInfoService.updateRefund(refundResult);
//这里不更新业务数据
payVo.setCode(0).setMessage("SUCCESS");
} else {
//todo 更新退款单和订单,并给出返回提示,财务经理 -可以重新提交申请
log.warn("退款申请状态不成功 ===> {}", refundsInfo.getRefundNo());
//如果确认退款成功,则更新订单状态为退款异常(此状态只能代表上一次退款异常的状态)
depositOrderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
//更新退款单
depositRefundInfoService.updateRefund(refundResult);
//更新退款申请单
TradeInfoInVo tradeInfoInVo = new TradeInfoInVo();
tradeInfoInVo.setPayTradeNo(refundsInfo.getRefundNo());
tradeInfoInVo.setEventType(TradeLogType.REFUND.getCode());
tradeInfoInVo.setTradeAmount(refundResult.getAmount().getPayerRefund());
tradeInfoInVo.setUserId(userId);
tradeInfoInVo.setTradeStatus(status.name());
appDepositRefundApplicationService.updateRefundApplicationInfo(tradeInfoInVo);

log.info("本次退款申请失败,返回的结果 ==》{}", gson.toJson(refundResult));
payVo.setCode(-1).setMessage(status.name());
}

} catch (Exception e) {
payVo.setCode(-2).setMessage("程序异常");
log.error("退款申请出现异常,异常原因" + e.getMessage());
e.printStackTrace();
} finally {
log.info("退款申请结束");
}
return payVo;
}

上述代码中,如果退款申请成功,则并不能说明退款成功了,还要等待回调通知接口或主动调用查单接口来获取真实的状态。如果退款申请异常,微信平台则不会调用退款回调接口。如果退款下单过程中,您的订单不够退就会走上面的else。

退款回调

退款的回调同支付回调,在通知回调中接收退款的实际状态,并根据状态更新订单中退款的状态(退款的实际状态),及处理已退金额和退款的结果(部分退款、全部退完)。插入退款单退款记录,更新退款申请单的状态。
退款回调接口如下:

/**
* 退款申请回调 refunds/notify
*/

@ApiOperation("微信退款申请回调")
@PostMapping("/refunds/notify" )
public ResponseEntity refundNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("退款通知执行");
Gson gson = new Gson();
//创建一个map用来存储所有的header信息
Map<String, Object> headerMap = new HashMap<>();

try {
//处理通知参数 1、获取header值
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
//获取一个header的名称
String name = headerNames.nextElement();
//获取该header的值
String value = request.getHeader(name);
headerMap.put(name, value);
}
log.info("通知回调中header的值===>" + headerMap);

String wechatPaySerial = headerMap.get(WECHAT_PAY_SERIAL).toString();
String wechatSignature = headerMap.get(WECHAT_PAY_SIGNATURE).toString();
String wechatpayNonce = headerMap.get(WECHAT_PAY_NONCE).toString();
String wechatTimestamp = headerMap.get(WECHAT_PAY_TIMESTAMP).toString();
// String wechatPaySignatureType = headerMap.get("Wechatpay-Signature-Type").toString();

String requestBody = HttpUtils.readData(request);
log.info("退款通知的完整数据 ===> {}", requestBody);
//反序列化请求body串得到map数据
Map<String, Object> bodyMap = gson.fromJson(requestBody, HashMap.class);
String requestId = (String) bodyMap.get("id");
log.info("支付通知的id ===> {}", requestId);

//构造验签请求验签名串
// 构造 RequestParam以用来进行验签和解密
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(wechatpayNonce)
.signature(wechatSignature)
.timestamp(wechatTimestamp)
.body(requestBody)
.build();

// 初始化 NotificationParser(验签、解密)
NotificationParser parser = new NotificationParser((NotificationConfig) config);

RefundNotification refundNotification = null;
try {
// 验签、解密并转换成 Transaction
refundNotification = parser.parse(requestParam, RefundNotification.class);
} catch (ValidationException e) {
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
log.error("通知验签失败", e);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

log.info("通知验签成功");

//处理订单
wxPayService.processRefund(bodyMap, refundNotification);
//应答超时
//模拟接收微信端的重复通知(超过5s商户没有给微信平台回应则超时,平台继续发通知)
// TimeUnit.SECONDS.sleep(5);
// 处理成功,返回 200 OK 状态码
return ResponseEntity.status(HttpStatus.OK).build();

} catch (Exception e) {
e.printStackTrace();
//失败应答:如果处理失败,应返回 4xx/5xx 的状态码,例如 500 INTERNAL_SERVER_ERROR
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}

}

这里同支付回调一样,先接收参数、解析参数(验签、解密等操作),然后调用 wxPayService.processRefund(bodyMap, refundNotification);进行业务处理,根据微信的请求参数更新订单状态、插入退款记录、如果退款为成功状态,则更新订单信息的已退金额和退款结果,并更新保证金的余额、保证金使用记录等信息。最后更新退款申请单(小程序端提交申请时插入的表)。processRefund方法代码段如下:

 @Override
public void processRefund(Map<String, Object> bodyMap, RefundNotification refundNotification) throws Exception {

log.info("处理退款单");

//将明文转换成map
Gson gson = new Gson();
//订单号
String orderNo = refundNotification.getOutTradeNo();
//退款单号
String refundNo = refundNotification.getOutRefundNo();
//实际退款状态
String refundStatus = refundNotification.getRefundStatus().name();
//实际退款金额 当前业务来说 申请金额 = 实际退款金额,以实际退款金额为主
long payerRefund = refundNotification.getAmount().getPayerRefund();

//获取退款通知回调锁
RLock lockRefundNotify = redisson.getLock(LockType.REFUND_NOTIFY_LOCK.getType());

try {
if (lockRefundNotify.tryLock(5, 30, TimeUnit.SECONDS)) {
log.info("获取到退款通知回调锁");
WxPayServiceImpl wxPayServiceImpl =(WxPayServiceImpl) AopContext.currentProxy();
//处理订单数据和业务数据
wxPayServiceImpl.handleNotifyRefund(refundNotification);
}

} finally {
if (lockRefundNotify.isHeldByCurrentThread()) {
log.info("退款通知回调锁释放");
//要主动释放锁
lockRefundNotify.unlock();
}
}
}
 /**
* 退款通知回调业务数据处理
* @param refundNotification
* @throws Exception
*/

@Transactional(rollbackFor = Exception.class)
@Override
public void handleNotifyRefund(RefundNotification refundNotification ) throws Exception {
//订单号
String orderNo = refundNotification.getOutTradeNo();
//退款单号
String refundNo = refundNotification.getOutRefundNo();
//实际退款状态
String refundStatus = refundNotification.getRefundStatus().name();
//实际退款金额 当前业务来说 申请金额 = 实际退款金额,以实际退款金额为主
long payerRefund = refundNotification.getAmount().getPayerRefund();

//获取订单状态,如果订单状态还未处理,说明系统还未接收到微信的通知,如果不是未处理,说明已经处理过了
// 所以一定要保证,查单或回调后更新订单的状态为实际退款状态
DepositOrderInfo orderInfo = depositOrderInfoService.getOrderStatus(orderNo);

if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderInfo.getOrderStatus())) {
return;
}

/**
* 判断当前退款状态,根据状态处理
* 成功-调用updateOrderRefundInfoByOrderNo更新
* 其他-更新订单状态为当前退款状态
*/

if (!"SUCCESS".equals(refundStatus)) {

// this.handleWxReturnResult();

//如果退款出现异常或者关闭取消,更新成实际状态(不影响实际业务处理)。
depositOrderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.valueOf(refundStatus));
//更新退款单的状态(REFUND_INFO)
depositRefundInfoService.updateRefund(refundNotification);
//更新退款申请单
//更新退款申请单
TradeInfoInVo tradeInfoInVo = new TradeInfoInVo();
tradeInfoInVo.setPayTradeNo(refundNo);
tradeInfoInVo.setEventType(TradeLogType.REFUND.getCode());
tradeInfoInVo.setTradeAmount(payerRefund);
tradeInfoInVo.setUserId(orderInfo.getUserId());
tradeInfoInVo.setTradeStatus(refundStatus);
tradeInfoInVo.setTradeTime(refundNotification.getCreateTime());
appDepositRefundApplicationService.updateRefundApplicationInfo(tradeInfoInVo);
return;
}
//更新订单信息
depositOrderInfoService.updateOrderRefundInfoByOrderNo(orderNo, payerRefund);
//更新退款单
depositRefundInfoService.updateRefund(refundNotification);

log.info("退款回调成功,开始执行异步任务--更新业务数据");

//更新业务数据
TradeInfoInVo tradeInfoInVo = new TradeInfoInVo();
tradeInfoInVo.setPayTradeNo(refundNo);
tradeInfoInVo.setEventType(TradeLogType.REFUND.getCode());
tradeInfoInVo.setTradeAmount(payerRefund);
tradeInfoInVo.setUserId(orderInfo.getUserId());
tradeInfoInVo.setTradeStatus(refundStatus);
tradeInfoInVo.setTradeTime(refundNotification.getSuccessTime());
this.updateBusiDataByPayInfo(tradeInfoInVo);
}

如果退款成功,更新订单表的逻辑如下:

 /**
* 退款成功后-更新
* 根据订单号和退款金额更新订单的状态和已退金额。
* 1、更新订单的 订单状态为退款成功(这一笔退款单)、
* 2、更新退款结果为 部分退款或全部退款
* 3、更新已退金额
* @param orderNo
* @param payerRefund
*/

@Override
public void updateOrderRefundInfoByOrderNo (String orderNo, long payerRefund) throws Exception{

RLock lock = redissonClient.getLock(LockType.YT_MONEY_LOCK.getType());
try {
// lock.lock(10, TimeUnit.SECONDS);
lock.lock();
log.info("获取到已退金额锁");
QueryWrapper<DepositOrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
orderInfoQueryWrapper.eq("ORDER_NO",orderNo);

//根据查询订单信息
DepositOrderInfo orderByOrderNo = this.getOrderByOrderNo(orderNo);

DepositOrderInfo orderInfo = new DepositOrderInfo();
//退款回调后判断的只要不是退款中的就不能再次接收到回调了,所以订单状态要更新成当前退款单的实际状态
//一笔订单对应多笔退款单,当前订单状态只代表当前这一笔退款单的状态。
orderByOrderNo.setOrderStatus(OrderStatus.SUCCESS.getType());

//计算是否是全部退完
//当前订单的已退金额
int ytMoney = orderByOrderNo.getRefundFee();
//当前订单的总金额
int totalMoney = orderByOrderNo.getTotalFee();
//未退完更新退款状态为部分退款
if (totalMoney-ytMoney>payerRefund){
orderByOrderNo.setRefundStatus(OrderStatus.REFUND_PART.getType());
}else {
orderByOrderNo.setRefundStatus(OrderStatus.REFUND_SUCCESS.getType());
}
orderByOrderNo.setRefundFee((int) (ytMoney+payerRefund));
this.saveOrUpdate(orderByOrderNo);

}catch (Exception e){
log.error("更新已退金额、退款状态、订单状态出现异常");
throw new RuntimeException("异常");
}finally {
log.info("已退金额锁释放");
lock.unlock();
}

}

到此,用户退款申请、微信退款申请的逻辑就实现完成了。

原文始发于微信公众号(小核桃编程):定时任务查单、关单、退款功能实战

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/215973.html

(1)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!