`
brandNewUser
  • 浏览: 446168 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

微信公众号支付整体流程记录备忘

阅读更多

 

 

相比支付宝支付,微信公众号支付的实现以及过程真的是比较复杂,而且坑多,都是血泪史。
 
首先,需要登录微信公众平台,https://mp.weixin.qq.com
 
查看微信支付的开发配置,这里就可以看到对应的支付授权目录以及测试目录,可以选择使用线上作为支付测试,但是不推荐。使用测试授权目录时,注意需要设置测试白名单,规定哪些人可以进行支付测试。
 


 
 
 
当然,我们有微信公众号,就肯定也便拥有了H5网站。公众号支付采用的支付方式属于JSAPI方式,查看JSAPI网页支付是否已经开通了权限,并配置好支付授权目录,该目录必须是发起支付的页面的精确目录,子目录下无法正常调用支付。
 
根据微信支付的文档,商户系统和微信支付系统主要交互:
 
  1. 商户server调用统一下单接口请求订单,api参见公共api【统一下单API】
  2. 商户server接收支付通知,api参见公共api【支付结果通知API】
  3. 商户server查询支付结果,api参见公共api【查询订单API】

 

统一下单API

 
参考公众号支付中的统一下单相关文档:
 
商户系统先调用该接口在微信支付服务后台生成预支付交易单,商户订单号为商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号,由商户自定义生成,微信支付要求商户订单号保持唯一性(建议根据当前系统时间加随机序列来生成订单号)。重新发起一笔支付要使用原订单号,避免重复支付;已支付过或已调用关单、撤销(请见后文的API列表)的订单号不能重新发起支付。
 
统一下单流程完成后,最主要是根据前几步骤生成的相关参数,获取对应的PrepayId,
 
请求的url为统一下单api: 
 
 
请求的参数:
<xml>
     <appid><![CDATA[wx1exxxx]]></appid>
     <body><![CDATA[JSAPI_payment_test]]></body>
     <mch_id>1242312122</mch_id>
     <nonce_str><![CDATA[6aghlqz18duhfebole531dce0r7bw0td]]></nonce_str>
     <notify_url><![CDATA[http://xxxx.com/xxx]]></notify_url>
     <openid><![CDATA[ogGCluNRaxBTNFWZzS_kH-rRez_Q]]></openid>
     <out_trade_no><![CDATA[nraxbtnfwzzskhrrezq1434590817259]]></out_trade_no>
     <spbill_create_ip><![CDATA[119.161.230.131]]></spbill_create_ip>
     <total_fee>1</total_fee>
     <trade_type><![CDATA[JSAPI]]></trade_type>
     <sign><![CDATA[F415B11A1C1B4894085FD703CBD14B71]]></sign>
</xml>
 
 
将参数以POST方式发送给统一下单URL,返回值仍然也是xml格式:
 
 
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[xxx]]></appid>
<mch_id><![CDATA[1212]]></mch_id>
<nonce_str><![CDATA[amxU3MOLatSWVzua]]></nonce_str>
<sign><![CDATA[E458BE2C4F23C6F22B7561E74F41DEEF]]></sign><result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201506180927207ee0b107300739613144]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
 
 
我们只需获取其中的prepay_id即可;如果没有获得,直接返回错误信息。
 
上面所说的参数中,appid和mch_id属于公众号以及商户号id,申请公众号以及开通支付时就已经确定;notify_url属于微信支付服务器向服务端回调的接口(后续会用到);spbill_create_id是用户的ip;total_fee是付款总额;trade_type如果是公众号支付写死为JSAPI;out_trade_no是商户订单号,可在服务端通过文档中的算法生成即可;nonce_str是本次请求支付的随机字符串,最多32位。
 
不确定的就只有两项:
 
openid, 关于公众号中如何获取openid可以查看相关文档,在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。
 
在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的开发者中心页配置授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html,授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。
 

获取Openid

 
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(服务号获得高级接口后,默认拥有scope参数中的snsapi_base和snsapi_userinfo),引导关注者打开如下页面:
 
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
 
若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。
 
公众号支付首先要设置微信支付的链接,通过该链接,就可以调用到微信后端以及服务端,支付的链接格式如下:
 
https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri={{url}}%2Fwxpay%2FpayModelAndView?parameterName=${xxxx}&response_type=code&scope=snsapi_base&state=123#wechat_redirect
 
 
scope设置为snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),虽然是snsapi_userinfo的时候可以获取更多信息,但需要弹出授权界面,而且我们也不需要获取那么多信息。
 
实际上需要微信服务器进行回调才能实现,而回调的redirect_uri为:
 
${url}/wxpay/payModelAndView?
 
 
其中appid即为注册的微信公众服务号ID,url参数即为当前网站的url,并带上coach_product_id,传送给回调地址,url需要进行URLDecoder,微信服务器会回调该服务。
 

通过code换取网页授权access_token

 
首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
 
尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。
 
redirect_url中就可以获取到对应的request Parameter,其中的code就是我们需要的编码。
 
获取code后,请求以下链接获取access_token: 
 
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
 
注意此URL必须在微信浏览器中打开,redirect_uri设置为当前controller方法对应的restful接口。
 
发送过来的code可以通过request.getParameter(“code”)来获取,如果没有生成该code,不能继续进行。
 
如果请求失败,记得别让用户还能重复使用这个code调用后台代码。也即,到支付页面后不能刷新。
 
返回的数据为json格式,如下:
 
{
   "access_token": "OezXcEiiBSKSxW0eoylIeGhaJjUxzVpRR4o6hX-jAhOn160_GRNWPwzcWR_QSO4gbjzWHPV6zuNazuJp3spc2gptHLcR-g2QetMKeDGZ3IJD6PbJCf2YKyw6k4aeiFbdJgfJgNBXKfZ0dPb98IKR_w",
   "expires_in": 7200,
   "refresh_token": "OezXcEiiBSKSxW0eoylIeGhaJjUxzVpRR4o6hX-jAhOn160_GRNWPwzcWR_QSO4g7r7Y2BQy_p7bmrjxH8YN3scFXn7C4fUnNn9AFDcz_qW5ErAi4Lp9p18PcLv60yUtOBSwd8MfDIKap12lVExOAg",
   "openid": "ogGCluNRaxBTNFWZzS_kH-rRez_Q",
   "scope": "snsapi_base"
 }
 
其中,我们只需要获取其openid,然后进行下一步操作。
 

进行签名:sign

 
就剩下一个参数了sign,属于签名,关于签名的算法见文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3,算法用java进行实现如下:
 
 
public String getSign(Map<String, String> items, String APISecret) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        Map<String, String> tmp = new TreeMap<String, String>(items);
        StringBuilder sb = new StringBuilder();
        for(Map.Entry<String, String> entry : tmp.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            if(StringUtils.isEmpty(value)) continue;
            sb.append(key).append("=").append(value).append("&");
        }
        sb.append("key=").append(APISecret);

        return publicService.padStr(new BigInteger(1, MessageDigest.getInstance("MD5").digest(sb.toString().getBytes(CharEncoding.UTF_8))).toString(16).toUpperCase(), "0", 32);
    }
 
 
使用TreeMap对key进行字符串字典排序,附加上对应的key,后进行MD5计算并拍成32位并补零然后转成大写。
 
微信提供相关接口在线签名验证工具:https://pay.weixin.qq.com/wiki/tools/signverify/
 
有了签名就可以进行统一下单相关操作了。
 
生成公众号支付接口所使用的jsapi调起支付的所有参数,返回给前端。参考微信公众平台相关文档:
 
 
 
其中package使用的是上一步的prepay_id=?,本系统中paySign采用MD5算法,其中的paySign采用统一签名生成算法来计算完成。
 

保存至服务端本地/数据库,页面发起支付

 
将用户信息,产品信息,生成的订单保存至数据库,以便在我方能够查询到该记录。
 
//将userId和out_trade_no等信息写入payment_result表
paymentPublicService.insertStubToPaymentResultTable(userId, PaymentResult.CHANNEL.WEIXIN, coachProductId, outTradeNo);
 
在从服务端转到页面上之后,再发起支付调用,跳转至付款页面
 
微信支付需要在回调之后跳转至付款页面(通过调试发现,这个最终付款界面还是必须存在的)。
 
页面中会调用真正的付款功能。
 
$(function(){
  alert("xxxxxxxx");
  callPay();
  function onBridgeReady(){
    WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
              "appId" : '${appId}',           //公e众号名称,由商户传入
              "timeStamp": '${timeStamp}',    //时间戳,自1970年以来的秒数
              "nonceStr" : '${nonceStr}',     //随机串
              "package" : '${package1}',      //预支付ID参数
              "signType" : '${signType}',     //微信签名方式:
              "paySign" : '${paySign}'        //微信签名
            },
            function(res){
              if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                alert("支付成功");
                window.location.href="/student/student_booking";
              }else if(res.err_msg == "get_brand_wcpay_request:cancel" ){
                alert("支付过程中用户取消");
                window.location.href="student_pay.jsp";
              }else{
                alert('支付失败');
                window.location.href="student_pay.jsp";
              }
            }
    );
  }
  function callPay(){
    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();
    }
  }
})
 
 
生成完成之后,就可以进行支付,支付完成后,微信服务端就会通过设置的notify_url来进行回调通知,此时数据库端的订单信息就可以填充完整。
 

支付结果通知

 
 
发送过来的Request,得到对应的xml
 
       
 String xml = IOUtils.toString(request.getInputStream(), CharEncoding.UTF_8);
 
 
商户处理后同步返回给微信参数,根据回调通知API,需要返回如下xml,才能让微信服务器确认已经接受到notify消息,否则微信服务器会多次retry调用我们的接口:
 
<xml>
     <return_code><![CDATA[SUCCESS]]></return_code>
     <return_msg><![CDATA[OK]]></return_msg>
</xml>
 
如果在服务端主动查询订单,可以查看对应的文档来进行操作,这里坑比较少就不详细说明了:
 
 
 

 

 
  • 大小: 42.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics