第七期:用雲函數快速實現小程序支付

作者:知曉雲- 小程序開發快人一步
來源:知曉課堂

本文主要側重於講述小程序在線支付功能中的編程思想和編程模式,並在必要的地方提供關鍵代碼示例。

爲方便演示,這裏將實現一個最簡單的虛擬商品的訂單支付功能,訂單略去了收貨地址和多規格、多數量的情況,示例中僅討論在商品詳情頁中直接創建訂單併發起支付的情況。需要分別定義 Product 表和 Order 表進行數據存取,在 BaaS 後臺中創建兩張數據表。

一、數據表結構設計

Product 表:
數據表錄入權限:所有人
數據行讀寫權限:創建者可寫,所有人可讀
在這裏插入圖片描述
Order 表:
數據表錄入權限:所有人
數據行讀寫權限:創建者可寫,創建者可讀
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QTuOEx8z-1591240483124)(/img/bVbHYud)]

商品的訂單結算和支付流程一般包括“創建訂單 -> 支付 -> 更新訂單狀態”三個步驟。下文中將分析幾種實現該流程的方案,供我們一起探討。

關注「知曉雲」微信公衆號,在微信後臺回覆「源碼1」,獲取完整的商品詳情頁 JS 代碼。

二、客戶端創建訂單,客戶端更新訂單狀態

我們先來看下只在客戶端中如何處理這些邏輯。

1) 創建訂單:Order 表中創建一條新記錄,status 字段默認值爲 “no_paid”,保存訂單金額,商品快照和商品 id 以及訂單創建者,其中訂單創建者由 BaaS 的用戶系統自動處理,值爲創建訂單的用戶 id:

/**
 * 創建訂單處理函數
 */
createOrderHandle() {
  const orderTableId = 12345678
  const tableObject = new wx.BaaS.TableObject(orderTableId)
  const createObject = tableObject.create()


  const product = this.data.product
  const data = {
    product_id: product.id,
    product_snapshot: product,
    total_cost: product.price,
    status: 'no_paid',
  }


  // 客戶端創建訂單,客戶端更新訂單狀態
  return createObject.set(data).save().then(res => {
    this.order = res.data || {}
    return this.pay(this.order)
  }).then(transactionNo => {
    return this.updateOrder(transactionNo)
  }).then(res => {
    wx.navigateTo({ url: '../order/order' })
  })

2)支付:調用 BaaS SDK 提供的支付方法 wx.BaaS.pay,調起微信支付:

/\*\*   
\* 發起微信支付   
\* @param {Object} order   
\*/  
pay(order) {    
const product = this.data.product    
const orderTableId = 12345678    
const params = {   
   totalCost: order.total\_cost,   
   merchandiseDescription: product.title,   
   merchandiseSchemaID: orderTableId,  
   merchandiseRecordID: order.id,   
   merchandiseSnapshot: product,    
 }    
  return wx.BaaS.pay(params).then(res => {    
   return res.transaction\_no    
  })  
 }

3)更新訂單狀態:支付成功後,更新 status 字段值爲 “paid”,並更新微信支付序列號:

/**
 * 更新訂單狀態
 * 僅在由客戶端更新訂單狀態時使用
 * @param {String} transaction_no 支付成功後由微信返回的微信支付序列號
 */
updateOrder(transaction_no) {
  const orderTableId = 12345678
  const tableObject = new wx.BaaS.TableObject(orderTableId)
  const recordId = this.order.id
  const record = tableObject.getWithoutData(recordId)


  record.set('status', 'paid')
  record.set('transaction_no', transaction_no)
  return record.update()
}

我們從整體上來看支付流程,便能發現訂單狀態實質上是由客戶端中 updateOrder 方法發起請求來進行更新的。

**而這一情況將導致極大的安全隱患。**因爲從原則上來說,我們認爲來自客戶端的信息都是不可信的,訂單狀態很容易被僞造出的一個請求跳過支付直接將狀態更新爲 ‘paid’,並更新一個假的 transaction_no。

這意味着,不花一分錢也能將訂單變爲已支付。在生產環境中,任何情下都不應該使用這種支付流程。

三、客戶端創建訂單,觸發器更新訂單狀態

基於這種情況,你或許會想:既然由客戶端來更新訂單狀態會引起安全問題,又沒有後端開發者參與,要怎麼做?

BaaS 平臺中觸發器和雲函數可以幫你解決這個問題。它們可以完成這種非客戶端的處理邏輯,同時使用它們的時候跟開發後端應用又有很大的不同。

首先來看一下觸發器(Trigger),觸發器是一種當觸發條件被滿足,將會執行觸發器中的事先定義的動作,定義好的動作可以是操作數據庫或者調用雲函數。

我們希望當支付完成之後,觸發器可以幫我們自動地操作數據庫,更新訂單對應的 status 和 transaction_no 字段。觸發器設置如下:

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
「觸發類型」選擇微信支付回調,條件是支付成功後執行觸發器。一般觸發器類型常見的還有操作數據表,定時任務等,分別對應操作數據表後觸發和定時觸發。

「動作」定義了觸發器將要執行的操作,這裏是更新 Order 數據表對應的 status、total_cost 和 transaction_no 字段。更多觸發器的具體細節,不同平臺的實現有所不同,在此不展開討論。

藉助觸發器,客戶端創建訂單成功後不需要再調 updateOrder 方法,Order 訂單的數據會自動更新成支付成功對應的狀態:

/**
 * 
  創建訂單處理函數
  */createOrderHandle() {  
   ... // 與上文相同   
   
  // 客戶端創建訂單,觸發器自動更新訂單狀態 
  return createObject.set(data).save().then(res => {  
    this.order = res.data || {}   
    return this.pay(this.order) 
  }).then(res => { 
    wx.navigateTo({ url: '../order/order' }) 
   })
 }

值得注意的是,上面介紹的第一種方案中 Order 表的 ACL 數據行讀寫權限是創建者可寫的,意味着創建者可以對數據進行任意操作,將更新訂單狀態的工作交給觸發器後,Order 表的 ACL 數據行讀寫權限應設置爲「不可寫」,保證 Order 表的數據創建後不會由外部更改,提高了數據的安全性。

四、雲函數創建訂單,觸發器更新訂單狀態

細心的讀者可能發現了除了 status 和 transacton_no 字段外,還由觸發器自動更新了 total_cost 字段,保存的是實際支付的金額。

這就引出了另外一個問題,雖然現在不能通過客戶端修改訂單狀態,但是創建訂單的所有數據仍是由客戶端發起請求,在請求參數中定義的,這種方式同樣很容易被人篡改數據,比如 1000 元的商品可以被更改成 1 元甚至 0 元,造成只需要花很少的錢就可以買到高價值的商品。

使用觸發器自動根據微信支付回調更新 total_cost 可以保證無論何種情況下,數據中保存的都是最終用戶實際支付的金額。雖然這種方式可以事後幫助我們發現訂單金額異常的問題,但還是不能解決在創建訂單時金額被篡改的問題,這又要如何解決呢?

這時候創建訂單的功能應該交給後端邏輯去做了,在 BaaS 平臺中就需要用到雲函數了,雲函數又被稱爲 FaaS(Functions as a Service)函數即服務。

雲函數是一段可以部署在服務端的代碼,關鍵詞是一段代碼,而不是一整套的後端邏輯,它本質上就是函數而已,特別是對於運行在 node.js 環境下的雲函數來說,它跟平常所寫的 JavaScript 代碼幾乎一模一樣,對前端開發者來說非常容易上手。雲函數可以由 SDK 或觸發器調用,也可以在雲函數之間相互調用。

爲了避免創建訂單時客戶端數據篡改或商品信息不能實時同步的問題,我們將創建訂單的邏輯遷移到 BaaS 平臺的雲函數中:

關注「知曉雲」微信公衆號,在微信後臺回覆「創建訂單」,獲取完整的【創建訂單】雲函數源碼。

調用該雲函數時傳入商品 id,雲函數先查出此商品的具體信息,再使用該商品信息來創建訂單,整個過程在 BaaS 平臺的雲函數系統中完成,保證了數據的準確性。支付完成後,觸發器同樣會自動更新訂單狀態。客戶端中使用 invokeFunction 方法調用雲函數:

/**
 * 創建訂單處理函數
 */
createOrderHandle() {
  ... // 與上文相同
  
     // 使用雲函數創建訂單,觸發器更新訂單狀態 
     wx.BaaS.invokeFunction('createOrder', {   
       product_id: this.data.product.id 
    }).then(res => { 
       this.order = res.data || {} 
       return this.pay(this.order)
    }).then(res => { 
       wx.navigateTo({ url: '../order/order' }) 
    })
  }

由於創建訂單和更新訂單的操作已經分別交由雲函數和觸發器處理了,爲了更好的安全性,Order 表的數據創建權限和修改權限都不應該對客戶端開放。

需要額外說明的是,而觸發器和雲函數系統級別的操作,相當於擁有最高權限,所以我們這裏相當於禁止了客戶端中除了讀取數據外的所有操作,也就使得 Order 表的權限控制和數據的準確性得到了安全的保障。

五、雲函數創建訂單,雲函數校驗並更新訂單狀態

我們再來研究一下代碼,在 pay 這個方法中 wx.BaaS.pay(params) 所做的事情實際上是發起一個請求,獲取 BaaS 系統返回的支付解密數據,然後使用這些支付解密數據調用微信客戶端的支付功能,最終由用戶輸入密碼完成支付。

同理,根據客戶端提供的數據都不可信的原則,這個請求中 params 參數時的數據同樣可以被僞造,比如修改掉 totalCost 的值,也會導致最終支付的金額跟實際應該支付的金額不一值,根據之前觸發器的設定,雖然會如實地記錄了最終支付的金額,可以爲後臺追溯金額異常的訂單提供依據,但是並不會阻止訂單更新爲已支付的狀態。

當用戶支付成功後,我們更希望在更新訂單狀態前可以先進行支付數據的校驗,校驗不通過則不更新訂單狀態。想要實現這個功能,則要將觸發器和雲函數進行搭配使用了。

關注「知曉雲」微信公衆號,在微信後臺回覆「檢驗」,獲取完整的【檢驗及更新訂單狀態】雲函數源碼。

先將觸發器的動作類型改爲雲函數:

在這裏插入圖片描述

微信支付成功後會觸發調用 verifyPayment 雲函數:

在這裏插入圖片描述

客戶端的代碼保持不變,此時整個流程是:調用 createOrder 雲函數創建訂單,拿到創建訂單成功的回調數據後,發起支付,支付成功之後,由觸發器自動調用 verifyPayment 雲函數,校驗實付金額是否跟該商品的價格一致,若一致則更新該訂單爲已支付狀態。

在 verifyPayment 雲函數中只考慮了校驗實付金額這一個維度,在實際開發中應綜合考慮更多維度來確保數據準確,在此不再展開討論。

至此,本文完成了一個小程序在線支付的案例介紹瞭如何藉助 BaaS 平臺最快地實現小程序在線支付功能,通過開發過程中發現的各種安全問題,迭代出四種不同的實現方案,一步步完善支付功能的安全性,最後得出一個最快最安全實現小程序在線支付的方案

相關閱讀

第一期:快速實現圖片爬蟲
第二期:快速生成分享海報
第三期:處理微信卡券消息
第四期:自動回覆客服消息
第五期:生成帶參數的二維碼
第六期:將數據表導出爲 Excel 文件

關注「知曉雲」公衆號,點擊菜單欄「知曉雲」-「知曉課堂」,獲取更多開發教程。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章