mall + pay:

1. 完善微信支付的 V3 支付回调、退款回调
plp
YunaiV 3 years ago
parent 68a4ef98ca
commit d0a7f41875

@ -70,6 +70,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
return channelId; return channelId;
} }
// ============ 支付相关 ==========
@Override @Override
public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
@ -91,6 +93,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
throws Throwable; throws Throwable;
// ============ 退款相关 ==========
@Override @Override
public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);

@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
@ -19,8 +18,11 @@ import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.service.WxPayService;
@ -78,6 +80,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
client.setConfig(payConfig); client.setConfig(payConfig);
} }
// ============ 支付相关 ==========
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
try { try {
@ -113,6 +117,58 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) protected abstract PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO)
throws WxPayException; throws WxPayException;
@Override
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
try {
// 微信支付 v2 回调结果处理
switch (config.getApiVersion()) {
case API_VERSION_V2:
return parseOrderNotifyV2(body);
case WxPayClientConfig.API_VERSION_V3:
return parseOrderNotifyV3(body);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
} catch (WxPayException e) {
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
// throw buildPayException(e);
throw new RuntimeException(e);
// TODO 芋艿:缺一个异常翻译
}
}
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
// 1. 解析回调
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
// 2. 构建结果
return PayOrderRespDTO.builder()
.outTradeNo(response.getOutTradeNo())
.channelOrderNo(response.getTransactionId())
.channelUserId(response.getOpenid())
.status(Objects.equals(response.getResultCode(), "SUCCESS") ?
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
.successTime(parseDateV2(response.getTimeEnd()))
.rawData(response)
.build();
}
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
// 1. 解析回调
WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
WxPayOrderNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
// 2. 构建结果
return PayOrderRespDTO.builder()
.outTradeNo(responseResult.getOutTradeNo())
.channelOrderNo(responseResult.getTradeState())
.channelUserId(responseResult.getPayer() != null ? responseResult.getPayer().getOpenid() : null)
.status(Objects.equals(responseResult.getTradeState(), "SUCCESS") ?
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
.successTime(parseDateV3(responseResult.getSuccessTime()))
.build();
}
// ============ 退款相关 ==========
@Override @Override
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
try { try {
@ -140,73 +196,48 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
.setTotalFee(reqDTO.getPayPrice()) .setTotalFee(reqDTO.getPayPrice())
.setNotifyUrl(reqDTO.getNotifyUrl()); .setNotifyUrl(reqDTO.getNotifyUrl());
// 2.1 执行请求 // 2.1 执行请求
WxPayRefundResult response = client.refundV2(request); // TODO 芋艿:可以分成 V2 和 V3 的退款接口 WxPayRefundResult response = client.refundV2(request);
// 2.2 创建返回结果 // 2.2 创建返回结果
PayRefundRespDTO refund = new PayRefundRespDTO() PayRefundRespDTO refund = new PayRefundRespDTO()
.setOutRefundNo(reqDTO.getOutRefundNo()) .setOutRefundNo(reqDTO.getOutRefundNo())
.setRawData(response); .setRawData(response);
if (Objects.equals("SUCCESS", response.getResultCode())) { if (Objects.equals("SUCCESS", response.getResultCode())) {
refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus()); refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
refund.setChannelRefundNo(response.getRefundId()); .setChannelRefundNo(response.getRefundId());
} else { } else {
refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()); refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
// TODO 芋艿;异常的处理;
} }
// TODO 芋艿;异常的处理;
return refund; return refund;
} }
private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable { private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
return null; // 1. 构建 WxPayRefundRequest 请求
} WxPayRefundV3Request request = new WxPayRefundV3Request()
.setOutTradeNo(reqDTO.getOutTradeNo())
@Override .setOutRefundNo(reqDTO.getOutRefundNo())
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) { .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice())
try { .setTotal(reqDTO.getPayPrice()).setCurrency("CNY"))
// 微信支付 v2 回调结果处理 .setReason(reqDTO.getReason())
switch (config.getApiVersion()) { .setNotifyUrl(reqDTO.getNotifyUrl());
case API_VERSION_V2: // 2.1 执行请求
return parseOrderNotifyV2(body); WxPayRefundV3Result response = client.refundV3(request);
case WxPayClientConfig.API_VERSION_V3: // 2.2 创建返回结果
return parseOrderNotifyV3(body); PayRefundRespDTO refund = new PayRefundRespDTO()
default: .setOutRefundNo(reqDTO.getOutRefundNo())
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion())); .setRawData(response);
} if (Objects.equals("SUCCESS", response.getStatus())) {
} catch (WxPayException e) { refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e); .setChannelRefundNo(response.getRefundId())
// throw buildPayException(e); .setSuccessTime(parseDateV3(response.getSuccessTime()));
throw new RuntimeException(e); } else if (Objects.equals("PROCESSING", response.getStatus())) {
// TODO 芋艿:缺一个异常翻译 refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
.setChannelRefundNo(response.getRefundId());
} else {
refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
} }
} // TODO 芋艿;异常的处理;
return refund;
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
// 1. 解析回调
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
// 2. 构建结果
return PayOrderRespDTO.builder()
.outTradeNo(response.getOutTradeNo())
.channelOrderNo(response.getTransactionId())
.channelUserId(response.getOpenid())
.status(Objects.equals(response.getResultCode(), "SUCCESS") ?
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
.successTime(parseDateV2(response.getTimeEnd()))
.rawData(response)
.build();
}
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
// TODO 芋艿:翻译下 state
// 转换结果
Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
"支付结果非 SUCCESS");
return PayOrderRespDTO.builder()
.outTradeNo(result.getOutTradeNo())
.channelOrderNo(result.getTradeState())
.channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null)
.successTime(parseDateV3(result.getSuccessTime()))
.build();
} }
@Override @Override
@ -229,27 +260,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
} }
} }
@SuppressWarnings("DuplicatedCode")
private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException { private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
// 1. 解析回调 // 1. 解析回调
WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body); WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
WxPayRefundNotifyResult.ReqInfo reqInfo = response.getReqInfo(); WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
// 2. 构建结果 // 2. 构建结果
PayRefundRespDTO notify = new PayRefundRespDTO() PayRefundRespDTO notify = new PayRefundRespDTO()
.setChannelRefundNo(reqInfo.getRefundId()) .setChannelRefundNo(responseResult.getRefundId())
.setOutRefundNo(reqInfo.getOutRefundNo()) .setOutRefundNo(responseResult.getOutRefundNo())
.setRawData(response); .setRawData(response);
if (Objects.equals("SUCCESS", reqInfo.getRefundStatus())) { if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()) notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
.setSuccessTime(parseDateV2B(reqInfo.getSuccessTime())); .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
} else { } else {
notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()); notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
} }
return notify; return notify;
} }
@SuppressWarnings("DuplicatedCode")
private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException { private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
// TODO 芋艿:未实现 // 1. 解析回调
return null; WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
// 2. 构建结果
PayRefundRespDTO notify = new PayRefundRespDTO()
.setChannelRefundNo(responseResult.getRefundId())
.setOutRefundNo(responseResult.getOutRefundNo())
.setRawData(response);
if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
.setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
} else {
notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
}
return notify;
} }
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========

@ -55,13 +55,13 @@ public class WxNativePayClient extends AbstractWxPayClient {
@Override @Override
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
// 构建 WxPayUnifiedOrderRequest 对象 // 构建 WxPayUnifiedOrderRequest 对象
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request(); WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request()
request.setOutTradeNo(reqDTO.getOutTradeNo()); .setOutTradeNo(reqDTO.getOutTradeNo())
request.setDescription(reqDTO.getBody()); .setDescription(reqDTO.getSubject())
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分 .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())) // 单位分
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime())); .setTimeExpire(formatDateV3(reqDTO.getExpireTime()))
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp())); .setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()))
request.setNotifyUrl(reqDTO.getNotifyUrl()); .setNotifyUrl(reqDTO.getNotifyUrl());
// 执行请求 // 执行请求
String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);

@ -41,7 +41,8 @@
</el-table-column> </el-table-column>
<el-table-column label="退款时间" align="center" prop="refundTime" width="180"> <el-table-column label="退款时间" align="center" prop="refundTime" width="180">
<template v-slot="scope"> <template v-slot="scope">
<span>{{ parseTime(scope.row.refundTime) }}</span> <span v-if="scope.row.refundTime">{{ parseTime(scope.row.refundTime) }}</span>
<span v-else-if="scope.row.payRefundId">退款中等待退款结果</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">

Loading…
Cancel
Save