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


    相比支付宝支付,微信公众号支付的实现以及过程真的是比较复杂,而且坑多,都是血泪史。
     
    首先,需要登录微信公众平台,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>
     
    如果在服务端主动查询订单,可以查看对应的文档来进行操作,这里坑比较少就不详细说明了:
     
     
     
     
  • 相关阅读:
    面向复杂应用,Node.js中的IoC容器 -- Rockerjs/core
    一步步学会用docker部署应用(nodejs版)
    nodeEE双写与分布式事务要点一二
    提升node.js中使用redis的性能
    puppeteer实现线上服务器任意区域截图
    Nodejs“实现”Dubbo Provider
    TypeScript入门教程
    node.js与比特币(typescript实现)
    关于首屏时间采集自动化的解决方案
    回顾2017,未来仍需要不停充电
  • 原文地址:https://www.cnblogs.com/mmaa/p/5789874.html
Copyright © 2020-2023  润新知