微信公众号支付踩坑记
前两周做微信H5支付,在浏览器端用的,天真地以为app挂到公众号中也能用,结果不行>"<|||| ,只好再对接一次公众号支付,微信的支付对接下来总体感觉就是封装地不如支付宝,文档不完善,坑贼多。本文会主要关注对接过程中所遇到的问题,以及部分实现代码。
1.介绍
公众号支付(JSAPI支付)是指用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块来完成支付,适用于在公众号、朋友圈、聊天窗口等微信内完成支付的场景。注意公众号支付必须在微信环境中才能生效,在普通浏览器中是不能用的。
现有微信支付的接口都分为普通商户版、服务商版、银行服务商版,普通商户就是单个商户,服务商版就是一个服务商下面可以挂很多子商户,我这边对接的是服务商版。
公众号的对接过程大致分为申请公众号、提交资料、签署协议、绑定场景等流程,其中第四步绑定场景是登录公众平台,确认商户号和公众号的绑定关系,这里具体可以参考微信文档。
这里需要注意区分公众号和商户号:
公众号:微信公众号是开发者或商家在微信公众平台上申请的应用账号,该帐号与QQ账号互通,通过公众号,商家可在微信平台上实现和特定群体的文字、图片、语音、视频的全方位沟通、互动
商户号:指财付通的支付系统为公司配置的用来存储公司的身份信息、交易信息并处理公司的交易指令的账号。微信支付商户号将直接与公司提供的合法银行卡账户绑定,公司的银行卡账户将根据微信支付商户号的交易情况做相应的资金扣划或归集。说白了就是微信分配用于支付的账户。
2. 实现
本文不会过多的讲述微信公众号支付实现代码(部分代码可参考微信h5支付),主要从前期配置(这个很重要)和遇到问题这两方面来论述。
2.1 前期配置
涉及到的域名配置需要分别登陆公众平台和商户平台进行配置。
在商户平台登陆商户号之后,在产品中心--开发配置中可以设置支付授权目录,这里设置的是在发起支付时使用的域名,如果不设置在发起支付时会报商家存在未配置的参数,请联系商家解决错误。设置如下:
在公众平台登陆公众号后,需要在开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名,按照微信的要求改就行了。至于为什么微信要设置两个域名,而且分别是在公众平台和商户平台都要设置,这里在说一下,一个是用于获取openid时需要的,一个是支付时需要的,微信要求的没办法,为什么不在一个账号里面设置?那就要问微信了--开发配置页面迁移至商户平台。
注意,这里的配置一定要重视,不然后面获取openid和支付时通不过!!!
2.2 获取openid
公众号支付必须传用户的openid,在服务商模式下,可传openid(用户标识)或者sub_openid(用户子标识),如果选择传sub_openid,则必须传sub_appid。用户标识其实用户在商户appid下的唯一标识,传openid就用appid获取,传sub_openid则需要通过sub_appid来获取。
由于我这里只是获取用户的openid,并没有获取用户信息,所以分为2步,具体可以参考微信网页授权。
a 用户同意授权,获取code
public RestResult<String> auth(@RequestBody TradeVo tradeVo){ String redirectUrl = ""; //注意,此处的域名需要填写前面在公众号平台设置的授权域名 JSONObject jsonObject = (JSONObject) JSONObject.toJSON(tradeVo); String state = jsonObject.toJSONString(); try{ redirectUrl = URLEncoder.encode(redirectUrl,"UTF-8"); state = URLEncoder.encode(state,"UTF-8"); }catch (Exception e){ return new RestResult<String>(new ServiceException(e)); } String auth = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx468c78f63c050017&redirect_uri="+redirectUrl+"&response_type=code&scope=snsapi_base&state="+state+"#wechat_redirect"; return new RestResult<>(auth); }
这里主要是后端将一些业务参数也一起封装到state字段里面了,可以在下一次重定向时使用。该方法返回的是一个url地址,前端收到后端返回后直接跳转到这个地址上,微信会进行授权生成code并将刚才的自定义字段state一起放在一个url后面(就是代码中指定的回调redirect_uri),然后页面会自动重定向到这个地址,我这里的是一个中间页面,这个页面里面会把code和state解析出来再调用后端另外一个接口,就是等下的第2步。。。
b 通过code换取网页授权access_token
public RestResult getOpenId(@RequestParam("code") String code, @RequestParam("state") String state){ RestResult restResult = null; TradeVo tradeVo = null; try{ getopenId(code); tradeVo = ((JSON)JSON.parse(URLDecoder.decode(state,"UTF-8"))).toJavaObject(TradeVo.class); }catch (Exception e){ return new RestResult(new ServiceException(e)); } // 此处才会调用微信统一下单接口,代码可以参考我的另一篇博客微信h5支付
return restResult; }
private void getopenId(String code){
String host = "https://api.weixin.qq.com";
String path = "/sns/oauth2/access_token";
Map<String, String> headers = new HashMap<>();
Map<String, String> querys = new HashMap<>();
querys.put("appid",""); //公众号appid,可以是服务商appid,也可以是子商户sub_appid
querys.put("secret",""); //和上面公众号appid对应的公众号的appKey
querys.put("code",code);
querys.put("grant_type","authorization_code");
String result = null;
try{
HttpResponse response = HttpUtils.doGet(host,path,"",headers,querys);
HttpEntity entity = response.getEntity();
if(entity != null){
result = EntityUtils.toString(entity,"UTF-8");
System.out.println(result);
}
JSONObject jsonObject = JSON.parseObject(result);
//这一步就可以获取到openid了,接下来你需要保存下来,在调用微信统一下单接口时这个参数是必传的
String openid = String.valueOf(jsonObject.get("openid"));
// TODO 保存openid,可以存session,也可以放缓存,甚至可以放数据库,随你一 一+
}catch (Exception e){
logger.info("oh !!! 获取openid失败");
throw new ServiceException("获取openid失败" + e.getMessage());
}
}
这里主要是通过上一步获取到的code,通过http请求的方式调用微信后台接口获取一个特殊的网页授权access_token,从中获取到openid,详见微信网页授权。关于HttpUtils这个网上有很多的资源。获取到openid后你需要先保存起来(为什么,因为作用域的问题),然后再去调用微信统一下单接口,生成预订单,然后将相关参数返回给前端,由前端来发起支付请求,调起微信支付,这部分可以参考微信内H5调起支付。
以上两步实际执行过程中是调来调去,中间经过了多次前后端交互,但是都是对用户无感的。
3. 遇到问题
3.1 点击支付之后,没反应
这种问题其实不太好排查,因为是在手机端,不能f12(你懂的),后来是通过一堆alert将信息打印出来,才找到了问题。直接说结果吧,是由于WeixinJSBridge这个微信浏览器的内置对象还未加载完成,导致其invoke方法不能调用,自然就不能调起微信支付了。解决的办法就是搞了一个定时器,通过typeof window.WeixinJSBridge != "undefined"来判断WeixinJSBridge是否加载进来,加载好了再调微信支付,代码如下:
var test = setInterval(function(){ if (typeof window.WeixinJSBridge != "undefined"){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":json.appId, "timeStamp":json.timeStamp, "nonceStr":json.nonceStr, "package":json.package, "signType":json.signType, "paySign":json.paySign }, function (resp) { if (resp.err_msg === 'get_brand_wcpay_request:ok') { Toast('微信支付成功') _this.$router.go(-2); } else if (resp.err_msg === 'get_brand_wcpay_request:cancel') { Toast('用户取消支付') _this.$router.go(-1); } else if (resp.err_msg === 'get_brand_wcpay_request:fail') { Toast('网络异常,请重试') _this.$router.go(-1); } } ); clearInterval(test) } }, 100)
3.2 下单账号与支付账号不一致,请核实后再支付怎么解决?
原因:请求支付的 openid 和调用起支付时用户的 openid 不一致
解决办法:传入的 openid 可以实时获取,获取支付用户的 openid 和调用微信统一下单接口时传的 openid 需要保证一致,不一样则会在微信支付界面出现上面的错误提示。
3.3 微信公众号支付出现:“当前页面的URL未注册”
点击支付按钮出现“当前页面的URL未注册”的提示,这是由于未在商户平台中设置支付授权目录导致的。登录微信商户平台-产品中心-开发配置,配置支付授权路径。
3.4 {"errMsg":"chooseWXPay:fail"}
出现这个问题目前发现有两个原因可能导致:
1. 未设置授权回调域名,需要在开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。
2. 是否这个时候还在pc端的微信开发者工具上测试,有时这也会导致这个问题,那就想办法在手机上测试吧。
3.5 get_brand_wcpay_request:fail
错误如下,这个报错信息代表调起支付失败,原因同3.3。
4. 总结
因为之前有h5对接的经验,所以调用统一下单接口是使用的之前代码(部分参数需要修改),主要是获取openid和配置域名,整个过程出的问题大部分还是自己去找的,微信文档中关于这方面的并不多,其实写的问题并不代表全部,但是整个过程都是自己一点一点做的。最后附上一张成功调起支付的图。最后祝大家国庆节快乐。