package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin;
|
|
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.codec.Base64;
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.TemporalAccessorUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
|
import cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.AbstractPayClient;
|
import com.github.binarywang.wxpay.bean.notify.*;
|
import com.github.binarywang.wxpay.bean.request.*;
|
import com.github.binarywang.wxpay.bean.result.*;
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
|
import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
|
import com.github.binarywang.wxpay.config.WxPayConfig;
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
import com.github.binarywang.wxpay.service.WxPayService;
|
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.time.LocalDateTime;
|
import java.time.ZoneId;
|
import java.util.Map;
|
import java.util.Objects;
|
|
import static cn.hutool.core.date.DatePattern.*;
|
import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V2;
|
import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.weixin.WxPayClientConfig.API_VERSION_V3;
|
|
/**
|
* 微信支付抽象类,实现微信统一的接口、以及部分实现(退款)
|
*
|
* @author 芋道源码
|
*/
|
@Slf4j
|
public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientConfig> {
|
|
protected WxPayService client;
|
|
public AbstractWxPayClient(Long channelId, String channelCode, WxPayClientConfig config) {
|
super(channelId, channelCode, config);
|
}
|
|
/**
|
* 初始化 client 客户端
|
*
|
* @param tradeType 交易类型
|
*/
|
protected void doInit(String tradeType) {
|
// 创建 config 配置
|
WxPayConfig payConfig = new WxPayConfig();
|
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "publicKeyContent");
|
payConfig.setTradeType(tradeType);
|
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
|
if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) {
|
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
|
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
|
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
|
// 参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICUE53 和 https://t.zsxq.com/ODR5V
|
if (StrUtil.isNotBlank(config.getPublicKeyContent())) {
|
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
|
}
|
// 特殊:强制使用微信公钥模式,避免灰度期间的问题!!!
|
payConfig.setStrictlyNeedWechatPaySerial(true);
|
}
|
|
// 创建 client 客户端
|
client = new WxPayServiceImpl();
|
client.setConfig(payConfig);
|
}
|
|
// ============ 支付相关 ==========
|
|
@Override
|
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
|
try {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doUnifiedOrderV2(reqDTO);
|
case API_VERSION_V3:
|
// TODO @芋艿:【可能是 wxjava 的 bug】参考 https://github.com/binarywang/WxJava/issues/1557
|
client.getConfig().setApiV3HttpClient(null);
|
return doUnifiedOrderV3(reqDTO);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
} catch (WxPayException e) {
|
log.error("[doUnifiedOrder][支付({}) 发起微信支付异常", reqDTO, e);
|
String errorCode = getErrorCode(e);
|
String errorMessage = getErrorMessage(e);
|
return PayOrderRespDTO.closedOf(errorCode, errorMessage,
|
reqDTO.getOutTradeNo(), e.getXmlString());
|
}
|
}
|
|
/**
|
* 【V2】调用支付渠道,统一下单
|
*
|
* @param reqDTO 下单信息
|
* @return 各支付渠道的返回结果
|
*/
|
protected abstract PayOrderRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO)
|
throws Exception;
|
|
/**
|
* 【V3】调用支付渠道,统一下单
|
*
|
* @param reqDTO 下单信息
|
* @return 各支付渠道的返回结果
|
*/
|
protected abstract PayOrderRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO)
|
throws WxPayException;
|
|
/**
|
* 【V2】创建微信下单请求
|
*
|
* @param reqDTO 下信息
|
* @return 下单请求
|
*/
|
protected WxPayUnifiedOrderRequest buildPayUnifiedOrderRequestV2(PayOrderUnifiedReqDTO reqDTO) {
|
return WxPayUnifiedOrderRequest.newBuilder()
|
.outTradeNo(reqDTO.getOutTradeNo())
|
.body(reqDTO.getSubject())
|
.detail(reqDTO.getBody())
|
.totalFee(reqDTO.getPrice()) // 单位分
|
.timeExpire(formatDateV2(reqDTO.getExpireTime()))
|
.spbillCreateIp(reqDTO.getUserIp())
|
.notifyUrl(reqDTO.getNotifyUrl())
|
.build();
|
}
|
|
/**
|
* 【V3】创建微信下单请求
|
*
|
* @param reqDTO 下信息
|
* @return 下单请求
|
*/
|
protected WxPayUnifiedOrderV3Request buildPayUnifiedOrderRequestV3(PayOrderUnifiedReqDTO reqDTO) {
|
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
request.setOutTradeNo(reqDTO.getOutTradeNo());
|
request.setDescription(reqDTO.getSubject());
|
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分
|
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
return request;
|
}
|
|
@Override
|
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doParseOrderNotifyV2(body);
|
case API_VERSION_V3:
|
return doParseOrderNotifyV3(body, headers);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
}
|
|
private PayOrderRespDTO doParseOrderNotifyV2(String body) throws WxPayException {
|
// 1. 解析回调
|
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
|
// 2. 构建结果
|
// V2 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂
|
Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
|
PayOrderStatusEnum.SUCCESS.getStatus() : PayOrderStatusEnum.CLOSED.getStatus();
|
return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
|
response.getOutTradeNo(), body);
|
}
|
|
private PayOrderRespDTO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException {
|
// 1. 解析回调
|
SignatureHeader signatureHeader = getRequestHeader(headers);
|
WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader);
|
WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult();
|
// 2. 构建结果
|
Integer status = parseStatus(result.getTradeState());
|
String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
|
return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()),
|
result.getOutTradeNo(), body);
|
}
|
|
@Override
|
protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
|
try {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doGetOrderV2(outTradeNo);
|
case API_VERSION_V3:
|
return doGetOrderV3(outTradeNo);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
} catch (WxPayException e) {
|
if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) {
|
String errorCode = getErrorCode(e);
|
String errorMessage = getErrorMessage(e);
|
return PayOrderRespDTO.closedOf(errorCode, errorMessage,
|
outTradeNo, e.getXmlString());
|
}
|
throw e;
|
}
|
}
|
|
private PayOrderRespDTO doGetOrderV2(String outTradeNo) throws WxPayException {
|
// 构建 WxPayUnifiedOrderRequest 对象
|
WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder()
|
.outTradeNo(outTradeNo).build();
|
// 执行请求
|
WxPayOrderQueryResult response = client.queryOrder(request);
|
|
// 转换结果
|
Integer status = parseStatus(response.getTradeState());
|
return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
|
outTradeNo, response);
|
}
|
|
private PayOrderRespDTO doGetOrderV3(String outTradeNo) throws WxPayException {
|
// 构建 WxPayUnifiedOrderRequest 对象
|
WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request()
|
.setOutTradeNo(outTradeNo);
|
// 执行请求
|
WxPayOrderQueryV3Result response = client.queryOrderV3(request);
|
|
// 转换结果
|
Integer status = parseStatus(response.getTradeState());
|
String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null;
|
return PayOrderRespDTO.of(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()),
|
outTradeNo, response);
|
}
|
|
private static Integer parseStatus(String tradeState) {
|
switch (tradeState) {
|
case "NOTPAY":
|
case "USERPAYING": // 支付中,等待用户输入密码(条码支付独有)
|
return PayOrderStatusEnum.WAITING.getStatus();
|
case "SUCCESS":
|
return PayOrderStatusEnum.SUCCESS.getStatus();
|
case "REFUND":
|
return PayOrderStatusEnum.REFUND.getStatus();
|
case "CLOSED":
|
case "REVOKED": // 已撤销(刷卡支付独有)
|
case "PAYERROR": // 支付失败(其它原因,如银行返回失败)
|
return PayOrderStatusEnum.CLOSED.getStatus();
|
default:
|
throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState));
|
}
|
}
|
|
// ============ 退款相关 ==========
|
|
@Override
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
try {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doUnifiedRefundV2(reqDTO);
|
case API_VERSION_V3:
|
return doUnifiedRefundV3(reqDTO);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
} catch (WxPayException e) {
|
String errorCode = getErrorCode(e);
|
String errorMessage = getErrorMessage(e);
|
return PayRefundRespDTO.failureOf(errorCode, errorMessage,
|
reqDTO.getOutRefundNo(), e.getXmlString());
|
}
|
}
|
|
private PayRefundRespDTO doUnifiedRefundV2(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
// 1. 构建 WxPayRefundRequest 请求
|
WxPayRefundRequest request = new WxPayRefundRequest()
|
.setOutTradeNo(reqDTO.getOutTradeNo())
|
.setOutRefundNo(reqDTO.getOutRefundNo())
|
.setRefundFee(reqDTO.getRefundPrice())
|
.setRefundDesc(reqDTO.getReason())
|
.setTotalFee(reqDTO.getPayPrice())
|
.setNotifyUrl(reqDTO.getNotifyUrl());
|
// 2.1 执行请求
|
WxPayRefundResult response = client.refundV2(request);
|
// 2.2 创建返回结果
|
if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下,不直接返回退款成功,而是等待异步通知
|
return PayRefundRespDTO.waitingOf(response.getRefundId(),
|
reqDTO.getOutRefundNo(), response);
|
}
|
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
|
}
|
|
private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
// 1. 构建 WxPayRefundRequest 请求
|
WxPayRefundV3Request request = new WxPayRefundV3Request()
|
.setOutTradeNo(reqDTO.getOutTradeNo())
|
.setOutRefundNo(reqDTO.getOutRefundNo())
|
.setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice())
|
.setTotal(reqDTO.getPayPrice()).setCurrency("CNY"))
|
.setReason(reqDTO.getReason())
|
.setNotifyUrl(reqDTO.getNotifyUrl());
|
// 2.1 执行请求
|
WxPayRefundV3Result response = client.refundV3(request);
|
// 2.2 创建返回结果
|
if (Objects.equals("SUCCESS", response.getStatus())) {
|
return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
|
reqDTO.getOutRefundNo(), response);
|
}
|
if (Objects.equals("PROCESSING", response.getStatus())) {
|
return PayRefundRespDTO.waitingOf(response.getRefundId(),
|
reqDTO.getOutRefundNo(), response);
|
}
|
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
|
}
|
|
@Override
|
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doParseRefundNotifyV2(body);
|
case API_VERSION_V3:
|
return parseRefundNotifyV3(body, headers);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
}
|
|
private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException {
|
// 1. 解析回调
|
WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
|
WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
|
// 2. 构建结果
|
if (Objects.equals("SUCCESS", result.getRefundStatus())) {
|
return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
|
result.getOutRefundNo(), response);
|
}
|
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
|
}
|
|
private PayRefundRespDTO parseRefundNotifyV3(String body, Map<String, String> headers) throws WxPayException {
|
// 1. 解析回调
|
SignatureHeader signatureHeader = getRequestHeader(headers);
|
WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, signatureHeader);
|
WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
|
// 2. 构建结果
|
if (Objects.equals("SUCCESS", result.getRefundStatus())) {
|
return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
|
result.getOutRefundNo(), response);
|
}
|
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
|
}
|
|
@Override
|
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException {
|
try {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V2:
|
return doGetRefundV2(outTradeNo, outRefundNo);
|
case API_VERSION_V3:
|
return doGetRefundV3(outTradeNo, outRefundNo);
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
} catch (WxPayException e) {
|
if (ObjectUtils.equalsAny(e.getErrCode(), "REFUNDNOTEXIST", "RESOURCE_NOT_EXISTS")) {
|
String errorCode = getErrorCode(e);
|
String errorMessage = getErrorMessage(e);
|
return PayRefundRespDTO.failureOf(errorCode, errorMessage,
|
outRefundNo, e.getXmlString());
|
}
|
throw e;
|
}
|
}
|
|
private PayRefundRespDTO doGetRefundV2(String outTradeNo, String outRefundNo) throws WxPayException {
|
// 1. 构建 WxPayRefundRequest 请求
|
WxPayRefundQueryRequest request = WxPayRefundQueryRequest.newBuilder()
|
.outTradeNo(outTradeNo)
|
.outRefundNo(outRefundNo)
|
.build();
|
// 2.1 执行请求
|
WxPayRefundQueryResult response = client.refundQuery(request);
|
// 2.2 创建返回结果
|
if (!Objects.equals("SUCCESS", response.getResultCode())) {
|
return PayRefundRespDTO.waitingOf(null,
|
outRefundNo, response);
|
}
|
WxPayRefundQueryResult.RefundRecord refund = CollUtil.findOne(response.getRefundRecords(),
|
record -> record.getOutRefundNo().equals(outRefundNo));
|
if (refund == null) {
|
return PayRefundRespDTO.failureOf(outRefundNo, response);
|
}
|
switch (refund.getRefundStatus()) {
|
case "SUCCESS":
|
return PayRefundRespDTO.successOf(refund.getRefundId(), parseDateV2B(refund.getRefundSuccessTime()),
|
outRefundNo, response);
|
case "PROCESSING":
|
return PayRefundRespDTO.waitingOf(refund.getRefundId(),
|
outRefundNo, response);
|
case "CHANGE": // 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者财付通转账的方式进行退款
|
case "FAIL":
|
return PayRefundRespDTO.failureOf(outRefundNo, response);
|
default:
|
throw new IllegalArgumentException(String.format("未知的退款状态(%s)", refund.getRefundStatus()));
|
}
|
}
|
|
private PayRefundRespDTO doGetRefundV3(String outTradeNo, String outRefundNo) throws WxPayException {
|
// 1. 构建 WxPayRefundRequest 请求
|
WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request();
|
request.setOutRefundNo(outRefundNo);
|
// 2.1 执行请求
|
WxPayRefundQueryV3Result response = client.refundQueryV3(request);
|
// 2.2 创建返回结果
|
switch (response.getStatus()) {
|
case "SUCCESS":
|
return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
|
outRefundNo, response);
|
case "PROCESSING":
|
return PayRefundRespDTO.waitingOf(response.getRefundId(),
|
outRefundNo, response);
|
case "ABNORMAL": // 退款异常
|
case "CLOSED":
|
return PayRefundRespDTO.failureOf(outRefundNo, response);
|
default:
|
throw new IllegalArgumentException(String.format("未知的退款状态(%s)", response.getStatus()));
|
}
|
}
|
|
@Override
|
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
|
// 1. 构建 TransferBillsRequest 请求
|
TransferBillsRequest request = TransferBillsRequest.newBuilder()
|
.appid(this.config.getAppId())
|
.outBillNo(reqDTO.getOutTransferNo())
|
.transferAmount(reqDTO.getPrice())
|
.transferRemark(reqDTO.getSubject())
|
.transferSceneId(reqDTO.getChannelExtras().get("sceneId"))
|
.openid(reqDTO.getUserAccount())
|
.userName(reqDTO.getUserName())
|
.transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"),
|
TransferBillsRequest.TransferSceneReportInfo.class))
|
.notifyUrl(reqDTO.getNotifyUrl())
|
.build();
|
// 特殊:微信转账,必须 0.3 元起,才允许传入姓名
|
if (reqDTO.getPrice() < 30) {
|
request.setUserName(null);
|
}
|
|
// 2.1 执行请求
|
try {
|
TransferBillsResult response = client.getTransferService().transferBills(request);
|
|
// 2.2 创建返回结果
|
String state = response.getState();
|
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response)
|
.setChannelPackageInfo(response.getPackageInfo()); // 一般情况下,只有 WAIT_USER_CONFIRM 会有!
|
}
|
if (Objects.equals("SUCCESS", state)) {
|
return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getCreateTime()),
|
response.getOutBillNo(), response);
|
}
|
return PayTransferRespDTO.closedOf(state, response.getFailReason(),
|
response.getOutBillNo(), response);
|
} catch (WxPayException e) {
|
log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e);
|
String errorCode = getErrorCode(e);
|
String errorMessage = getErrorMessage(e);
|
return PayTransferRespDTO.closedOf(errorCode, errorMessage,
|
reqDTO.getOutTransferNo(), e.getXmlString());
|
}
|
}
|
|
@Override
|
protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException {
|
// 1. 执行请求
|
TransferBillsGetResult response = client.getTransferService().getBillsByOutBillNo(outTradeNo);
|
|
// 2. 创建返回结果
|
String state = response.getState();
|
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response);
|
}
|
if (Objects.equals("SUCCESS", state)) {
|
return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()),
|
response.getOutBillNo(), response);
|
}
|
return PayTransferRespDTO.closedOf(state, response.getFailReason(),
|
response.getOutBillNo(), response);
|
}
|
|
@Override
|
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
|
switch (config.getApiVersion()) {
|
case API_VERSION_V3:
|
return parseTransferNotifyV3(body, headers);
|
case API_VERSION_V2:
|
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
|
default:
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
}
|
}
|
|
private PayTransferRespDTO parseTransferNotifyV3(String body, Map<String, String> headers) throws WxPayException {
|
// 1. 解析回调
|
SignatureHeader signatureHeader = getRequestHeader(headers);
|
TransferBillsNotifyResult response = client.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader);
|
TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult();
|
|
// 2. 创建返回结果
|
String state = result.getState();
|
if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
|
return PayTransferRespDTO.processingOf(result.getTransferBillNo(), result.getOutBillNo(), response);
|
}
|
if (Objects.equals("SUCCESS", state)) {
|
return PayTransferRespDTO.successOf(result.getTransferBillNo(), parseDateV3(result.getUpdateTime()),
|
result.getOutBillNo(), response);
|
}
|
return PayTransferRespDTO.closedOf(state, result.getFailReason(),
|
result.getOutBillNo(), response);
|
}
|
|
// ========== 各种工具方法 ==========
|
|
/**
|
* 组装请求头重的签名信息
|
*
|
* @see <a href="https://github.com/binarywang/weixin-java-pay-demo/blob/master/src/main/java/com/github/binarywang/demo/wx/pay/controller/WxPayV3Controller.java#L202-L221">官方示例</a>
|
*/
|
private SignatureHeader getRequestHeader(Map<String, String> headers) {
|
// 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/ICSFL6
|
return SignatureHeader.builder()
|
.signature(getHeaderValue(headers, "Wechatpay-Signature", "wechatpay-signature"))
|
.nonce(getHeaderValue(headers, "Wechatpay-Nonce", "wechatpay-nonce"))
|
.serial(getHeaderValue(headers, "Wechatpay-Serial", "wechatpay-serial"))
|
.timeStamp(getHeaderValue(headers, "Wechatpay-Timestamp", "wechatpay-timestamp"))
|
.build();
|
}
|
|
private String getHeaderValue(Map<String, String> headers, String capitalizedKey, String lowercaseKey) {
|
String value = headers.get(capitalizedKey);
|
if (value != null) {
|
return value;
|
}
|
return headers.get(lowercaseKey);
|
}
|
|
static String formatDateV2(LocalDateTime time) {
|
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
|
}
|
|
static LocalDateTime parseDateV2(String time) {
|
return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
|
}
|
|
static LocalDateTime parseDateV2B(String time) {
|
return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN);
|
}
|
|
static String formatDateV3(LocalDateTime time) {
|
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN);
|
}
|
|
static LocalDateTime parseDateV3(String time) {
|
return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN);
|
}
|
|
static String getErrorCode(WxPayException e) {
|
if (StrUtil.isNotEmpty(e.getErrCode())) {
|
return e.getErrCode();
|
}
|
if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
|
return "CUSTOM_ERROR";
|
}
|
return e.getReturnCode();
|
}
|
|
static String getErrorMessage(WxPayException e) {
|
if (StrUtil.isNotEmpty(e.getErrCode())) {
|
return e.getErrCodeDes();
|
}
|
if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
|
return e.getCustomErrorMsg();
|
}
|
return e.getReturnMsg();
|
}
|
|
}
|