微信公众号中的支付宝支付与微信支付 && 支付宝支付问题(微信bug)

  通常,在微信公众号中的商城都是须要支持微信支付和支付宝支付的,固然,较大的公司对于鹅厂和阿里的站队就不说了,因此这里简单记录一下支付宝支付和微信支付的主要流程。说是简单介绍,这是由于确实不难,由于前端在这方面,包括微信受权登录这一块须要作的都不是不少,而主要的工做量都在后端部分。 php

  

支付宝支付

  不管是支付宝支付仍是微信支付,最开始的步骤固然是将商品列表、商家相关信息、用户remark、运费、总价等等支付须要的信息经过post请求向后端传递,这里介绍支付宝支付,因此假设用户选择的是支付宝支付,那么后端返回的就是一个阿里的url,咱们经过这个url来进行支付宝的支付。 固然, 首先,咱们应该进入一个支付宝引导页面,即指导用户在浏览器中打开,这是由于微信是不支持支付宝进行付款的,因此支付宝须要单独在网页中打开进行支付。注意: 若是真的但愿在微信上使用支付宝付款,可使用微信开发者工具v0.7.0 , 通过测试,这个至少是能够的。  用户在这个支付宝的url中支付完成以后,最终再跳转到公众号的页面上便可。 至于判断微信浏览器的方法,可使用下面这种:html

util.isWeChat = function () {
  var browserInfo = navigator.userAgent.toLowerCase();
  var weChatPattern = /MicroMessenger/i;
  if (weChatPattern.test(browserInfo)) {
    return true;
  } else {
    return false;
  }
}

 

 

 

微信支付

  微信支付较之于支付宝支付稍显复杂,公众号支付开发者文档对此作了详细的说明。此文档的阅读对象固然就是一些涉及微信支付的研发工程师、运维工程师等等了。而且微信支付从2014年开始到目前2017年6月都在不断地迭代更新。前端

 

支付方式vue

   为了对微信支付有更深层次的了解,咱们须要知道下面几种支付方式:web

  • 刷卡支付 --- 即微信里个人钱包中的二维码、条形码的支付方式,主要用在线下的面对面的收银场景。
  • 扫码支付 --- 即商户根据微信支付协议生成的二维码,用户经过扫一扫来进行支付。
  • 公众号支付 --- 这也就是用户在H5页面中使用JSAPI进行支付的场景。这也是使用比较多的场景。
  • APP支付 --- 即在app中使用的支付方式。

  

支付帐户算法

  若是商户但愿使用上面的支付方式的其中一种,就须要向微信进行申请,申请成功以后,微信会发来一封邮件,这个邮件上会记录关于这个支付帐户相关的信息,即提供给商户一些API供其调用来完成公众号内的支付功能。邮件中的参数与将会调用的API有下面的对应关系:后端

 即一个公众号就是一个应用,它就有惟一的appid,这是在申请公众号的时候就会有的,而mch_id是微信支付商户号,这必须是在申请了微信支付功能以后才会有的帐号,用于给商户分配收款帐号。 key就是api秘钥。 Appsecret是appid对应的接口密码,经过这个接口密码可使用access_token调用api了,而后经过OAuth2.0接口获取openid,openid是每个用户在一个公众号中特有的标识,也就是用于标识一个公众号下不一样的用户。api

 

接口规则浏览器

  若是但愿使用微信支付接口,那就就要遵照它所指定的一些规则: 好比使用https协议,采用post方式提交相应数据,而且这些数据必须是XML的格式,签名算法是MD5,申请退款、取消订单也是须要证书的。安全

  而且在调用接口的时候,固然须要传递必要的订单、商品、商铺等相关的参数。 如交易金额默认单位为分,而且不能有小数。

  交易类型也有多种,好比 JSAPI对应的是公众号支付,NATIVE对应的时原生扫码支付,APP为app支付,MICROPY是刷卡支付等等。

  货币类型是CNY,即china yuan。

  咱们采用的时间都是标准的北京时间。 

  时间戳就是js中的1970年1月1日0点0分0秒到目前的秒数。

  订单号是由商户本身定义的不得重复的数字。 从新发起一笔支付要是用原订单号,避免重复支付。

  紧接着就是不一样的银行类型。

 

安全规范

  这一部分是很是重要的一部分。 

1. 签名算法

  首先,舍全部发送或者接收的数据为集合M,将集合M中非空参数值的参数(也是键值对)按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2)的形式拼接成字符串 stringA。 特别注意下面的规则:

  • 参数名ASCII码从小到大进行排序(字典序)。
  • 若是参数值为空则不参与签名
  • 参数名区分大小写。
  • 验证调用返回或微信主动通知签名时所传递的sign参数不参与签名,将生成的签名与该sign值做校验。 
  • 微信接口可能增长字段(即微信本身增长的,不是咱们传递的),验证签名时必须支持增长的扩展字段。 

  紧接着在stringA后面拼接上key(即秘钥)获得stringSignTemp字符串,而后对stringSignTemp进行MD5运算。 将获得的字符串全部字符转化为大写,获得sign值signValue。 

  举例以下:

假设传送的参数以下:

appid:    wxd930ea5d5a258f4f
mch_id:    10000100
device_info:    1000
body:    test
nonce_str:    ibuaiVcKdpRxkhJA

第一步就是按照key=value的方式按照参数名的ASCII字典序进行排序:

stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";

而后拼接api秘钥并使用md5运算:

stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d"
sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"

最后就能够获得要发送的数据了:

<xml>
<appid>wxd930ea5d5a258f4f</appid>
<mch_id>10000100</mch_id>
<device_info>1000<device_info>
<body>test</body>
<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
</xml>

这个就作到了加密。

 

 

2. 生成随机数算法

  即在微信支付的api接口协议中包含字段nonce_str, 主要是保证签名不可预测,推荐生成随机数算法以下: 调用随机数函数生成,将获得的数值转化为字符串。  

 

3. 商户证书

 资金退款、撤销接口须要用到商户证书,商家在申请微信支付成功后,收到的相应邮件后,能够按照指引下载api证书。 商户证书不能放在web服务器虚拟目录, 应该放在有访问权限控制的目录中,防止被他人下载。在普通的网络环境下,http请求存在DNS劫持、运营商插入广告、数据被窃取、正常数据被修改等安全风险。 用户回调接口使用HTTPs协议能够保证数据传输的安全性。 因此微信支付建议商户提供微信支付的各类回调采用HTTPs协议。

 

4. 获取openid

  在关注者与公众号产生了消息交互以后,公众号能够得到关注着的OpenID(加密后的微信号,每一个用户对每一个公众号的OpenID是惟一的。对于不一样的公众号,同一用户的openid不一样)。

  公众号能够经过接口来获取用户的openid,还能够得到用户的昵称、头像、性别、所在城市、语言和关注时间,可是这些都是须要用户受权。

 

 

 

公众号支付

场景介绍

  用户选择商铺,挑选商品,点击购买,调起微信支付控件,用户开始输入支付密码,密码验证经过,支付成功,返回用户页面,显示购买成功(商户自定义),固然,用户也能够吧商品网页的连接生成二维码, 用户扫一扫打开后便可完成购买支付。

  如下是重要的逻辑

  • 用户选购商品后须要发起支付,前端经过JavaScript调用getBrandWCPayRequest(get-获取、brand-新的、WC-wechat、payRequest-支付请求)接口,发起微信支付请求,用户进入支付流程。
  • 用户支付成功并点击完成按钮后,商户的前端会收到JavaScript的返回值,商户能够直接跳转到支付成功的静态页面(即商户自定义的静态页面,能够嵌入相应的返回值)进行展现。
  • 商户后台收到来自微信开放平台的支付成功回调通知标志着该笔订单成功支付

  值得注意的时,后二者显然是不会保证严格的触发时序的,若是用户不点击完成,那么后台固然也会接收到相应的支付成功回调通知。JS API返回值做为触发商户网页跳转的标志,可是商户后台应该只是在收到微信后台的支付成功回调通知后,才作真正的支付成功的处理。

 

开发步骤

 首先须要设置支付目录。

 接着设置受权域名 --- 在统一下单接口中要求必须传入用户的openid, 而获取openid须要在公众平台上获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,不然将获取失败。 

 

获取微信版本号

 微信5.0版本后才加入微信支付模块,因此低于微信5.0的使用用户将没法使用支付功能。 

 

微信内H5调起支付

  在微信路蓝旗中打开H5页面执行JS调起支付,接口输入输出数据格式为JSON。 

  (注意)在微信中有一个 WeixinJSBridge 内置对象,而其余浏览器中是不存在的。

  (注意)在列表中的参数名区分大小写,大小写错误签名验证会失败。

  

 

注:JS API的返回结果get_brand_wcpay_request:ok仅在用户成功完成支付时返回。因为前端交互复杂,get_brand_wcpay_request:cancel或者get_brand_wcpay_request:fail能够统一处理为用户遇到错误或者主动放弃,没必要细化区分。 

 示例代码以下所示:

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

  其中最后if判断的代码是用于支持浏览器不能实现WeixinJSBridge对象的浏览器支持的,而else明显是在调用函数的。

 

前端所须要的只是大概就这么多了,更多请参考官方文档

  

 

 

 

 

支付宝支付问题  

  this.commitOrder(postObj).then(function (response) {
            that.$loading.close();                      
            switch(response.data.code){
                // 支付宝支付方式
                case 74:
                    that.$router.push({ path: '/commodity/payment/AlipayHint', query: { alipay: encodeURIComponent(response.data.data) }})
                    break;

 

vue中支付的接口如上所示,提交订单以后,若是返回值为74,那么用户选择的就是支付宝支付,因为支付宝不能在微信中使用,因此push到支付宝的提示页面,提示页面代码以下所示:

<template>
  <div class="alipay-hint">
    <img src="../assets/img/alipay-hint.png" alt="">
    <MenuFooter></MenuFooter>
  </div>
</template>

<style lang="less" scoped>
  div.alipay-hint {
    background: #eeefef;
    img {
      width: 100%;
      height: 100%;
    }
  }
</style>

<script>
  import MenuFooter from "@components/menu"
  import util from '../utils/js/util'
  export default {
    data: function () {
      return {
        name: 'alipay-hint'
      }
    },
    created () {
      if (!util.isWeChat()) { 
        window.location.href = decodeURIComponent(this.$route.query.alipay);
      }
    },
    components: {
      MenuFooter
    }
  }


</script>

 

即首先进入这个页面,而后判断当前的浏览器是不是微信,若是是微信,那么就什么都不作,显示的是提示在浏览器中打开的图片; 若是不是微信,也就是说在其余浏览器打开,就导航到阿里支付的页面,这样就能够进行支付了。 如今的问题在于:后端返回的数据(即阿里支付的连接)是没有问题的,可是一旦选择在浏览器中打开,并无打开当前的阿里提示支付的页面,因此也就没有打开阿里支付页面,而是跳转到了首页,因而支付宝支付一直是有问题的。 

问题总结:

  从微信中复制到的连接的查询字符串是否含有阿里支付的连接,若是有,那么在浏览器打开以后,在判断了不是微信浏览器以后,就应该打开了支付宝支付,为何没有打开,甚至即便打不开,也应该打开阿里支付的图片啊,为何回到了首页?????

问题猜想以及解决尝试方法:

  1. 是不是由于后端返回的数据根本就不含阿里支付的连接,因此才打不开? 

 验证方法 --- 对于每次返回的数据,alert出来,看看是不是阿里的支付连接?  

   验证结果 --- 返回的结果的确是阿里支付的连接,因此说并非由于后端没有返回正确的数据,此问题猜想排除。

 

  2. 当我进入到支付宝支付页面时,讲到里我经过decodeURIComponent应该是能够拿到传递过来的阿里支付连接的参数,是不是由于这个连接没有正确拿到,因此即便在浏览器中打开,也是打开不了的。

    验证方法 --- 进入支付宝支付页面的时候(在微信中),alert出来 decodeURIComponent 阿里支付连接的值,看是否正常拿到了这个值?

  验证结果 --- 在支付宝支付页面中,alert时, decodeURIComponent的值的确就是后端返回的数据 --- 阿里支付的连接, 此问题猜想排除

 

  3. 通过2能够知道,数据(连接)确实是准确的拿到了的,那么就应该在检测到浏览器不是微信的时候就进入支付连接。那是否是由于在微信中选择在浏览器打开的时候它复制到的连接和实际的连接不一样呢? 

   验证方法 --- 进入支付宝支付页面的时候,先alert出当前的location.href, 而后再亲自复制连接,而后粘贴到别的地方,进行比对。  

 验证结果 --- alert出来的当前的url和copy出来的url差异很大!!!  在实际的url中,是http://xxx.xxx.com/index.html?84654685465#/dfjlajfoasjfoa8653465fdasl; 在copy获得的url中,只copy到了前面一部分,http://xxx.xxx.com/index.html?84654685465, 把锚点以后的省略了,而且用=来代替锚点以后的全部内容。 

 问题 --- 也就是说微信中在copy的时候并无copy到全部的url,而是截取了一部分,把锚点给删除了!

 再次验证 ---  把vue中的其余页面(微信中)在浏览器打开 --- 问题一样 --- 即打开后都没有获得对应的url,而是都把锚点去掉了,在浏览器打开时获得的都是相同的index.html, 而没有复制到锚点。 

    

  4.  对于3的问题追踪

  可是对于其余的页面是否也是相同的问题呢?  我尝试着打开了vuejs官网,而后进入了api页面,发现这里面的每个知识点也是使用锚点定位的? 因此问题并无出在这里。 那么为何没有copy上呢? 

  通过尝试,发现了这样的一个问题,对于下面的连接:

www.biangou.cn/hat2/index.html?495452482fbv56a3dc2130aba8b32fbv549479711

 

  进入以后,而后点击不一样的路由,即这时应该是带有锚点的, 可是在copy以后,发现锚点并无被复制下来。 可是若是咱们使用下面的连接进入。

www.biangou.cn/hat2/index.html?495452482fbv56a3dc2130aba8b32fbv549479711#

 

  这样,若是在使用锚点,那么复制的时候,锚点就能够复制下来了。

  

 

 

  微信中的bug  

 

在微信中, 咱们能够尝试输入 vuejs.org ,而后进入页面以后找到api页面,而后点击一个标题,这时url中应该是添加了一个#的,即锚点定位,可是实际上却没有。在除了微信以外的浏览器中这样的作法是能够实现的。

可是咱们若是想要准确记录下微信中的锚点,即copy到而且能够在浏览器中打开,那么解决这个问题是很是必要的,咱们只要在url的最后添加一个#便可,这样,带着#号进入就能够解决到全部的问题了。