手把手实现微信网页受权和微信支付,附源代码(VUE and thinkPHP)

wechat

github 手把手实现微信网页受权和微信支付,附源代码(VUE and thinkPHP)php

概述前端

公众号开发是痛苦的,痛苦在好多问题开发者文档是没有提到的,是须要你的. 在开发过程当中翻了好多的文档,都是说明其中的一部分问题的,很费时间,因此在此总结大致过程。咱们模拟的是一个支付的商城,在实现购买过程当中基本是把微信公众号最主要模块实现了,其他的功能咱们没有涉及,但应该是举一反三的。vue

咱们叙述的过程是按开发流程进行叙述的,不会是按照开发文档的形式叙述,但愿您能结合微信的开发文档一块儿阅读,固然在流程中咱们会提醒你阅读的部分node

目录

  • 概述
    • [解决的问题]
    • [前端技术栈]
    • [后端技术栈]
    • [基本说明]
  • [开发过程]
    • [0.准备]
    • [1.基本配置]
    • [2.网页受权]
  • JS-SDK
    • [签名]
    • [签名后台]
    • [签名前台]
    • [微信支付]

解决的问题

  • [x] 微信网页受权
  • [x] 公众号支付
  • [x] 公众号分享
  • [x] 公众号扫一扫
  • [x] 微信后台获取webapp(spa-vue)路由,致使 invalid 问题
  • [x] 前端history.pushState()致使ios失效问题
  • [x] 换取微信openID 顺序问题
  • [x] 网页受权后强制登陆官网帐户,全局进行拦截

前端技术栈

vue2 + vuex + vue-router + webpack + ES6/7 + axios + sass + flexmysql

后端技术栈

thinkPHP3.2 + mysql + 阿里云Linux Ubuntulinux

基本说明

开发环境 macOS 10.13.3  nodejs 8.0.0 centOS 7.4webpack

本文中使用的url是m.example.com (demo), 开发过程当中须要替换成你的URL。ios

若有问题请直接在 Issues 中提,或者您发现问题并有很是好的解决方案,欢迎 PRnginx

本着线上线下同样的原则,最好申请两个认证微信公众号,一个是发布使用,一个是本地开发使用。微信自带提供的微信测试功能也不太好用laravel

能够添加群交流 544958637

开发过程

0.准备

请阅读如下微信开发者文档

首页

开发者规范

公众号接口权限说明

全局返回码说明

附:参数说明

appid:公众号惟一标识id(公众号-开发-基本配置中查看)。

secret:公众号开发密钥(初次请保存本地,忘记请重置)。

openid: 每一个微信用户关注此公众号后会生成openid,而且在此公众号中每一个用户得openid是惟一的。

code : code做为换取access_token的票据,每次用户受权带上的code将不同,code只能使用一次,5分钟未被使用自动过时。

IP 白名单:容许访问微信服务器的ip(linux 公网ip 注意若是服务器有CDN加速,CDN请添加白名单)

1.基本配置

此部分对应文档的 入门指引 接入指南

  1. 基础工具

  2. 设置web开发者工具

    开发-开发者工具-web开发者工具设置开发者帐号

  3. 设置IP 白名单

    设置-安全中心-IP白名单设置你服务器的IP,经过开发者ID及密码调用获取access_token接口时,须要设置访问来源IP为白名单。

  4. 设置基本配置-开发者ID

    设置开发者密码(AppSecret)

    image

    咱们获取到的AppSecret (eg) a66b789009df271cde47aaaaaaa

  5. 设置服务器基本配置

    这部的目的是为了和微信服务器创建联系, 经过微信平台实现咱们的业务逻辑。

server.png

详细版:

![image](http://upload-images.jianshu.io/upload_images/2491178-87d4e2f74ea3da4b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

接入微信公众平台开发,开发者须要按照以下步骤完成:

一、填写服务器配置

二、验证服务器地址的有效性

三、依据接口文档实现业务逻辑

下面详细介绍这3个步骤。

**第一步:填写服务器配置**

    * 登陆微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮、。

    * 填写服务器地址(URL)、Token和EncodingAESKey

    * URL是开发者用来接收微信消息和事件的接口URL。

    * Token可由开发者能够任意填写,用做生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)

    * EncodingAESKey由开发者手动填写或随机生成,将用做消息体加解密密钥。

    * 同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会当即生效

    * 加解密方式的默认状态为明文模式,选择兼容模式和安全模式须要提早配置好相关加解密代码

<!-- ![image](http://upload-images.jianshu.io/upload_images/2491178-1f1ad8d62bb39952?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -->

image

第二步:验证消息的确来自微信服务器
如今若是你点击确认 按钮,确定会报认证错误。由于咱们没有作微信认证请求 接收。 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数以下表所示: ​

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串

开发者经过检验signature对请求进行校验(下面有校验方式)。若确认这次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,不然接入失败。加密/校验流程以下:

  1. 将token、timestamp、nonce三个参数进行字典序排序
  2. 将三个参数字符串拼接成一个字符串进行sha1加密
  3. 开发者得到加密后的字符串可与signature对比,标识该请求来源于微信

微信官方提供在文档中提供了PHP原生示例 PHP原生验证demo

Thinkphp 3.2 验证示例

<?php
namespace Home\Controller;//命名空间注意用本身的
use Think\Controller; //引入Think Controller

/**
 * Class IndexController
 * @package Home\Controller
 * @name 微信服务器验证类
 * @author weikai
 */
class IndexController extends Controller {
	//微信服务器接入 
    public function index()
    {
        //这个echostr呢  只有说验证的时候才会echo  若是是验证过以后这个echostr是不存在的字段了
        if($_GET['echostr']){
            $echoStr = $_GET["echostr"];
            if ($this->checkSignature()) {
                ob_clean();//防止以前缓存区数据影响
                echo $echoStr;
                exit; 
            }
        }else{
            $this->responseMsg(); //若是没有echostr,则返回消息
        }
    }
    //验证微信开发者模式接入是否成功
    private function checkSignature()
    {
        //signature 是微信传过来的签名
        $signature = $_GET["signature"];
        //微信发过来的时间戳
        $timestamp = $_GET["timestamp"];
        //微信传过来的值随机字符串
        $nonce     = $_GET["nonce"];
        //定义你在微信公众号开发者模式里面定义的token 这里举例为weixin
        $token  = "weixin";
        //三个变量 按照字典排序 造成一个数组
        $tmpArr = array(
            $token,
            $timestamp,
            $nonce
        );
        // 字典排序
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        //哈希加密  在laravel里面是Hash::
        $tmpStr = sha1($tmpStr);
        //哈希加密后的数据 和微信服务器传过来的签名比较
        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @name 消息接收
     * @author weikai
     */
    public function responseMsg()//执行接收器方法
    {
      //获取微信服务器的XML数据 转化为对象 判断消息类型
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
        if (!empty($postStr)){
            $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            $RX_TYPE = trim($postObj->MsgType);
            switch($RX_TYPE){
                case "event":
                    $result = $this->receiveEvent($postObj);
                    break;
                case "text":
                    $result = $this->handleText($postObj);
                    break;
            }
            echo $result;
        }else{
            echo "";
            exit;
        }
    }

} //classend

**注意:示例代码中 Token 要与微信公众号基本配置中的Token 一致 **

微信公众号基本配置中点击启用配置,若是验证失败多是网络延迟致使,再点击启用多试几回,3次以上不成功,请检查代码。

2.网页受权

若是使用支付功能,必须先受权

你们应该经历过,咱们在公众号打开页面,通常都会弹出一个按钮须要咱们点击赞成才会继续浏览页面

WechatIMG190.jpeg

可是咱们第二次点击的时候是不须要点击受权的,这两个过程是不一样的,是两种受权:(样例不可直接使用,是demo)

  • 非静默受权的 URL 样例:(scope=snsapi_userinfo

请注意加粗部分

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx841a97238d9e17b2&redirect_uri=http://cps.dianping.com/weiXinRedirect&response_type=code&scope=snsapi_userinfo&state=type%3Dquan%2Curl%3Dhttp%3A%2F%2Fmm.dianping.com%2Fweixin%2Faccount%2Fhome

点击`容许`便可带着用户信息跳转到第三方页面,如上图. 
做用: 是用来获取用户的基本信息的
  • 静默受权的 URL 样例: (scope=snsapi_base)

请注意加粗部分

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx841a97238d9e17b2&redirect_uri=http://cps.dianping.com/weiXinRedirect&response_type=code&scope=snsapi_base&state=type%3Dquan%2Curl%3Dhttp%3A%2F%2Fmm.dianping.com%2Fweixin%2Faccount%2Fhome

在微信 web 开发者工具中打开相似的受权页 URL 则会`自动跳转`到第三方页面。
做用:是用来获取进入页面的用户的openid的

注意:俩者受权区别

非静默受权:可获取微信用户基础信息如 用户微信昵称 、城市、语言、头像、关注公众号时间、openid等。

静默受权:用户体验好,用户不知觉间完成受权,但只能够获取到用户得openid。

<hr>

网页受权

请通读微信网页受权

请注意 关于网页受权access_token和普通access_token的区别 章节

具体而言,网页受权流程分为四步:

注意: 步骤一是由前台完成的,前台获取code 以后须要传给后台,由后台完成 2 3 4

1. 引导用户进入受权页面赞成受权后微信跳转回调地址并传递参数code 获取code

2. 经过code换取网页受权access_token(与基础支持中的access_token不一样)

3. 若是须要,开发者能够刷新网页受权access_token,避免过时

4. 经过网页受权access_token和openid获取用户基本信息(支持UnionID机制)

getcode.png

一、引导用户进入受权页面赞成受权,获取code

建议:若是路由由vue管理,建议code由前台获取并发送给后台。 在公众帐号中配置受权回调域名

redirect_uri_config.png

(${redirect_url}看下面)

微信受权是前端发起的,

  • 时机: 是在你须要获取微信信息以前的页面发起请求,固然大部分状况是用户进入webapp 咱们就开始受权,咱们的dome中就是进入立马受权

  • 前台(Vue): 前台作引导到受权页面,使用location.href 就能够实现。受权过程当中会有几回这页面跳转。咱们把受权函数放在 router.beforeEach((to, from, next)=> {}) 函数中,好控制。

  • 跳转: 咱们刚才提到前台须要引导,这个引导路径是须要后台提供,基本的形式是location.href = http://m.example.com/Home/WxSignature/getBaseInfos?redirect_url=${redirect_url}。要注意redirect_url=${redirect_url}${redirect_url} 是告诉后台回调到前台的URL(请encodeURIComponent).

  • 后台(PHP): 前台调到了后台,后台此时须要获取code了。后台要经过 特定的URL(见官网第一步:用户赞成受权,获取code)获取code.

    api参数解释:

    • appid : 请查看本文 参数说明

    • redirect_uri : 回调连接,完成用户受权后微信服务器自动回调得uri,通常为业务首页连接(注意请转义)。

    • response_type : 固定为code 。

    • scope : 受权方式 可选静默(snsapi_base) 或者非静默(snsapi_userinfo)

    • state : 此参数可为业务需求使用,根据业务须要传入。

    • 静默受权方式获取code: https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appid&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect

    • 非静默受权方式获取code:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

/**
     * @name 受权引导后微信会跳转到回调地址并携带code参数
     * @author weikai
     */

public function getBaseInfos(){
    $redirect_url = I('get.redirect_url');//获取前台传递的回调地址
    $app_id = C('WX_APPID');//获取本身公众号的 appid
    $redirect_uri = urlencode($redirect_url);//处理url
    $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=".$app_id."&redirect_uri=".$redirect_uri."&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";
    header('location:'.$url);

}

错误返回码说明以下:

返回码 说明
10003 redirect_uri域名与后台配置不一致
10004 此公众号被封禁
10005 此公众号并无这些scope的权限
10006 必须关注此测试号
10009 操做太频繁了,请稍后重试
10010 scope不能为空
10011 redirect_uri不能为空
10012 appid不能为空
10013 state不能为空
10015 公众号未受权第三方平台,请检查受权状态
10016 不支持微信开放平台的Appid,请使用公众号Appid
  • 前台获取code

    引导用户到受权页面后 微信服务器会根据redirect_uri参数跳转,而且携带code参数和值

code.jpeg

前台截取出code 就能够了,传给后台,到这里基本就能够了。可是咱们一般是用户每次登录都须要进行受权,咱们判断url中的参数就能够实现了。每每咱们也要监听用户是否登陆判断用户是否须要帐号密码登陆也须要在 router.beforeEach((to, from, next)=> {}) 实现,beforeEach()有些复杂了,请你们阅读具体代码。请结合本身具体的业务书写

/**
 * 判断用户是否须要帐号密码登陆,login页面监听
 * @Author   Hybrid
 * @DateTime 2018-02-28
 */
let checkIsLoginGotologin = function(to, next) {
    // isRelation 判断用户微信帐户是否关联官网帐户
    // routeArr 是一些路由是不须要受监听的
    // !res ? 已经受权 :没有受权
  isRelation().then(res => {
    if (routeArr.includes(to.path)) {
      !res ? next('index') : next();
    } else {
      // 没有受权且不是受权页                             // 当在受权的状况下是不容许访问login页面
      (res && to.path !== '/login') ? next('/login'): ((!res && to.path === '/login') ? next('index') : next())
    }
  })
}

/**
 * 获取和推送code
 * @Author   Hybrid
 * @DateTime 2018-02-28
 * @param    {}   url 路径   
 */
let getCodePullCode = async function(url) {
  let mycode = url.substring(url.indexOf('code=') + 5, url.indexOf('state=') - 1);// 前台截取code
  selfStore.set('wechatCodeStr', mycode); // 存储code
  //传送给后台code
  await axios
    .get("/home/WxSignature/getCode", {
      params: {
        code: mycode
      }
    })
    .then(res => {
      //须要登陆
      var res = res.data;
      if (res && res.status === 1) {
        selfStore.set('openId', res.data);//本地存储Openid,也能够不存储。由后台调配

        location.href = `http://m.example.com/?a=1#${location.href.split('#')[1]}`; // 增长a=1 防止支付错误 防止前台死循环
      }
    });
}

/**
 * 全局路由
 * @Author   Hybrid
 * @DateTime 2018-02-28
 */
if (process.env.NODE_ENV == 'production') {
  router.beforeEach((to, from, next) => {
    let url = location.href;
    // 同时判断'a=1' 和code= 防止前台死循环 
    // wechatCode没有 发起受权
    if ((url.indexOf('a=1') < 1) && (url.indexOf('code=') < 1)) {
      let redirect_url = encodeURIComponent(`http://m.example.com#${to.path}`);
      location.href = `http://m.example.com/Home/WxSignature/getBaseInfos?redirect_url=${redirect_url}`;
    } else {
      // 后台重定向页面,受权登陆
      (!(url.indexOf('code=') < 1)) ? getCodePullCode(url): checkIsLoginGotologin(to, next)
    }
  })
}

二、经过code换取网页受权access_token(与基础支持中的access_token不一样)

appid : 请查看本文 参数说明

secret : 请查看本文 参数说明

code :获取到得code。

grant_type : 固定为authorization_code

获取code后,请求如下连接获取access_token以及用户得openid:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=你的appid&secret=你的sercret&code=刚刚获取得code&grant_type=authorization_code
/**
     * 前台传递code,后台存储
     * @Author   weikai
     * @DateTime 2017-11-23
     * @return   [type]     [description]
     */
    public function getCode() {
        $code = I('get.code');//受权用code
        $appid = C('WX_APPID');//你的公众号appid
        $secret = C('WX_APPSECRET');//你的公众号secret
         // 组合获取的url
            $url="https://api.weixin.qq.com/sns/oauth2/access_token?				appid=$appid&secret=$secret&code=$code&grant_type=authorization_code";
            // curl获取access_token 和openid
            $result=$this->curl_get_contents($url);//curl get请求函数请自行百度
            $result=json_decode($result,true);//json转数组
            $data['openid']=$result['openid'];
      	    $data['access_token'] = $result['access_token'];
      		$data['refresh_token'] = $result['refresh_token'];
        if($data){
            return $this->ajaxReturn(show(1,'获取access_token 和openid',$data));//show()方法为自定义封装消息
        }else{
            return $this->ajaxReturn(show(0,'没有access_token 和openid')); 
        }
    }

正确时返回的JSON数据包以下:

{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }

有些数据是须要存储到后台数据库的如openid

参数 描述
access_token 网页受权接口调用凭证,注意:此access_token与基础支持的access_token不一样
expires_in access_token接口调用凭证超时时间,单位(秒)
refresh_token 用户刷新access_token
openid 用户惟一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号惟一的OpenID
scope 用户受权的做用域,使用逗号(,)分隔

错误时微信会返回JSON数据包以下(示例为Code无效错误):

{"errcode":40029,"errmsg":"invalid code"}

其实完成第二步就完成了基本的网页受权可是尚未获取到用户的信息,受权也没有实际做用了。

三、刷新access_token(若是须要)

因为获取用户信息所用得access_token有效时常比较短,若是想获取access_token后间隔时间较长获取微信用户基本信息请请求刷新access_token api

我这里没有采用受权后拉取用户信息的api,而是采用用户信息获取的api。

请求方法

获取第二步的refresh_token后,请求如下连接获取access_token:
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
参数 是否必须 说明
appid 公众号的惟一标识
grant_type 填写为refresh_token
refresh_token 填写经过access_token获取到的refresh_token参数
/**
     * 刷新access_token
     * @Author   weikai
     * @refresh_token 用与刷新access_token的参数
     * @DateTime 2017-11-23
     * @return   [type]     [description]
     */
    public function reAccessToken($refresh_token) {
        $appid = C('WX_APPID');//你的公众号appid
         // 组合获取的url
            $url="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=$appid&grant_type=refresh_token&refresh_token=$refresh_token";
            // curl请求刷新access_token 
            $result=$this->curl_get_contents($url);//curl get请求函数请自行百度
            $result=json_decode($result,true);//json转数组
      	    $data['access_token'] = $result['access_token'];
        if($data){
            return $this->ajaxReturn(show(1,'获取access_token' ,$data));//show()方法为自定义封装消息
        }else{
            return $this->ajaxReturn(show(0,'没有access_token')); 
        }
    }

返回说明

正确时返回的JSON数据包以下:

{ "access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE" }

和上步返回的数据相同,目的就是刷新了access_token 的有效时间,有效期内可使用access_token 和openid获取微信用户信息

四、经过网页受权access_token和openid获取用户基本信息

注意:

这里微信提供了两个不一样的api

1.在微信受权里微信提供拉取用户信息的api(access_token时间限制为五分钟失效)

http:GET

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

参数说明

参数 描述
access_token 网页受权接口调用凭证,注意:此access_token与基础支持的access_token不一样
openid 用户的惟一标识
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

2.微信用户管理提供的获取用户信息api(只须要使用全局access_token有效期2小时,和openid)

推荐使用这个接口获取用户信息

http: GET

https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

参数说明

参数 描述
access_token 全局access_token 有效期7200秒
openid 用户的惟一标识
lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语

若是网页受权参数为snsapi_userinfo(非静默受权),则此时能够经过access_token和openid获取用户信息了。

PHP示例

/**
     * @return mixed
     * @name 使用全局access_token获取用户详细信息 
     * @author weikai
     */
    public function getWxUserInfo($openid){
        $access_token = $this->getAccess_Token();
        $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=".$access_token."&openid=".$openid."&lang=zh_CN";
        $data = $this->cUrl($url);
        $data = json_decode($data);
      if($data){
        return $data;  
      }     
    }

返回说明

正确时返回的JSON数据包以下:

{    "openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
参数 描述
openid 用户的惟一标识
nickname 用户昵称
sex 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知
province 用户我的资料填写的省份
city 普通用户我的资料填写的城市
country 国家,如中国为CN
headimgurl 用户头像,最后一个数值表明正方形头像大小(有0、4六、6四、9六、132数值可选,0表明640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。
privilege 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom)
unionid 只有在用户将公众号绑定到微信开放平台账号后,才会出现该字段。

错误时微信会返回JSON数据包以下(示例为openid无效):

{"errcode":40003,"errmsg":" invalid openid "}
相关文章
相关标签/搜索