微信小程式踩坑指南【一】

_沒有好名字了_發表於2018-12-28

最近因為公司業務一直在做微信小程式的專案,趁此機會將最近踩過的一些坑總結記錄下。

微信小程式登陸相關

登入流程時序

  1. 前端呼叫 wx.login(),獲取臨時登入憑證 code
  2. 通過 wx.request()將 code 發給伺服器(需要後端建立介面接收 code)
  3. 後端進行登入憑證校驗,入參為(appid,secret,js_code,grant_type)

附常見名詞解釋:

appid 小程式唯一標識 secret 小程式的 app secret js_code 登入時獲取的 code grant_type 填寫為 authorization_code

  1. 登陸憑證校驗通過,從微信伺服器換取 openid 和 session_key

openid 使用者唯一標識 session_key 會話金鑰

openid 是使用者唯一標識,但不建議直接用做後端伺服器的各標示符。 session_key 是針對使用者資料進行加密簽名的密 session_key 在檔案校驗,獲取使用者具體資訊時均需使用

一般為了安全起見,這兩個資料都不會發往客戶端。

  1. 後端將 session_key 處理之後,返回前端一個處理後的一個字串作為使用者的登陸標識,一般以 token 的形式。(自定義登陸態與 openid session_key 相關)
  2. 前端接收到 token,儲存到 localStorage 中,每次向伺服器請求資料的時候帶上,作為伺服器識別使用者的憑證。
  3. 後續使用者進入小程式時,首先呼叫 wx.checkSession() 檢測登陸態,如果失敗,重新發起登陸流程。
//app.js
const NOLOGINCODE = 1000003 //未登入
const SUCCESS = 1000001 //成功
App({
  onLaunch: function() {
    var loginFlag = wx.getStorageSync('sessionId')
    var that = this
    if (loginFlag) {
      // 檢查 session_key 是否過期
      wx.checkSession({
        // session_key 有效(未過期)
        success: function() {
          var userInfo = wx.getStorageSync('wxUserInfo')
          if (userInfo) {
            that.globalData.hasUserInfo = true
          }
        },
        // session_key 過期
        fail: function() {
          // session_key過期,重新登入
          that.doLogin()
        }
      })
    } else {
      // 無skey,作為首次登入
      this.doLogin()
    }
  },
  doLogin() {
    this.log().then(res => {
      this.$post('/auth', { code: res.code }, false).then(data => {
        wx.setStorageSync('sessionId', data.sessionId)
      })
    })
  },
  /**
   *微信登入 獲取code值,並將code傳遞給伺服器
   * @returns
   */
  log() {
    return new Promise(resolve => {
      wx.login({
        success(res) {
          if (res.errMsg === 'login:ok') {
            resolve(res)
          } else {
            wx.showToast({
              title: '微信登入失敗',
              icon: 'none',
              duration: 1200
            })
          }
        },
        fail() {
          wx.showToast({
            title: '微信登入介面呼叫失敗',
            icon: 'none',
            duration: 1200
          })
        }
      })
    })
  },
  globalData: {
    baseurl: 'https://www.fake.shop'
  }
})
複製程式碼

網路請求封裝

微信小程式中網路請求的 api 是 wx.request(),但是這個請求是個非同步回撥的形式,每次發請求都要寫好長一串,而且如果是巢狀的發請求,就會發現程式碼寫的及其臃腫,所以將其 Promisefy 是及其有必要的。 程式碼如下:

 $get(url, data = {}, needToken = true) {
    let SUCCESS = 200
    var that = this
    needToken ? (data.token = wx.getStorageSync('ToKen')) : ''
    return new Promise((resolve, reject) => {
      wx.request({
        url: that.globalData.baseurl + url,
        method: "GET",
        header: {
          'content-type': 'application/json'
        },
        data: data,
        success(e) {
          if (e.data.code == SUCCESS) {
            resolve(e.data)
            return
          }

        },
        fail(e) {
          wx.showModal({
            title: '提示',
            content: '請求失敗',
            showCancel: false
          })
          reject(e)
        }
      })
    })
  },
  $post(url, data = {}, needToken = true) {
    let that = this
    let SUCCESS = 200
    let TimeOut = 1000
    var that = this
    needToken ? (data.token = wx.getStorageSync('ToKen')) : ''
    return new Promise((resolve, reject) => {
      wx.request({
        url: that.globalData.baseurl + url,
        method: "POST",
        //此處可以根據介面文件設定header頭
        // header: {
        //   'content-type': 'application/x-www-form-urlencoded'
        // },
        data: data,
        success(e) {
          if (e.statusCode == SUCCESS) {
            if (e.data.code == SUCCESS) {
              resolve(e.data)
            }
            else {
              reject(e)
              wx.showModal({
                title: '提示',
                content: e.data.msg,
                showCancel: false,
                success: function (res) {
                  if (res.confirm) {
                    if (e.data.code == TimeOut) { //根據實際業務返回的code碼判斷是否過期
                      // 登入過期
                      that.doLogin();
                    }
                  }
                }
              })
            }
          } else {
            wx.showModal({
              title: '提示',
              content: e.data.error,
              showCancel: false
            })
            reject(e)
          }
        },
        fail(e) {
          console.log(e)
          wx.showModal({
            title: '提示',
            content: '請求失敗',
            showCancel: false
          })
          reject(e)
        },
        complete(e) {
        }
      })

    })
  },
複製程式碼

微信公共號支付(微信瀏覽器)

雖然是寫小程式踩坑指南,但是在微信內的 H5 頁面支付和小程式內掉起支付還是有相似之處的,順便記錄一下。

應用場景

  • 已有 H5 商城網站,使用者通過訊息或掃描二維碼在微信內開啟網頁時,可以呼叫微信支付完成下單購買的流程。
準備

UnionID:為了識別使用者,每個使用者針對每個公眾號會產生一個安全的 OpenID,如果需要在多公眾號、移動應用之間做使用者共通,則需前往微信開放平臺,將這些公眾號和應用繫結到一個開放平臺賬號下,繫結後,一個使用者雖然對多個公眾號和應用有多個不同的 OpenID,但他對所有這些同一開放平臺賬號下的公眾號和應用,只有一個 UnionID 網頁授權: 一些複雜的業務場景下,需要以網頁的形式提供服務,通過網頁授權可以獲取使用者的 openid(注:獲取使用者的 OpenID 是無需使用者同意的,獲取使用者的基本資訊則需使用者同意) 微信 JS-SDK:是開發者在網頁上通過 JavaScript 程式碼使用微信原生功能的工具包,開發者可以使用它在網頁上錄製和播放微信語音、監聽微信分享、上傳手機本地圖片、拍照等許多能力。

業務流程時序圖

業務流程時序圖

主要流程
  • 網頁內引入 jssdk,主要有兩種
    1. 在需要呼叫 JS 介面的頁面引入如下 JS 檔案:res.wx.qq.com/open/js/jwe… JSSDK 使用步驟
    2. 模組引入: 直接引入 npm 包weixin-js-sdk,可以通過 npm 直接安裝,然後在需要的檔案中直接引用即可。
  • 網頁授權
    • 我的理解就是網頁授權主要是為了使在微信瀏覽器裡面開啟的第三方網頁,可以跟微信公共號以及使用者的微信相關聯的操作,最終獲取使用者在該公共號下的openid.
    • 網站應用微信登入是基於 OAuth2.0 協議標準構建的微信 OAuth2.0 授權登入系統。獲取 openid 分為兩步
      1. 前端通過跳轉網址獲取 code,然後將 code 傳送給後端
      2. 後端然後根據 code 獲取 openid。

code 的獲取

  • 在微信公眾號請求使用者網頁授權之前,開發者需要先到公眾平臺官網中的 “開發 - 介面許可權 - 網頁服務 - 網頁帳號 - 網頁授權獲取使用者基本資訊” 的配置選項中,修改授權回撥域名。本例中回撥域名為 www.foo.com
  • 業務流程 舉例: 支付頁面地址: payUrl => "www.foo.com/pay" 1. 要跳轉到支付頁面時,如果是微信瀏覽器直接跳轉 href(辦法有很多可以重定向也可以 location.href)到 "open.weixin.qq.com/connect/oau…"+ appid +"&redirect_uri="+ URLEncoder.encode(payUrl) +"&response_type=code&scope=snsapi_base&state=123#wechat_redirect" 2. 系統會自動跳轉到 payUrl 並且返回一個引數 code 例如=> "www.aa.com/pay?code=aa…" 3. 然後讀取下 code 傳送後端就 ok 了,這個大家應該都會吧。
注:

URLEncoder.encode(payUrl)是非常有必要的 state 引數: 用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方。該引數可用於防止 csrf 攻擊(跨站請求偽造攻擊),建議第三方帶上該引數,可設定為簡單的隨機數加 session 進行校驗 後端獲取 openid 的原因: 因為我是前端,不想搞這個(開玩笑的 ?),其實主要可能是因為這部分邏輯部分敏感的公眾號的祕鑰等以及為了避免前端跨域的問題。 code 的是時限: code 作為換取 access_token 的票據,每次使用者授權帶上的 code 將不一樣,code 只能使用一次,5 分鐘未被使用自動過期。 所以每次進行支付的時候都需要進行以上邏輯

微信內 H5 調起支付

  • 需要將 openid 和 商戶訂單號發給後端,後端呼叫 api 生成前端呼叫支付 jsapi 需要的配置(這個主要是後端的邏輯)
    配置
    不囉嗦,程式碼如下:
//this.wechaConfig 裡面儲存的是後端呼叫預支付api 以後傳遞給前端用來呼叫getBrandWCPayRequest 的配置項。
		let config = {
				appId: this.wechaConfig.appId + '', // 公眾號名稱,由商戶傳入
				timeStamp: this.wechaConfig.timeStamp + '', // 時間戳,自 1970 年以來的秒數
				nonceStr: this.wechaConfig.nonceStr + '', // 隨機串
				package: this.wechaConfig.package + '', //	統一下單介面返回的 prepay_id 引數值,提交格式如:prepay_id=***
				signType: this.wechaConfig.signType + '', // 微信簽名方式:
				paySign: this.wechaConfig.paySign + '', // 微信簽名
			}
			// config = JSON.parse(JSON.stringify(config))
			WeixinJSBridge.invoke(
				'getBrandWCPayRequest',
				config,
				function(res) {
					if (res.err_msg == 'get_brand_wcpay_request:ok') {
						// 使用以上方式判斷前端返回, 微信團隊鄭重提示:res.err_msg 將在使用者支付成功後返回    ok,但並不保證它絕對可靠。
						this.$router.push({
							name: 'payResult',
							query: {
								status: true,
								id: this.addOrder.orderId,
							},
						})
					} else {
						this.$router.push({
							name: 'payResult',
							query: {
								status: false,
							},
						})
					}
				}.bind(this)
			)
複製程式碼

注意:

  1. 如果是使用 wx.chooseWXPay(),那麼配置欄位中是 timestamp 而不是 timeStamp
  2. config 變數裡面之所以每個變數都加 '' 例如:this.wechaConfig.appId + '',因為沒有加之前在安卓上面可以正常的喚起 微信支付,而在 ios 上面測試的時候,會報錯 缺少 jsapi appid 或者缺少 jsapipackage (我當時心裡面就是 什麼鬼啊 (((m -__-)m 我明明都傳了的),所以加上查資料好多都說是 json 格式的問題, 我推測可能是由於很奇怪的原因(有理清楚的大佬評論區說下 ?),appid 的值沒有被當成 String 型別被解析,所以我加了這個來處理一下。

    查到的比較有用的一個是 問題在於支付的時候 JSON 引數,必須全部是字串。 比如我的錯誤是引數中 {"timeStamp":12312312},時間戳的值為整型,雖然 Android 上可以支付,但是 IOS 上就不行了,必須嚴格按文件上說的,鍵和值全部是字串!這樣 {"timeStamp":"12312312"} >才對! 傳送門

  3. 如果是進行本地除錯的話,需要注意微信的介面預設使用 80 埠

之前寫這篇文章的初衷是想著記錄下自己踩過的坑,避免小夥伴們重複踩坑。現在看來內容還是乾貨比較少,以後會持續更新的。。。

參考

相關文章