1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 微信公众号开发 微信支付功能开发(网页JSAPI调用)

微信公众号开发 微信支付功能开发(网页JSAPI调用)

时间:2019-09-20 20:18:28

相关推荐

微信公众号开发 微信支付功能开发(网页JSAPI调用)

1、微信支付的流程

如下三张手机截图,我们在微信网页端看到的支付,表面上看到的是 “点击支付按钮 - 弹出支付框 - 支付成功后出现提示页面”,实际上的核心处理过程是: 点击支付按钮时,执行一个Ajax到后台后台通过前台的部分信息(如商品名额,金额等),将其组装成符合微信要求格式的xml,然后调用微信的“统一下单接口”调用成功后微信会返回一个组装好的xml,我们提取之中的消息(预支付id也在其中)以JSON形式返回给前台前台将该JSON传参给微信内置JS的方法中,调其微信支付支付成功后,微信会将本次支付相关信息返回给我们的服务器

这些在《 微信支付官方文档 - 场景介绍》和《 微信支付官方文档 - 业务流程》都进行了更详细的说明。

2、微信支付功能开发详解

2.1 设置支付目录和授权域名

登陆公众号,进行支付相关的目录和域名设置,详情参考《 微信支付官方文档 - 开发步骤》,我这里简单贴几张官方的图就行了,这步比较简单,就不过多说明了,只提其中一点:对于微信支付授权的目录,发起微信支付的页面必须精确地位于授权目录下,假如支付页面为 /wx/pay/a.html,那么授权目录必须为 /wx/pay/,其他的如 /wx/ , /wx/pay/(http和https是不一样的),/wx/pay/(千万别忘了www)都是不行的。填写了这些非法目录无法调起支付。

2.2 组装xml,调用统一下单接口,获取prepay_id

2.2.1 组装xml

点击支付按钮后,写一个Ajax将前台部分信息发送给后台,然后组装xml,调用统一下单接口。该接口在《 微信支付官方文档 - 统一下单》进行了很详细的解释,我在这里进行部分说明:

其他的都比较简单,重要的在于这两个涉及算法的参数,nonce_str 和 sign,这里说明一下:

nonce_str 随机字符串,用于保证签名不可预测 算法:官方建议调用随机数函数生成,然后转为字符串

sign 签名 算法:所有发送或接收的数据按参数名ASCII码从小到大排序,使用键值对形式拼接为字符串(如 key1=value1&key2=value2…)ASCII码的字典排序,可以利用TreeMap帮我们自动实现将拼接好的字符串最后,再拼接上API密钥,即key,得到新的字符串将新的字符串进行MD5加密,并将加密后字符串全部转换为大写

按照以上的这些说明,进行xml的拼装,贴上我自己的测试代码(注:为方便测试,部分数据我直接写入方法了,如body、openId等):

String appId = WeChatAPI.getAppID();String body = "JSAPI支付测试";String merchantId = WeChatAPI.getMerchantID();String tradeNo = String.valueOf(new Date().getTime());String nonceStr1 = SignUtil.createNonceStr();String notifyUrl = "/pay/do/afterPaySuccess.q";String openId = "okAkc0muYuSJUtvMf25UQHnqYvM4";String totalFee = "1";TreeMap<String, String> map = new TreeMap<String, String>();map.put("appid", appId);map.put("mch_id", merchantId);map.put("device_info", "WEB");map.put("body", body);map.put("trade_type", "JSAPI");map.put("nonce_str", nonceStr1);map.put("notify_url", notifyUrl);map.put("out_trade_no", tradeNo);map.put("total_fee", totalFee);map.put("openid", openId);String sign = SignUtil.createSign(map);String xml = "<xml>" +"<appid>" + appId + "</appid>" +"<body>" + body +"</body>" +"<device_info>WEB</device_info>" +"<mch_id>" + merchantId + "</mch_id>" +"<nonce_str>" + nonceStr1 + "</nonce_str>" +"<notify_url>" + notifyUrl +"</notify_url>" +"<openid>" + openId + "</openid>" +"<out_trade_no>" + tradeNo + "</out_trade_no>" +"<total_fee>" + totalFee + "</total_fee>" +"<trade_type>JSAPI</trade_type>" +"<sign>" + sign + "</sign>" +"</xml>";

注意:body参数如果直接填写中文,在调用接口时会出现“签名错误”,要以ISO8859-1编码所以String body = new String("body内容字符串".getBytes("ISO8859-1"));但即便如此,在支付完成后微信推送的“微信支付凭证”中,商品详情中的中文也依然显示的乱码

body参数内容如果包含中文,那么在调用接口时会出现“签名错误”在网上找了很多方法,有了如上删除线部分的方法,但是仍然是有问题的,因为支付成功后的凭证里中文是乱码后来终于在网上各种倒腾,找到了原因,确实是编码问题,但问题不在body是否使用ISO8859-1,而在MD5的加密算法中是否使用UTF-8所以md.update(sourceStr.getBytes("UTF-8"));

两个算法的代码如下:

/*** 生成随机数* <p>算法参考:https://pay./wiki/doc/api/jsapi.php?chapter=4_3</p>* @return 随机数字符串*/public static String createNonceStr() {SecureRandom random = new SecureRandom();int randomNum = random.nextInt();return Integer.toString(randomNum);}/*** 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign* <p>算法参考:https://pay./wiki/doc/api/jsapi.php?chapter=4_3</p>* @param params 需要发送的所有数据设置为的Map* @return 签名sign*/public static String createSign(TreeMap<String, String> params) {String signValue = "";String stringSignTemp = "";String stringA = "";//获得stringASet<String> keys = params.keySet();for (String key : keys) {stringA += (key + "=" + params.get(key) + "&");}stringA = stringA.substring(0, stringA.length() - 1);//获得stringSignTempstringSignTemp = stringA + "&key=" + WeChatAPI.getMerchantKey();//获得signValuesignValue = encryptByMD5(stringSignTemp).toUpperCase();log.debug("预支付签名:" + signValue);return signValue;}/*** MD5加密** @param sourceStr* @return*/public static String encryptByMD5(String sourceStr) {String result = "";try {MessageDigest md = MessageDigest.getInstance("MD5");md.update(sourceStr.getBytes("UTF-8"));byte b[] = md.digest();int i;StringBuffer buf = new StringBuffer("");for (int offset = 0; offset < b.length; offset++) {i = b[offset];if (i < 0)i += 256;if (i < 16)buf.append("0");buf.append(Integer.toHexString(i));}result = buf.toString();} catch (NoSuchAlgorithmException e) {System.out.println(e);}return result;}

2.2.2 调用统一下单接口,获取prepay_id

有了组装好的xml,现在我们直接使用POST方式的请求发送给微信提供的接口就可以了,如果一切顺利,我们会收到微信返回的xml字符串,格式示例如下:

<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx2421b1c4370ec43b]]></appid><mch_id><![CDATA[10000100]]></mch_id><nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str><openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid><sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx11101639507cbf6ffd8b07629950874]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>

其中我们最需要的就是 prepay_id,这个值在后续需要用到。这段过程比较简单,其中提取prepay_id我是用的正则,我直接贴代码好了:

String url = WeChatAPI.getUrl_prePay();String result = NetUtil.sendRequest(url, "POST", xml);String reg = "<prepay_id><!\\[CDATA\\[(\\w+)\\]\\]></prepay_id>";Pattern pattern = pile(reg);Matcher matcher = pattern.matcher(result);String prepayId = "";while (matcher.find()) {prepayId = matcher.group(1);log.debug("预支付ID:" + prepayId);}

2.3 回传参数,调起微信支付JS

2.3.1 回传参数

这时候,已经有了预支付ID,但是后台的处理还没有结束,我们还没有把该有的信息返回给前台。那么前台需要哪些东西呢?《 微信支付官方文档 - 微信内H5调起支付》有详细的解释,这里再贴一下:

有了之前的经验,想必到这里对这些的获取已经没有什么问题了,但是仍然有几个 注意的地方: package的值是 “prepay_id=***”,而不是 "***" 的方式(***表示之前获取的prepay_id)timeStamp注意使用标准北京时间,可以使用Calendar设置Locale为CHINA,因为是秒级所以记得除以1000paySign签名要重新生成,算法还是之前的,但是参数需要除自己以外的appId、timeStamp、nonceStr、package、signType之前xml中参数appid是小写,这里的appId是大写的I

好了,因为前台接受到参数以后会以JSON的形式发送给微信服务器,所以我们这里后台,直接就把这些参数封装到一个JSONObject中就行了,然后转成JSON的形式发给前台。下面贴一下我的测试代码,签名算法和之前一样,我这里就不重复贴出来了:

Date beijingDate = Calendar.getInstance(Locale.CHINA).getTime();

String nonceStr2 = SignUtil.createNonceStr();JSONObject json = new JSONObject();json.put("appId", appId);json.put("timeStamp", beijingDate.getTime() / 1000);json.put("nonceStr", nonceStr2);json.put("package", "prepay_id=" + prepayId);json.put("signType", "MD5");TreeMap<String, String> map2 = new TreeMap<String, String>();map2.put("appId", appId);map2.put("timeStamp", String.valueOf(beijingDate.getTime() / 1000));map2.put("nonceStr", nonceStr2);map2.put("package", "prepay_id=" + prepayId);map2.put("signType", "MD5");String paySign = SignUtil.createSign(map2);json.put("paySign", paySign);String re = json.toJSONString();AjaxSupport.sendSuccessText(null, re);

2.3.2 使用微信内置的JS调起微信支付

前台的调用就很简单了,看下官方给的示例代码:

function onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest', {"appId":"wx2421bk1c4370c43b",//公众号名称,由商户传入"timeStamp":"1395712654", //时间戳,自1970年以来的秒数"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串"package":"prepay_id=u802345jfgjsdfgsdg888","signType":"MD5", //微信签名方式:"paySign":"70EA570631E4B79628FBCS90534C63FF7FADD89" //微信签名},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();}

使用时直接替换掉invoke方法中的参数即可,实际上如果后台直接是传递的JSON字符串到前台,可以直接解析为JS对象作为参数,下面贴我自己的代码:

$().invoke("/pay/do/pay.q", null, function (re) {var result = JSON.parse(re);function onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest', result, function(res){alert(JSON.stringify(res));if(res.err_msg == "get_brand_wcpay_request:ok" ) {//doit 这里处理支付成功后的逻辑,通常为页面跳转}});}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();}});

这里还有个 坑,是iOS和Android系统不同导致的,如上代码: 如果你在 var result = JSON.parse(re); 之前再添加一个用于debug的输出语句 alert(re);

你可以看到传过来的各项参数,其中timeStamp的值是没有双引号的,这会导致在iOS中支付出现错误,提示缺少timeStamp参数

所以为了兼容,必须要将这个转换成字符串,带上双引号:

$().invoke("/pay/do/pay.q", null, function (re) {var result = JSON.parse(re);result['timeStamp'] = result['timeStamp'] + "";function onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest', result, function(res){alert(JSON.stringify(res));if(res.err_msg == "get_brand_wcpay_request:ok" ) {//doit 这里处理支付成功后的逻辑,通常为页面跳转}});}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();}});

另外,在这个页面调试有个小技巧,将微信回调的JS对象序列化为JSON字符串,进行弹窗显示:alert(JSON.stringify(res));

2.4 校验信息的正确性

实际上在完成上面的步骤以后,已经可以进行微信支付了。这最后一步主要是为了确认支付信息的正确性,以及传递给我们本次支付的一些信息,以便业务处理。

支付成功后,微信会将本次支付的相关信息,以流的方式发送给我们指定的url地址,而我们指定的url地址,就是第一次组装xml时 <notify_url> 中填写的地址,下面我们可以先回顾一下:

...String notifyUrl = "/pay/do/afterPaySuccess.q";...String xml = "<xml>" +"<appid>" + appId + "</appid>" +"<body>" + body +"</body>" +"<device_info>WEB</device_info>" +"<mch_id>" + merchantId + "</mch_id>" +"<nonce_str>" + nonceStr1 + "</nonce_str>" +"<notify_url>" + notifyUrl +"</notify_url>" +"<openid>" + openId + "</openid>" +"<out_trade_no>" + tradeNo + "</out_trade_no>" +"<total_fee>" + totalFee + "</total_fee>" +"<trade_type>JSAPI</trade_type>" +"<sign>" + sign + "</sign>" +"</xml>";

而我们要做的,就是接受到这些信息后,进行处理,并对微信服务器做出应答。如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。详情请参考《 微信支付官方文档 - 支付结果通知》

需要做三件事: 解析微信发来的信息,通过重新签名的方式验证信息的正确性,确认信息是否是微信所发return_code和result_code都是SUCCESS的话,处理商户自己的业务逻辑应答微信,告诉它说我们收到信息了,不用再发了(如果不进行应答,则微信服务器会通过一定的策略定期重新发起通知)

过程也很简单,将微信发来的流信息解析出来之后,再次调用之前的签名算法,用计算出来的算法,和微信发来的xml中的签名sign进行对比,如果相同,则说明是微信返回的通知,响应给微信即可。

注意:验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,而是将生成的签名与该sign值作校验。也就是说,微信发来的xml中包含元素sign,该元素内容不参与签名算法之中,而是和最后算法的结果进行比较的,所以传参进行算法的时候不用加入sign值。

好了,现在我们先看下微信发回来的流信息是什么,实际上文档里有说明,就是个xml,我们看下官方的示例:

<xml><appid><![CDATA[wx2421b1c4370ec43b]]></appid><attach><![CDATA[支付测试]]></attach><bank_type><![CDATA[CFT]]></bank_type><fee_type><![CDATA[CNY]]></fee_type><is_subscribe><![CDATA[Y]]></is_subscribe><mch_id><![CDATA[10000100]]></mch_id><nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str><openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid><out_trade_no><![CDATA[1409811653]]></out_trade_no><result_code><![CDATA[SUCCESS]]></result_code><return_code><![CDATA[SUCCESS]]></return_code><sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign><sub_mch_id><![CDATA[10000100]]></sub_mch_id><time_end><![CDATA[0903131540]]></time_end><total_fee>1</total_fee><trade_type><![CDATA[JSAPI]]></trade_type><transaction_id><![CDATA[100440074009030005092168]]></transaction_id></xml>

其中除了sign的值,其他的值需要做成集合进行签名算法,然后结果和sign值对比,相同的话,给微信一个应答,应答的格式官方也给出了示例,如下:

<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>

总之,这一部分还是很简单的,就直接上我的代码了:

/*** 支付成功后的处理* <p>微信支付成功后,对微信返回的信息进行校验</p>* @return*/public String afterPaySuccess() {HttpServletRequest request = ServletActionContext.getRequest();HttpServletResponse response = ServletActionContext.getResponse();TreeMap<String, String> map = new TreeMap<String, String>();try {//解析xml,存入mapInputStream inputStream = request.getInputStream();SAXReader saxReader = new SAXReader();Document document = saxReader.read(inputStream);Element rootElement = document.getRootElement();List<Element> elements = rootElement.elements();String reg = "<!\\[CDATA\\[(.+)\\]\\]>";Pattern pattern = pile(reg);for (Element element : elements) {String key = element.getName();String value = element.getText();Matcher matcher = pattern.matcher(value);while (matcher.find()) {value = matcher.group(1);}map.put(key, value);}//如果微信结果通知为失败if ("FAIL".equals(map.get("return_code"))) {log.debug(map.get("return_msg"));return NONE;}//doit 处理商户业务逻辑//签名对比,应答微信服务器String signFromWechat = map.get("sign");map.remove("sign");String sign = SignUtil.createSign(map);if (sign.equals(signFromWechat)) {String responseXml = "<xml>" +"<return_code><![CDATA[SUCCESS]]></return_code>" +"<return_msg><![CDATA[OK]]></return_msg>" +"</xml>";response.getWriter().write(responseXml);}} catch (IOException e) {e.printStackTrace();} catch (DocumentException e) {e.printStackTrace();}return NONE;}

另外,如果在执行支付流程中,有部分数据希望能放在支付完成后再处理,可以在组装xml的时候放置在attach标签中;然后在支付完成后微信发送来的xml中,会将原数据在此返回。需要注意的是,该attach有字符串的长度限制(详见文档),所以试图直接在支付处理时直接把某个类的JSON格式放进来留做事后处理,是会出错的(我就是这样踩了坑),所以用来传递一些核心数据就行了。

再另外,对于最后这部分,看看微信推荐我们的做法是:当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。另,商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

3、参考链接

微信支付之JSAPI开发第一篇-基本概念

微信公众号支付开发全过程 --JAVA原文链接 /deng-cc/p/7183239.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。