简介与支付接口运行原理
这次介绍的是微信H5支付,会了这个其他类型的也是大同小异,因为调用的都是统一下单接口,只是在某些参数上存在差异。H5支付只能在手机上使用与非微信内使用。这次案例是在真实环境下测试的,并不是沙箱环境。
本次演示的项目基本环境是:SpringBoot,JPA,MySql,Thymeleaf。
注:如果你用的是MyBatis影响不大。
搭建微信支付需要的条件:
服务号(需要公司才能申请),备案的域名,能连接外网的服务器。
运行原理:
首先是一个form表单收集好订单的基本信息如:下单人的账号标识,购买的商品标识,下单的金额,这些是订单的基础。然后我们要自己生成一个商户订单号,这个可以用当前时间戳作为商户订单号。这个时候就将这些信息保存到我们自己的数据库。再将必要的信息传入支付接口(详细下面再介绍)这时候手机就会弹出微信支付的页面,用户完成支付后,点击完成就会返回到支付的页面。微信只有异步回调,支付成功后微信会回调信息到发起订单时我们自己设置的回调链接中(回调链接中的域名需要在服务号中进行绑定才行),在回调方法中签名校验成功就可以认为这是成功的支付,当然回调的时候里面会有你传入的商户订单号,这个就是标识是哪一个订单支付完成了,处理完订单业务后,需要返回成功的标识给接口。
官方给出的运行原理图:
基本配置说明
首先下载微信支付SDK,官方并没有上传SDK到Maven上面,为了安全起见从官网下载SDK并配置好。
下载地址:
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
操作如下图:
下载完微信支付SDK,分析一下目录结构:
上面图片中README.md文件简单说明了用法,但是里面的介绍有滞后性,比如里面介绍的配置类MyConfig,它里面介绍的是实现一个接口,但实际情况是需要继承一个WXPayConfig抽象类并实现里面的抽象方法。
从上面图片的pom.xml文件中,我们发现里面有微信支付SDK的依赖,我将必要的依赖提取出来放在我们自己项目的pom.xml文件中,代码如下:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
我们在项目中新建一个sdk文件夹,将下载好的微信支付SDK的Java文件放在其中,肯定会有报错,只需要修改一下包路径即可,操作如下图:
配置微信支付参数:
微信支付SDK里面有一个WXPayConfig类。我们在sdk文件夹下创建一个MyWXPayConfig类,继承这个WXPayConfig抽象类并实现方法。代码如下图:
package com.heiban.springboot.sdk;
import java.io.InputStream;
public class MyWXPayConfig extends WXPayConfig {
/**
* 获取 App ID
* @return App ID
*/
@Override
public String getAppID() {
return "----根据自己的商户进行填写---";
}
/**
* 获取 Mch ID 商户号
* @return Mch ID
*/
@Override
public String getMchID() {
return "----根据自己的商户进行填写---";
}
/**
* 获取 API 密钥
* @return API密钥
*/
@Override
public String getKey() {
return "----根据自己的商户进行填写---";
}
/**
* 获取商户证书内容
* @return 商户证书内容
*/
@Override
public InputStream getCertStream() {
return null;
}
/**
* 获取WXPayDomain, 用于多域名容灾自动切换
* @return
*/
@Override
IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
}
}
上面的代码中有三个参数需要自己根据商户进行填写 分别是appid(公众账号ID)、mch_id(商户号)、API密钥。
到这里基本的配置操作都已经完成了,可以进行支付功能的开发了。
支付接口实现
注意:在真实环中默认生成的是HMAC-SHA256签名。
支付订单发起核心代码(完整代码可以按照文章末尾的提示获取):
MyWXPayConfig config = new MyWXPayConfig();//配置类
WXPay wxpay = new WXPay(config);//统一下单接口
Map<String, String> data = new HashMap<String, String>();
data.put("device_info", "WEB"); //设备号 PC网页或公众号内支付可以传"WEB"
data.put("nonce_str",WXPayUtil.generateNonceStr());//随机字符串
data.put("body", commodity); //商品描述
data.put("out_trade_no", nowtime); //商户订单号
data.put("total_fee", money); //标价金额 单位是分
data.put("spbill_create_ip", userIp); //用户的客户端IP
data.put("notify_url", "http://heibanyufenbi.xyz/notify_url"); //通知地址
data.put("trade_type", "MWEB"); // 交易类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
try {
Map<String, String> resp = wxpay.unifiedOrder(data);
logger.trace(resp.toString());
logger.trace("需要请求的地址:"+resp.get("mweb_url"));
//跳转URL //redirect_url则为支付后返回的指定页面
String url = resp.get("mweb_url")+"&redirect_url=http://heibanyufenbi.xyz/information.html";
//重定向页面
return "redirect:"+url;
} catch (Exception e) {
e.printStackTrace();
}
- 简单介绍一下统一下单接口,实例化一个MyWXPayConfig配置类,将配置类传入统一下单接口的WXPay的构造方法中,然后就新建一个Map将必要的参数添加到Map中,最后用统一下单对象的unifiedOrder方法并传入Map参数,即可调起支付。
- 其中的spbill_create_ip字段是需要自己获取用户下单的IP地址,详细获取客户端IP地址的代码看文末的提示获取整个项目的源文件。
如果是其他类型的支付Map中还需要添加其他参数,具体可以参看官方文档(API列表)链接地址:
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
还有值得注意的是notify_url字段,这个字段就是支付成功后微信支付回调的地址,该字段传入的域名必须是要备案成功的、在商户中进行绑定过了,在商户中绑定如下图:
订单支付成功处理(完整的代码):
@ResponseBody
@RequestMapping("/notify_url")
public String notify_url(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.trace("支付信息回调中");
String notifyData = "";
try{
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
notifyData = new String(outputStream.toByteArray(), "utf-8");// 支付结果通知的xml格式数据
//关闭流
outputStream.close();
inputStream.close();
logger.trace("获取xml格式数据成功:\n"+notifyData);
}catch (Exception e){
logger.trace("解析回调数据发生异常");
}
MyWXPayConfig config = new MyWXPayConfig();//配置类
Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map
logger.trace("notifyMap = "+notifyMap);
logger.trace("sign = "+notifyMap.get("sign"));
if (WXPayUtil.isSignatureValid(notifyMap,config.getKey(),WXPayConstants.SignType.HMACSHA256)) {//HMACSHA256签名验证方法
//if (WXPayUtil.isSignatureValid(notifyMap,config.getKey())) {//MD5签名验证方法
// 签名正确
// 进行处理。
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
String out_trade_no = notifyMap.get("out_trade_no");//获取商户订单号
String transaction_id = notifyMap.get("transaction_id");//微信支付订单号
//处理标记付款成功
Order order = orderRepository.findByOuttradeno(out_trade_no);
if(order.getTrade_no()==null){
order.setPlay(1);//设置为支付成功
order.setTrade_no(transaction_id);//设置微信支付订单号
orderRepository.save(order);//保存到数据库
logger.trace("回调验证成功 业务代码已经处理");
}
//返回给微信的数据 标志成功
Map<String,String> map = new HashMap<String, String>();
map.put("return_code","SUCCESS");//SUCCESS表示商户接收通知成功并校验成功
map.put("return_msg","OK");
logger.trace("验签成功");
return WXPayUtil.mapToXml(map);
}
else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
//返回给微信的数据 标志失败
Map<String,String> map = new HashMap<String, String>();
map.put("return_code","FAIL");
map.put("return_msg","NO");
logger.trace("验签失败");
return WXPayUtil.mapToXml(map);
}
}
简单介绍一下回调方法,获取返回的XML数据 ,将XML数据转换成Map,校验签名是否正确,如果正确就新建一个Map添加添加return_code与return_msg字段,再通过WXPayUtil.mapToXml(map)转换成XML格式,返回给支付接口,证明这是处理过的,所以需要在方法的头部标@ResponseBody注解。
签名选择与配置
特别注意:
签名(sgin字段):
微信支付有两种签名方式分别是MD5、HMAC-SHA256。在发起订单的时候调用统一下单接口,如果你是在沙箱环境下默认帮用MD5的方式生成签名,但是在真实环境中下默认帮你用HMAC-SHA256生成签名。(看API中 WXPay类 的构造方法得知)。
统一下单接口默认帮你用什么签名方式生成,你就要用什么签名方式去验证签名(在API中 WXPayUtil类中可以选择验证签名的方法)。所以在发起订单的时候其实sign_type(签名类型)这个是不用传递的。下面详细介绍如何切换这两种签名方式。
MD5、HMAC-SHA256这两种签名的方式有什么区别呢?很明显HMAC-SHA256这个更加安全。
如果你想用在真实环境中用MD5签名方式,你需要修改 WXPay类 的构造方法。操作如下图:
如上图中两个箭头,解除注释,将下面的代码注释即可在真实环境中使用MD5签名方式。
MD5与HMAC-SHA256签名方式,在回调方法中的校验签名的方法是不同的。
MD5签名方式在回调方法中应该调用,如下代码进行签名校验(具体代码请看原文件):
if (WXPayUtil.isSignatureValid(notifyMap,config.getKey())) {//MD5签名验证方法
HMAC-SHA256签名方式在回调方法中应该调用,如下代码进行签名校验(具体代码请看原文件):
if (WXPayUtil.isSignatureValid(notifyMap,config.getKey(),WXPayConstants.SignType.HMACSHA256)) {//HMACSHA256签名验证方法
本次例子目录结构如下图:
如果你想搭建该项目 你要做的是:
需要在能连接外网的服务器中,支付域名需要备案,需要有服务号。
搭建Mysql数据库 密码123456 并且创建数据库名称为wxpay的数据库,项目启动后JPA会自动创建数据库表。
你还需要自己配置MyWXPayConfig.java中的appid(公众账号ID)、mch_id(商户号)、API密钥配置