历经三天的爬坑之路,一套完整的 JAVA 版 JSAPI 微信支付和大家分享。
首先看官方文档,其中虽有一点点小差错,但还是很重要,毕竟它是根。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=1_1
准备工作
公众号配置和申请商户申请省略,直接上功能
一、首先下载官方提供的工具类demo
二、把这些工具类Copy到自己的项目中
流程
统一下单 —》 H5调起微信支付 —》 回调参数
场景案例:充值话费流量
场景介绍:
1)点击套餐时,统一下单;
2)下单完成后返回参数给H5页面做为调用微信支付页面的数据;
3)然后跳转到你的微信支付界面(输入密码界面);
4)输入密码支付成功;
5)回调信息用做通知。
一、前台
// 跳转微信支付(单击套餐时)
onBridgeReady() {
// 【1】首先调用后台,统一下单 【对应后台下单方法,在后文有定义】
this.util.postRequest('/wx/unifiedOrder',{}).then(response => {
// 返回信息
let data = response.data.result;
// 【2】如果下单成功
if(data.return_code === 'SUCCESS') {
// 调起支付(此处是自定义工具中封装)
this.util.onBridgeReady(data);
}
});
}
/**
* 调用微信支付(工具js中)
*/
static onBridgeReady(params) {
// 判断对象是否存在
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{
// 【主要功能】在微信手机端浏览器
// 下单时返回的数据,传入后台用做支付使用
var param = { "appId":params.appid, "package":"prepay_id="+params.prepay_id };
// 【1】ajax调用后台,使参数添加完整 【对应后台方法后文有定义】
this.postRequest("/wx/goH5Pay", param).then(response => {
// 整理完的参数
let data = response.data.result;
// 【2】参数传入,微信中H5页面调起支付
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, // 公众号名称,由商户传入
"timeStamp": data.timeStamp, // 时间戳,自1970年以来的秒数 (10位)
"nonceStr": data.nonceStr, // 随机串
"package": data.package, // 统一下单接口返回的prepay_id参数值,提交格式如prepay_id=***
"signType": data.signType, // 微信签名方式类型
"paySign": data.paySign, //微信签名
},
res => {
if(res.err_msg == "get_brand_wcpay_request:ok" ){
//使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
// 如果成功跳转微信支付页面
}
});
});
}
}
二、后台
1.统一下单
(1)配置 MyConfig 继承工具抽象类 WXPayConfig
package com.mesunday.util.pay;
import java.io.*;
/**
* 自定义配置
*/
public class MyConfig extends WXPayConfig{
private byte[] certData;
/**
* 构造方法读取证书, 通过getCertStream 可以使sdk获取到证书
* @throws Exception
*/
public MyConfig() throws Exception {
// 【必填】此处为你申请的证书存放的路径
String certPath = "D:\\cert\\apiclient_cert.p12";
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
// 【必填】个人公众号的appid (此处为假数据)
public String getAppID() { return "wx81107deaedd12"; }
// 【必填】个人申请的商户号 (此处为假数据)
public String getMchID() { return "1536399165"; }
// 【必填】个人商户的API密钥 (此处为假数据)
public String getKey() { return "c439h20196b286876b769fd196b2190k"; }
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
/**
* 【必須實現此方法,否則無法使用】
* @return
*/
public IWXPayDomain getWXPayDomain() {
IWXPayDomain iwxPayDomain = 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);
}
};
return iwxPayDomain;
}
}
(2)统一下单
/**
* 【统一下单】(正是前台调用的统一下单接口)
* controller省略,此处是service
* @return
*/
public Map unifiedOrder() throws Exception {
// 返回信息给前台
Map<String, String> resp = new DataRow<>();
// 自定义的配置用于实现
MyConfig myConfig = new MyConfig();
WXPay wxpay = new WXPay(myConfig);
// 商戶訂單號
String outTradeNo = WXPayUtil.generateNonceStr();
// 添加 map data 数据,对应官方请求参数列表
// 官方:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
DataRow<String, String> mapData = new DataRow();
mapData.put("appid", projectprops.getAppid()); // 公众APPid
mapData.put("mch_id", "1536399165"); // 商户号
mapData.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串32位(工具方法)
mapData.put("body","话费流量支付"); // 商品描述
mapData.put("out_trade_no", outTradeNo); // 商户订单号
mapData.put("total_fee","1"); // 标价金额(單位分)
mapData.put("spbill_create_ip","120.56.220.18"); // 终端IP,公众平台白名单
mapData.put("notify_url", "http://" + projectprops.getDomainurl() + "/api/wx/dealOrder/"+outTradeNo); // 通知地址,支付成功时回调的参数(外网可以访问),这里把订单号传过去
mapData.put("trade_type","JSAPI"); // 交易类型
mapData.put("openid", "ouAmpjl9ZZdSiMNYazgz7Ah8zqw4"); // openid【必填,官方未说明必填】
// 签名需要其它数据信息,最后设置
String sign = WXPayUtil.generateSignature(mapData, "c439h20196b286876b769fd196b2190k");
mapData.put("sign", sign); // 签名
// 调用统一下单
try {
resp = wxpay.unifiedOrder(mapData);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
return resp;
}
下单成功后返回的信息,前台取到需要的参数
2.H5调用微信端支付
其中参数犹为重要,所以这里在后台生成,同时参考官方参数列表
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
/**
* H5支付时需要的参数
* @return
* @throws Exception
*/
public DataRow goH5Pay(DataRow data) throws Exception {
DataRow<String, String> param = new DataRow();
param.put("appId", data.getString("appId")); // 公众号名称,这里是由前台传过来
param.put("nonceStr", WXPayUtil.generateNonceStr()); // 随机串(工具生成)
param.put("package", data.getString("package")); // 统一下单返回的prepay_id,注意官方格式,这里也是由前台传过来
param.put("signType", "MD5"); // 微信签名方式
param.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp())); // 时间戳,单位秒(10位)
// 签名需要其它数据信息,最后设置
String sign = WXPayUtil.generateSignature(param, "c439h20196b286876b769fd196b2190k");
param.put("paySign", sign); // 签名
// 输出参数示例
for(Map.Entry<String,String> p : param.entrySet()) {
System.out.println(p.getKey() + " : " + p.getValue());
}
return param;
}
在添加参数后调用微信支付,如果正常跳到微信支付,则跳过此处。
如果有异常,不能跳转微信支付页面,即官方给的返回为,前台可以用 alert() 输出JSON格式查看错误信息
get_brand_wcpay_request:fail —— 支付失败
异常两种情况:如下
出现图 a:
(1)首先对照自己参数是否真的有问题,主要是签名,使用微信平台测试工具
https://pay.weixin.qq.com/wiki/tools/signverify/
(2)自己生成的签名和官方的明明一样,还是报签名错误,此时做以下修改工具代码
出现图b:
是商户平台上,你当前的H5页面的URL支付目录未授权,所以配置,如下图
如果微信跳转成功如下
3.支付完成会通知,下单时设置的通知路由
这里通知路由查询订单,如下
/**
* 支付成功回調,查询订单信息
* @param outTradeNo 商戶訂單號【下单时传入的订单号】
* @return
*/
@RequestMapping(value = "/dealOrder/{outTradeNo:.+}")
@ResponseBody
public String goH5Pay(@PathVariable String outTradeNo) {
logger.debug("支付处理");
try {
// 自定义配置
MyConfig myConfig = new MyConfig();
WXPay wxpay = new WXPay(myConfig);
// 加入商戶訂單號
Map<String, String> data = new HashMap();
data.put("out_trade_no", outTradeNo);
// 查詢訂單
Map<String, String> resp = wxpay.orderQuery(data);
// 控制台输出,查看数据,如下图【【结合官方文档查看参数含义】】
for(Map.Entry<String,String> p : resp.entrySet()) {
System.out.println(p.getKey() + " : " + p.getValue());
}
} catch (Exception e) {
}
return ResultUtil.resultJsonString("null");
}
其它方法退款查询、下账单查询,与上图账单查询道理相同
作者微信公众号“怪東瓜”,有问题私聊,我们共同探讨实用技术,练出最美身材。