github 手把手實現微信網頁受權和微信支付,附源代碼(VUE and thinkPHP)php
概述前端
公衆號開發是痛苦的,痛苦在好多問題開發者文檔是沒有提到的,是須要你猜
的. 在開發過程當中翻了好多的文檔,都是說明其中的一部分問題的,很費時間,因此在此總結大致過程。咱們模擬的是一個支付的商城,在實現購買過程當中基本是把微信公衆號最主要模塊
實現了,其他的功能咱們沒有涉及,但應該是舉一反三的。vue
咱們敘述的過程是按開發流程
進行敘述的,不會是按照開發文檔
的形式敘述,但願您能結合微信的開發文檔一塊兒閱讀,固然在流程中咱們會提醒你閱讀的部分node
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
請閱讀如下微信開發者文檔
附:參數說明
appid:公衆號惟一標識id(公衆號-開發-基本配置中查看)。
secret:公衆號開發密鑰(初次請保存本地,忘記請重置)。
openid: 每一個微信用戶關注此公衆號後會生成openid,而且在此公衆號中每一個用戶得openid是惟一的。
code : code做爲換取access_token的票據,每次用戶受權帶上的code將不同,code只能使用一次,5分鐘未被使用自動過時。
IP 白名單:容許訪問微信服務器的ip(linux 公網ip 注意若是服務器有CDN加速,CDN請添加白名單)
基礎工具
設置web開發者工具
在開發-開發者工具-web開發者工具
設置開發者帳號
設置IP 白名單
在設置-安全中心-IP白名單設置你服務器的IP
,經過開發者ID及密碼調用獲取access_token接口時,須要設置訪問來源IP爲白名單。
設置基本配置-開發者ID
設置開發者密碼(AppSecret)
咱們獲取到的AppSecret (eg) a66b789009df271cde47aaaaaaa
設置服務器基本配置
這部的目的是爲了和微信服務器創建聯繫, 經過微信平臺實現咱們的業務邏輯。
詳細版:  接入微信公衆平臺開發,開發者須要按照以下步驟完成: 一、填寫服務器配置 二、驗證服務器地址的有效性 三、依據接口文檔實現業務邏輯 下面詳細介紹這3個步驟。 **第一步:填寫服務器配置** * 登陸微信公衆平臺官網後,在公衆平臺官網的開發-基本設置頁面,勾選協議成爲開發者,點擊「修改配置」按鈕、。 * 填寫服務器地址(URL)、Token和EncodingAESKey * URL是開發者用來接收微信消息和事件的接口URL。 * Token可由開發者能夠任意填寫,用做生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性) * EncodingAESKey由開發者手動填寫或隨機生成,將用做消息體加解密密鑰。 * 同時,開發者可選擇消息加解密方式:明文模式、兼容模式和安全模式。模式的選擇與服務器配置在提交後都會當即生效 * 加解密方式的默認狀態爲明文模式,選擇兼容模式和安全模式須要提早配置好相關加解密代碼
<!--  -->
第二步:驗證消息的確來自微信服務器
如今若是你點擊確認
按鈕,確定會報認證錯誤。由於咱們沒有作微信認證請求
接收。 開發者提交信息後,微信服務器將發送GET請求到填寫的服務器地址URL
上,GET請求攜帶參數以下表所示:
參數 | 描述 |
---|---|
signature | 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 |
timestamp | 時間戳 |
nonce | 隨機數 |
echostr | 隨機字符串 |
開發者經過檢驗signature對請求進行校驗(下面有校驗方式)。若確認這次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成爲開發者成功,不然接入失敗。加密/校驗流程以下:
微信官方提供在文檔中提供了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次以上不成功,請檢查代碼。
若是使用支付功能,必須先受權
你們應該經歷過,咱們在公衆號打開頁面,通常都會彈出一個按鈕須要咱們點擊贊成纔會繼續瀏覽頁面
可是咱們第二次點擊的時候是不須要點擊受權的,這兩個過程是不一樣的,是兩種受權
:(樣例不可直接使用,是demo)
請注意加粗部分
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
點擊`容許`便可帶着用戶信息跳轉到第三方頁面,如上圖. 做用: 是用來獲取用戶的基本信息的
請注意加粗部分
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機制)
一、引導用戶進入受權頁面贊成受權,獲取code
建議:若是路由由vue管理,建議code由前臺獲取併發送給後臺。 在公衆帳號中配置受權回調域名
(${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 就能夠了,傳給後臺,到這裏基本就能夠了。可是咱們一般是用戶每次登錄都須要進行受權,咱們判斷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 "}