跳到主要内容

微信支付架构

背景说明

  1. Javashop电商系统对接了微信推出的全新的微信支付APIv3接口,集成了微信支付全新的SDK,开发起来更加简单便捷。
  2. 微信支付全新的SDK,集成了包含自动签名和验签的 HTTP 客户端、回调处理、加解密库等,无需开发人员手动编写相关代码
  3. 我们还提供了便捷的参数配置功能,只需要在管理平台配置好相关的微信支付参数(如APIv3密钥、证书等),就可以直接使用微信支付。

接口类型

Javashop电商系统一共对接了5种微信支付的接口,分别为:Native支付、H5支付、JSAPI支付、小程序支付和APP支付

支付接口对应的客户端类型如下:

微信支付接口客户端类型官方接口文档
Native支付电脑浏览器(PC-WEB)端产品介绍 | API接口
H5支付手机浏览器端(非微信内置浏览器)产品介绍 | API接口
JSAPI支付微信内置浏览器产品介绍 | API接口
小程序支付微信小程序应用产品介绍 | API接口
APP支付移动端APP应用产品介绍 | API接口

注意:微信小程序应用和微信内置浏览器在使用微信支付付款时,都要先调用 JSAPI下单接口 在微信支付服务后台生成预支付交易单,然后再调起支付;也就是说两种客户端调用的是同一个支付下单接口,只是在调起支付时不一样

微信小程序应用调起支付相关文档:小程序调起支付

微信内置浏览器调起支付相关文档:JSAPI调起支付

SDK接入

微信支付SDK库地址:wechatpay-java (需要科学上网)

在系统中,我们在javashop-core工程下的pom.xml文件中加入了以下maven依赖,这样在程序中我们就可以直接使用微信支付SDK中已经集成好的方法了。

<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.7</version>
</dependency>

初始化配置

在调用微信支付下单接口之前,都要先初始化配置信息,接入SDK后就不需要手动编写计算请求签名和验证应答签名的代码了,直接使用SDK中集成好的方法即可。如下:

    /**
* 构建调用微信支付服务需要的RSA相关配置
* @param wechatPayConfig 微信支付相关配置参数
* @return
*/
protected RSAConfig buildWechatPayRSAConfig(WechatPayConfig wechatPayConfig) {
// 初始化商户配置
RSAConfig config =
new RSAConfig.Builder()
.merchantId(wechatPayConfig.getMerchantId())
.privateKey(wechatPayConfig.getPrivateKey())
.merchantSerialNumber(wechatPayConfig.getMerchantSerialNumber())
.wechatPayCertificates(wechatPayConfig.getWechatPayCertificate())
.build();
return config;
}

WechatPayConfig是我们的微信支付参数配置,其中包含内容如下:

    /** 商户号 */
public String merchantId;

/** 商户API证书私钥内容 */
public String privateKey;

/** 商户API证书序列号 */
public String merchantSerialNumber;

/** 微信支付平台API证书内容 */
public String wechatPayCertificate;

/** 微信支付 APIv3 密钥 */
public String apiV3Key;

/** 应用ID */
public String appId;

通过以下方法,我们可以获取到微信支付在平台管理端配置的参数信息,然后把获取到的参数转换为WechatPayConfig实体对象:

/**
* 读取支付方式在平台端配置的参数信息
* @param clientType 客户端类型 {@link com.enation.app.javashop.model.base.ClientTypeEnum}
* @param pluginId 支付方式唯一标识 微信支付:wechatPayPlugin
* @return
*/
Map<String, String> ThirdPlatformManager.getConfig(clientType, pluginId);

注意:在Javashop电商系统平台管理端配置微信支付参数时,我们是直接上传的相关证书,而不是配置证书内容(具体可参考微信支付参数配置),但是在代码中加载配置时我们用到的是相关证书的内容(商户API证书私钥内容、微信支付平台API证书内容),所以在代码中我们需要先取出存放在数据库中的证书信息,再获取证书内容,代码如下:

    /**
* 获取证书内容
* @param id 证书主键ID
* @return 证书内容
*/
protected String getFileContent(String id) {
try {
//获取证书信息
FileStreamDO fileStreamDO = this.fileStreamManager.get(Long.parseLong(id));
byte[] buffer = fileStreamDO.getStream();
return new String(buffer, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

接口调用

支付调用流程图

image-20230718175603720

重点步骤说明:

  • 步骤1:调用的API包含:交易支付、订单支付、会员充值和店铺升级等等

  • 步骤4:要根据支付插件ID来获取到支付插件信息,代码如下:

        /**
    * 查找支付插件
    *
    * @param pluginId
    * @return
    */
    private PaymentPluginManager findPlugin(String pluginId) {
    for (PaymentPluginManager plugin : paymentPluginList) {
    if (plugin.getPluginId().equals(pluginId)) {
    return plugin;
    }
    }
    return null;
    }
  • 步骤6:在调用支付插件时,还要根据不同的客户端类型来判断具体调用哪个微信支付下单接口

Native支付

时序图

image-20230718114513291

重点步骤说明:

  • 步骤4:通过Native下单接口来创建预支付交易订单
  • 步骤6:Javashop服务端调用微信支付Native下单接口成功后,直接将预支付交易链接(code_url)返回给Javashop客户端,服务端不做生成二维码的操作,由客户端统一进行生成二维码图片
  • 步骤7、8、18、19:生成二维码后,Javashop客户端会在支付完成之前不间断的进行查询支付结果的操作,直到支付完成,然后进行页面刷新

代码展示

在初始化微信支付配置信息完成后,就可以调用接口了,如下:

  try {
// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientType);
// 初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
// 构建service
NativePayService service = new NativePayService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();

//... ...

// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
// 使用微信扫描 code_url 对应的二维码,即可体验Native支付
return response.getCodeUrl();
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("生成微信支付二维码错误", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("生成微信支付二维码错误", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("生成微信支付二维码错误", e);
} catch (Exception e) {
this.logger.error("生成微信支付二维码错误", e);
}

H5支付

时序图

image-20230718151446273

重点步骤说明:

  • 步骤4:通过H5下单API创建支付订单
  • 步骤9和步骤11:由于步骤11属于异步操作,所以9和11的执行顺序不确定先后

代码展示

  try {
// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientType);
// 初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
// 构建service
H5Service service = new H5Service.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();

//... ...

// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
// 返回跳转链接
return response.getH5Url();
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("获取的微信H5支付跳转链接错误", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("获取的微信H5支付跳转链接错误", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("获取的微信H5支付跳转链接错误", e);
} catch (Exception e) {
this.logger.error("获取的微信H5支付跳转链接错误", e);
}

JSAPI支付

时序图

image-20230718152048377

重点步骤说明:

  • 步骤4:通过JSAPI下单接口创建预支付交易订单

  • 步骤8:客户端调起微信支付时,需要用到服务端返回的相关参数,参数包含:

    appId:应用ID

    timeStamp:时间戳

    nonceStr:随机字符串

    package:订单详情扩展字符串 JSAPI下单接口返回的prepay_id参数值,格式如:prepay_id=wx21201855730335ac86f8c43d1889123400

    signType:签名类型,默认为RSA,也仅支持RSA

    paySign:签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值

代码展示

服务端支付下单代码展示:

  try {
// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientType);
// 初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
// 构建service
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();

//... ...

// 调用下单方法,得到应答
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
// 构建返回参数
Map<String, String> result = new HashMap();
result.put("appId", response.getAppId());
result.put("timeStamp", response.getTimeStamp());
result.put("nonceStr", response.getNonceStr());
result.put("package", response.getPackageVal());
result.put("signType", response.getSignType());
result.put("paySign", response.getPaySign());
return result;
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("调用微信JSAPI支付下单接口错误", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("调用微信JSAPI支付下单接口错误", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("调用微信JSAPI支付下单接口错误", e);
} catch (Exception e) {
this.logger.error("调用微信JSAPI支付下单接口错误", e);
}

客户端调起支付代码展示:

function onBridgeReady() {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": "wx2421b1c4370ec43b", //公众号ID,由商户传入
"timeStamp": "1395712654", //时间戳,自1970年以来的秒数
"nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串
"package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
"signType": "RSA", //微信签名方式:
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==" //微信签名
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}

小程序支付

时序图

image-20230718162146568

重点步骤说明:

  • 步骤4:通过JSAPI下单接口创建预支付交易订单

  • 步骤7:客户端调起微信支付时,需要用到服务端返回的相关参数,参数包含:

    appId:应用ID

    timeStamp:时间戳

    nonceStr:随机字符串

    package:订单详情扩展字符串 JSAPI下单接口返回的prepay_id参数值,格式如:prepay_id=wx21201855730335ac86f8c43d1889123400

    signType:签名类型,默认为RSA,也仅支持RSA

    paySign:签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值

代码展示

tip

小程序和微信内置浏览器都是调用的JSAPI支付下单接口

客户端调起支付代码展示:

wx.requestPayment
(
{
"timeStamp": "1414561699",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx201410272009395522657a690389285100",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
"success":function(res){},
"fail":function(res){},
"complete":function(res){}
}
)

APP支付

时序图

image-20230718164654838

重点步骤说明:

  • 步骤4:通过API接口创建预支付交易订单

  • 步骤8:调起微信支付时,需要用到服务端返回的相关参数,参数包含:

    appId:应用ID

    partnerId:商户号

    timeStamp:时间戳

    nonceStr:随机字符串

    prepayId:预支付交易会话ID,例如:wx21201855730335ac86f8c43d1889123400

    packageValue:订单详情扩展字符串,暂填写固定值:Sign=WXPay

    sign:签名,使用字段appId、timeStamp、nonceStr、prepayId等计算得出的签名值

代码展示

服务端支付下单代码展示:

  try {
// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientType);
// 初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
// 构建service
AppServiceExtension service = new AppServiceExtension.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();

//... ...

// 调用下单方法,得到应答
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
// 构建返回参数
Map<String, String> result = new HashMap();
result.put("appId", response.getAppid());
result.put("partnerId", response.getPartnerId());
result.put("prepayId", response.getPrepayId());
result.put("packageValue", response.getPackageVal());
result.put("nonceStr", response.getNonceStr());
result.put("timeStamp", response.getTimestamp());
result.put("sign", response.getSign());
return result;
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("调用微信APP支付下单接口出错", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("调用微信APP支付下单接口出错", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("调用微信APP支付下单接口出错", e);
} catch (Exception e) {
this.logger.error("调用微信APP支付下单接口出错", e);
}

支付异步通知

微信支付通过支付通知接口将用户支付成功消息通知给商户

设置回调URL

在调用微信支付下单接口时,我们就要把异步通知回调地址作为参数放入到请求中,在AbstractPaymentPlugin.java中我们定义了获取异步通知回调地址的方法

    /**
* 获取异步通知url
*
* @param tradeType 交易类型
* @param clientTypeEnum 客户端类型
* @return
*/
protected String getCallBackUrl(TradeTypeEnum tradeType, ClientTypeEnum clientTypeEnum) {
return domainHelper.getCallback() + "/payment/callback/" + tradeType + "/" + this.getPluginId() + "/" + clientTypeEnum;
}

微信支付回调地址格式:https://buyer-api域名/payment/callback/交易类型/wechatPayPlugin/客户端类型。

以PC端Native支付为例

回调地址为:https://api-v730.javamall.com.cn/buyer-api/buyer/payment/callback/TRADE/wechatPayPlugin/PC

支付下单时设置参数如下:

  try {
// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientType);
// 初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
// 构建service
NativePayService service = new NativePayService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();

//设置回调通知地址
String notifyUrl = this.getCallBackUrl(tradeType, clientType);
request.setNotifyUrl(notifyUrl);

//... 其它参数忽略 ...

// 调用下单方法,得到应答
PrepayResponse response = service.prepay(request);
// 使用微信扫描 code_url 对应的二维码,即可体验Native支付
return response.getCodeUrl();
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("生成微信支付二维码错误", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("生成微信支付二维码错误", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("生成微信支付二维码错误", e);
} catch (Exception e) {
this.logger.error("生成微信支付二维码错误", e);
}

通知规则

用户支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理该消息,并返回应答。

对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)

通知参数解密

支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情,如果想得到详情明文数据,就必须对加密结果进行解密操作。

利用微信支付的SDK可以完成解密操作,如下:

    /**
* 微信支付结果回调
* @param clientTypeEnum 支付客户端类型 {@link ClientTypeEnum}
* @return
*/
public String wechatPayCallback(ClientTypeEnum clientTypeEnum) {
// 返回结果
Map<String, String> result = new HashMap<>();

try {
// 获取微信支付回调通知请求
HttpServletRequest request = ThreadContextHolder.getHttpRequest();
// 获取回调通知中的请求参数
// 获取微信支付证书序列号
String serialNumber = request.getHeader("Wechatpay-Serial");
logger.info("回调通知参数serialNumber----->" + serialNumber);
// 获取请求随机串
String nonce = request.getHeader("Wechatpay-Nonce");
logger.info("回调通知参数nonce----->" + nonce);
// 获取签名信息
String signature = request.getHeader("Wechatpay-Signature");
logger.info("回调通知参数signature----->" + signature);
// 获取时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
logger.info("回调通知参数timestamp----->" + timestamp);
// 获取请求主体信息
String requestBody = IOUtil.toString(request.getInputStream());
logger.info("回调通知参数requestBody----->" + requestBody);
// 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNumber)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(requestBody)
.build();

logger.info("回调通知参数requestParam----->" + requestParam.toString());

// 根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(clientTypeEnum);
// 初始化配置信息
NotificationConfig config = new RSANotificationConfig.Builder()
//此处用到的是平台证书,不是商户证书
.certificates(wechatPayConfig.getWechatPayCertificate())
.apiV3Key(wechatPayConfig.getApiV3Key())
.build();

// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);

// 以支付通知回调为例,验签、解密并转换成 Transaction
Transaction transaction = parser.parse(requestParam, Transaction.class);
// 如果交易状态为支付成功,则执行支付成功需要回调的方法
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
// 本商城交易单号
String outTradeNo = transaction.getOutTradeNo();
// 微信支付订单号
String returnTradeNo = transaction.getTransactionId();
// 支付金额
TransactionAmount amount = transaction.getAmount();
// 传回来的是分,转为元
double payPrice = CurrencyUtil.mul(amount.getTotal(), 0.01);

logger.info("支付成功:outTradeNo/returnTradeNo----->" + outTradeNo + "/" + returnTradeNo);

// 调用支付成功方法来处理交易订单信息
this.paySuccess(outTradeNo, returnTradeNo, payPrice);

result.put("code", SUCCESS);
// 标记为成功
cache.put(CACHE_KEY_PREFIX + outTradeNo, "ok", 120);
} else {
result.put("code", "FAIL");
result.put("message", "微信支付回调通知失败");
}

} catch (ValidationException e) {
// 签名验证失败
this.logger.error("微信支付回调通知签名验证失败", e);
result.put("code", "FAIL");
result.put("message", "微信支付回调通知签名验证失败");
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("微信支付回调通知参数不正确,解析通知数据失败", e);
result.put("code", "FAIL");
result.put("message", "微信支付回调通知参数不正确,解析通知数据失败");
} catch (Exception e) {
this.logger.error("微信支付回调通知失败", e);
result.put("code", "FAIL");
result.put("message", "未知错误");
}

return GsonUtil.toJson(result);
}

使用 NotificationParser 解析回调通知。

具体步骤如下:

  1. 使用回调通知请求的数据,构建RequestParam参数。
    • HTTP 头 Wechatpay-Signature
    • HTTP 头 Wechatpay-Nonce
    • HTTP 头 Wechatpay-Timestamp
    • HTTP 头 Wechatpay-Serial
    • HTTP 头 Wechatpay-Signature-Type
    • HTTP 请求体 body。要使用原始报文,不能用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
  2. 初始化 RSANotificationConfig。使用的证书是微信支付平台API证书,而不是微信商户API证书。
  3. 初始化 NotificationParser
  4. 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。

通知应答

异步通知消息处理完毕后,需要返回应答报文,应答报文格式如下:

image-20230717165510868

微信支付退款

退款流程时序图

image-20230719105716666

重点步骤说明:

  • 步骤5:调用微信申请退款API接口申请退款

  • 步骤6:退款结果中的退款状态共有四种:

    SUCCESS:退款成功 CLOSED:退款关闭 PROCESSING:退款处理中 ABNORMAL:退款异常

    如果状态为CLOSED或ABNORMAL,程序会自动判断微信退款失败

  • 步骤9-12:当退款状态为PROCESSING(退款处理中)时,系统会每隔10分钟定时查询一次退款结果(调用微信 查询单笔退款API),然后根据查询的退款结果来判定退款是否成功

退款代码展示

申请退款

    /**
* 微信支付申请退款
*
* @param bill 申请退款参数
* @return
*/
public Map wechatPayRefund(RefundBill bill) {
//退款结果
Map refundResult = new HashMap<>();

try {
//根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(ClientTypeEnum.H5);
//初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
//初始化服务
RefundService service = new RefundService.Builder().config(config).build();
//设置调用申请退款接口的参数
CreateRequest request = new CreateRequest();
//设置原支付交易对应的微信订单号
request.setTransactionId(bill.getReturnTradeNo());
//设置商户退款单号
request.setOutRefundNo(bill.getRefundSn());

//设置退款金额信息
AmountReq amount = new AmountReq();
//设置退款金额(转换为以'分'为单位的整数)
amount.setRefund(StringUtil.toLong(CurrencyUtil.toFen(bill.getRefundPrice()), 0));
//设置原订单金额(转换为以'分'为单位的整数)
amount.setTotal(StringUtil.toLong(CurrencyUtil.toFen(bill.getTradePrice()), 0));
//设置退款币种(目前只支持人民币:CNY)
amount.setCurrency("CNY");
//将退款金额信息放入请求参数中
request.setAmount(amount);

//调用申请退款接口
Refund response = service.create(request);
logger.info("微信支付申请退款状态为:" + response.getStatus());

if (SUCCESS.equals(response.getStatus().name())) {
refundResult.put("refund_status", RefundStatusEnum.COMPLETED.value());
refundResult.put("refund_time", DateUtil.getDateline(response.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ss"));
} else if (PROCESSING.equals(response.getStatus().name())) {
refundResult.put("refund_status", RefundStatusEnum.REFUNDING.value());
} else {
refundResult.put("refund_status", RefundStatusEnum.REFUNDFAIL.value());
}
return refundResult;
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("查询微信支付结果失败", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("查询微信支付结果失败", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("查询微信支付结果失败", e);
} catch (Exception e) {
this.logger.error("查询微信支付结果失败", e);
}

//接口如果调用失败,需要返回正在退款状态,以便于后面再继续查询
refundResult.put("refund_status", RefundStatusEnum.REFUNDING.value());
return refundResult;
}

查询退款结果

    /**
* 查询微信支付申请退款结果
*
* @param bill 申请退款参数
* @return
*/
public Map queryWechatPayRefundResult(RefundBill bill) {
Map result = new HashMap();
try {
//根据支付客户端类型来获取微信支付相关配置信息
WechatPayConfig wechatPayConfig = this.getWechatPayConfig(ClientTypeEnum.H5);
//初始化商户配置
RSAConfig config = this.buildWechatPayRSAConfig(wechatPayConfig);
//初始化服务
RefundService service = new RefundService.Builder().config(config).build();
//设置调用查询申请退款结果接口的参数
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
//设置商户退款单号
request.setOutRefundNo(bill.getRefundSn());

//调用申请退款接口
Refund response = service.queryByOutRefundNo(request);
logger.info("查询到的微信支付申请退款结果为:" + response.toString());

if (SUCCESS.equals(response.getStatus().name())) {
result.put("refund_status", RefundStatusEnum.COMPLETED.value());
result.put("refund_time", DateUtil.getDateline(response.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ss"));
} else if (PROCESSING.equals(response.getStatus().name())) {
result.put("refund_status", RefundStatusEnum.REFUNDING.value());
} else {
result.put("refund_status", RefundStatusEnum.REFUNDFAIL.value());
}
return result;
} catch (HttpException e) {
// 发送HTTP请求失败
this.logger.error("查询微信支付结果失败", e);
} catch (ServiceException e) {
// 服务返回状态小于200或大于等于300,例如500
this.logger.error("查询微信支付结果失败", e);
} catch (MalformedMessageException e) {
// 服务返回成功,返回体类型不合法,或者解析返回体失败
this.logger.error("查询微信支付结果失败", e);
} catch (Exception e) {
this.logger.error("查询微信支付结果失败", e);
}

result.put("refund_status", RefundStatusEnum.REFUNDFAIL.value());
return result;
}