首先,作為客戶,要對接一款 API 產品,客戶的開發人員首先需要接觸的就是對接文檔。傳統的方式是通過技術支持人員把最新的 PDF 或者 Word 文檔交給客戶,客戶線下閱讀和對接,而更好的體驗是直接在開放平臺上閱讀在線文檔,這樣不但更加方便客戶獲得文檔的路徑,還方便 API 平臺對文檔進行自動升級。在線下傳遞文檔模式下,版本的更新還需要技術支持的介入,這增加了很多成本。
大廠的 API 文檔首頁看起來清晰易懂,給讀者的體驗是:不需要任何的學習成本,一眼就能找到自己想要找到的內容。
我們看到微信在主頁上羅列了所有的支付方式的文檔,這包括刷卡支付、公眾號支付、掃碼支付、APP 支付、H5 支付、小程序支付,千萬不要認為這是簡單的羅列,實際上羅列的順序至關重要,主要取決於微信對各個產品的定位以及市場對各種支付方式的需求多少。排在第1位和第3位的是刷卡支付和掃碼支付,這是我們常說的“掃碼”和“被掃”支付,顯然,這是支付產品中最重要的支付方式,另外,排在第2位的是公眾號支付,公眾號支付也是微信主打的支付方式,這個支付方式並不是看起來那麼的簡單,它的背後是內容運營做背書的,微信想讓支付和內容運營形成一個閉環,形成一個自包含的生態,因此,大力推廣公眾號支付,這就是為什麼公眾號支付的時候需要企業報備,雖然很麻煩,但是價格卻很合理的原因。而並不常用的 H5 支付,通常費率很高,小程序支付則是一個新產品,我曾經提出一個創新產品,試圖在小程序中對接快捷支付,不考慮微信政策風險,相信現在仍然有市場,目前小程序還只支持微信支付,之所以最後一個是小程序支付,這是因為小程序是微信生態中繼微信語音聊天、微信支付、微信公眾號的又一大創新,雖然現在還並不那麼顯眼,但是讓我們拭目以待。
下面是支付寶支付文檔的主頁截圖。
我們看到支付寶支付文檔首頁也同樣提供了當面付、APP 支付、手機網站支付和電腦網站支付等四種支付方式。這裡的當面付指的是“掃碼”和“被掃”,這兩種方式被歸類成了當面付,比微信所說的刷卡支付和掃碼支付更合理,而且名字更加容易理解。說起當面付,除了傳統的 POS,那就是“掃碼”和“被掃”兩種支付方式。然後就是 APP 支付、手機網站支付,最後是電腦網站支付,可見把移動支付的兩大支付方式排在了第2位和第3位,優先於電腦網站支付,這也體現了現在是移動互聯網時代,也是移動支付的時代。對於微信支付,支付寶支付還提供了電腦網站支付這種支付方式,這是在互聯網時代裡,淘寶用戶在淘寶買東西,在支付寶 PC 網頁端支付的場景的延續,在另外一方面,微信支付獨有的公眾號模式,在支付寶支付中也沒有提供,但是近期支付寶的生活號支付已經開始推廣了,具有異曲同工的效果。
清晰易懂的 API 文檔
為什麼有的文檔看起來就像吃烤魚一樣爽,而有的文檔看起來很辛苦?這裡,我們一定記得寫文檔是給客戶來看的,因此,我們要思考客戶想看什麼樣的文檔?客戶想怎麼看文檔?而不是單方面的把一大坨的內容堆砌給客戶,心想著:反正都給你了,看不看得懂是你的問題了。
首先,我們先看下微信支付 API 文檔。從上一節在主頁中我們看到,微信把各種支付方式擺放在微信支付 API 文檔的主頁,客戶想看哪個支付方式,只需要點擊即可,這不需要任何的學習成本,而且在光標放到每個支付方式的圖標範圍內,這個支付方式的圖標會自動描框,並且下面顯示查看文檔的按鈕,如何操作則一目瞭然,根本不需要學習或者思考,這就應了那句話,好的產品是給“傻子”用戶設計的。點擊進入某種支付方式,左邊列表欄是關於這種支付方式的所有功能,點擊 API 項目即可查看對接 API 的文檔,於是乎,我們輕鬆的就可以找到我們想要看的 API 對接文檔,下面顯示的查詢訂單 API 的文檔。API文檔包括對接的 URI、參數列表、返回值列表等等。
在 API 文檔的最下方,是這個 API 可能產生的錯誤碼,對接支付 API,錯誤碼的處理尤其重要,因此這裡直接列在了 API 下面,如下圖所示。
這裡看到要想尋找微信支付安裝 API,只需要經過兩個頁面跳轉,非常的方便。
支付寶支付 API 文檔具有異曲同工的作用,在上節中,從首頁的支付方式中直接點擊某一個熱門產品,當光標放在熱門產品的時候,我們也看到了自動描框,這一點給用戶可以點擊進入的提示很重要,這就是設計指引,讓用戶使用的時候自然就知道應該怎麼來用,而不需要學習成本,這才是好產品和好文檔。
點擊進入某個支付方式的主頁以後,我們看到了和微信一樣,使用的左面導航欄。要查看對接的 API 文檔,我們需要再次點擊左邊的 API 列表,進入 API 列表頁面,如下圖所示。
在 API 列表頁面,選擇某個 API 點擊進入 API 文檔,文檔內容包括對接的 URI、參數列表、返回值列表等等,如下圖所示。
從列表中選擇了某個 API 後,就可以看到這個 API 的對接文檔,文檔的最下方是對應的錯誤處理和可能產生錯誤碼,如下圖所示。
我們看到,我們需要跳轉3個頁面才能看到支付寶支付 API 文檔,比微信支付 API 文檔要多一個步驟。
命令模式和 RESTful 的抉擇
首先,我們查看微信的 API,它使用的是類 RESTful 服務的實現,每個功能都有各自的 URI。
下單:
https://api.mch.weixin.qq.com/pay/unifiedorder
查單:
https://api.mch.weixin.qq.com/pay/orderquery
可見,下單和查單的 API 具有相同的 URI 前綴,即 https://api.mch.weixin.qq.com/pay,各自的後綴為 unifiedorder 和 orderquery。
然後,我們再來查看一下支付寶支付的 API 的設計,它使用的是命令模式。
所有的功能都使用同一個 URI。
統一的 URI 為:
https://openapi.alipay.com/gateway.do
在 HTTP 協議中,提供了一個方法參數:method,通過方法參數來傳遞具體使用的功能,不同的方法表示使用不同的操作。
下單:
alipay.trade.pay
查單:
alipay.trade.query
我們看到,支付寶使用命令模式,只提供了一個統一的 URI,然後通過 method 參數來表示具體調用哪個功能和操作。
對比微信支付的 RESTful 服務和支付寶支付的命令模式,兩者各有優缺點。
兩種方式的優點如下。
- 對於命令模式,URI 的設計比較簡單,只需要設計統一的 URI 即可,具體選擇哪個功能都在 method 中來表示,method 參數易於擴展,擴展時不需要對 URI 和以及7層代理的 URI 路由進行修改,只需要對後端代碼進行修改即可,對後期增加更多的命令和操作提供了方便。
- 對於 RESTful 模式,每個功能和操作都有一個對應的 URI,對於開發者來說學習起來比較簡單,使用起來比較方便,URI 信息是在 HTTP 頭上進行傳遞的,由於 URI 的不同,在7層代理可以靈活配置分流和路由,這通常在遷移的過程中是需要的。
我們看到這兩種方式各有優點,實際上,命令模式代表中心化的思想,而 RESTful 模式代表的是分而治之的思想,有的時候優點就是缺點兒,缺點就是優點,其實是個博弈。
命令模式是中心化的思想,比較封閉,雖然對將來的擴展實現比較容易,但是失去了簡單易懂的優點。而 RESTful 是分而治之的思想,保持原汁原味的簡單 API 的風格,看到 URI 就知道 API 的功能,簡單易懂,方便使用。
這裡,通過一個比喻來對比命令模式和 RESTful 模式。想象一下桌子上有3個杯子,杯子裡面裝著水、酒、雪碧,看起來都是透明的,如果每個杯子都沒有標籤,我們得拿起挨個嘗一下,才知道哪個是水、酒或者雪碧,這就像命令模式一樣,URI 長得一樣,想知道它是幹什麼的需要進一步看內部的參數,除了看起來麻煩,這在7層代理分流的時候,也是需要解析 HTTP 體才能看到 method 參數,性能偏低,然而,如果我們在杯子上加了標籤,我們一眼就看出哪個是水、酒或者雪碧,這就像在7層代理需要分流的時候,直接在 HTTP 頭中獲取 URI,做不同的事兒。
API 命名規範
好的 API 是會說話的,而不是靦腆不語的、深藏不漏的,因此,API 的設計應該秉著簡單、易懂、使用方便的原則來提供。
我們從微信和支付寶的 API 中看到,URI 使用的都是非駝峰式無下劃線的命名,例如,訂單查詢的 URI 為 /orderquery,而不是 orderquery,也不是 orderQuery,而參數命名也是採用非駝峰式有下滑線的命名,例如,商戶 ID 為 mchid,而不是 mchId,為什麼不用駝峰式命名,因為 HTTP 協議是不區分大小寫的,即使使用駝峰式,也沒有什麼實際的意義。
對於 URI 的設計上,無論是微信支付 API 還是支付寶支付 API,都保持簡單明瞭的風格,例如:
https://api.mch.weixin.qq.com/pay/orderquery
https://openapi.alipay.com/gateway.do
一般不需要定義版本字段,因為版本的升級一般通過增加參數來解決,並向前兼容,讀者也發現一般 API 中定義的版本都是 1.0,從來沒有升級過。也不需要把某個固定關鍵字放到 URI 上,例如 rest。實際上,是不是 RESTful 風格的 API,從 URI 的設計上一目瞭然,不需要再增加提示信息來表達,假設我們為微信的 URI 增加 restful 的信息,如下所示。
https://api.mch.weixin.qq.com/restful/pay/orderquery
那麼,我們看起來也會覺得這個 URI 顯得臃腫,不夠簡單明瞭。
在這裡,我們強烈推薦所有的 API 產品,無論是 URI 還是參數最好都使用英文來命名,最忌諱的是漢語拼音和英文混用,這樣確實顯得不夠專業,降低了 API 的檔次,使用英文的時候儘量使用正確詞彙來表達含義,例如,支付中常說的商戶是 Merchant,而 Customer 表示的是顧客,也不要把 queue 和 queen 混讀,這些內容需要準確把關,才能體現出設計支付產品的專業性,才能服務好客戶。
這裡,我們在設計一款 API 產品時,我們一般定義如下的規範。
- URI 採用非駝峰式無下劃線的命名,而參數使用非駝峰式有下滑線的命名。
- 使用 RESTful 風格的 API,為每一個操作 API 顯示的定義不同的 URI 來區別。
- 任何一個 URI 或者參數,一般由最多3個單詞組成,儘量使用縮寫的單詞。
- 單詞要使用英文來命名,英文要儘量準確表達其意,有固定縮寫的詞就用固定縮寫,例如:Identity->ID,沒有固定縮寫的,縮寫的單詞通常採用省略單詞的元音來構造,例如:method -> mthd。
- 建設一套適合業務的數據詞典,術語按照數據詞典命名,在多個 API 產品中保持風格統一,同一產品中對同一事物命名要前後一致。
- 原則上一個標識符不超過20個字母。
- 在 URI 的設計上,要簡單明瞭、風格統一、一目瞭然,不需要類似版本和 REST 等提示信息。
- 使用的域名必須是英文命名並且具有品牌名稱。
這裡我們給出幾個理想的 URI 設計的案例。
交易 API:
/std/trade/order
/std/trade/orderquery
/std/trade/refund
/std/trade/refundquery
/std/trade/orderclose
入網 API:
/sys/merchant/reginfoadd
/sys/merchant/prodinfoadd
/sys/merchant/agreeinfoquery
打款 API:
/std/balance/remit
/std/balance/remitquery
對賬 API:
/std/bill/tradedaydownload
/std/bill/trademonthdownload
/std/bill/remitdaydownload
數據詞典示例如下:
中文名 英文名 URI 參數 用戶 user user user 商戶 merchant merchant merchant 商戶編號 merchantno merchantno merchant_no 主商戶編號 parentmerchantno parentmerchantno parent_merchant_no 支付 payment payment payment 訂單 order order order 查詢 query query query 出款 remit remit remit 退款 refund refund rfnd 註冊/入網 register reg reg 擴展字段的設計
過去的幾年裡,我負責整個公司核心支付平臺的設計評審工作,經常看見小夥伴在 API 上設計擴展字段,未雨綢繆。
小夥伴的想法是好的,為了將來的擴展,先預留字段,等需要的時候,我們就不用再加了,直接使用擴展字段就好了,於是我們看到很多接口有 ext1、ext2 等字段,我們需要這些“萬惡”的擴展字段嗎?為什麼說這是“萬惡”的字段,這要從某個生產事故說起。話說曾經就有這麼個擴展字段,字段是很久以前的開發者設計的,後來這個字段被不斷的使用,終於有一天由於新的開發者不知道這個字段裡面放的什麼信息,也沒有相應的文檔說明,就武斷的在一個新需求中,把一個新數據放進這個字段,覆蓋了原有的數據,於是悲劇就產生了,有用的信息被覆蓋了,產生了非常不好的後果。
從設計規則上看,我們不要做這樣的未雨綢繆,因為它埋下了禍根,而且是一個不知深淺的禍根,這種問題一旦發生,產生的後果可大可小。
因此,我們一般不需要設計擴展字段,即使設計了擴展字段,等我們有新功能需要新字段的時候,我們也一樣需要開發,因為新需求還是會有新邏輯的實現,即使預留了擴展字段也不能達到在不改動代碼的前提下實現新需求。
還有些小夥伴願意使用一個 JSON 格式的擴展字段,有新需求了,就往 JSON 裡面放,直到 JSON 字段不堪重負為止,這也是不好的設計實踐,需求中需要的重要數據都要單獨設計相應的字段顯示的傳入,而不要用一個 JSON 大字符串來傳入,但是有的小夥伴說這個字段是透傳的,或者是隻讀的,都設計出來字段太大了,這類場景需要具體問題具體分析,看看是否這類開放式的非交易類的數據,可以通過別的方法來傳遞,比如單獨的批量接口、單獨的接口等等,這裡需要更靈活的分而治之的思想。
當客戶意識遇上平臺意識
這裡我們來看下當客戶意識碰到了平臺意識,會發生什麼?客戶意識從客戶需求出發,從各種客戶需求中,挖掘客戶真正的核心需求,俗話說,打蛇打七寸、擒賊先擒王,也是這個道理,做事兒要抓住要點,儘可能的滿足客戶的真實需求。而平臺意識則更多是從提供方角度來設計功能,來滿足客戶需求,這就造成我們設計的平臺有可能沒有最高效的滿足客戶需求,也許客戶需要的是個手槍,而我們給的是一個大炮。
這裡舉個例子,也是我曾經做過的一個設計評審案例,案例裡,需求是設計商戶對賬 API 的提供方法,一般會提供接口下載對賬文件、FTP 下載對賬文件、商戶後臺下載對賬文件,這裡面我們的焦點是通過 API 來提供對賬文件,這個 API 應該怎麼設計會更好呢?
於是,開發人員中就出現了兩個思路。
其中一種思路,把 API 對賬進行抽象和泛化,設計了一套完善的文件服務平臺,可以上傳和下載通用的文件,當然,這個文件服務平臺對於下載對賬文件也是一定能支持的,因此,就設計瞭如下的 API。
BizSystem bs = ...;
String fileId = bs.getReconciliationFileName(merchantNo, date);
FileServicePlatform fsp = ...;
fsp.downloadFile(fileId, LOCAL_DRIVE_FOLDER);
在這個 API 上,客戶需要首先從業務系統根據商編和日期來獲取對賬文件 ID,然後,使用對賬文件 ID 再到文件服務平臺中下載對賬文件。
另外一種思路則更具有客戶意識,擁有這種思路的人從客戶需求出發,他們想更好的服務客戶,解決客戶的痛點,既然客戶想做的是下載對賬文件,我們要儘可能讓客戶以最簡單的方式拿到對賬文件,因此,他們設計瞭如下的 API。
ReconciliationSystem rs = ...;
rs.getReconciliationFile(merchantNo, date, LOCAL_DRIVE_FOLDER);
我們看到,在後面的這個設計中,只要提供了商戶的商編、日期和本地磁盤目錄,就可以直接下載文件,相比較而言,前面方法雖然設計了文件服務平臺,但是需要兩步才能下載一個對賬文件,並且需要客戶直接使用文件服務平臺的 API,而客戶並不關注文件服務平臺。
那麼,到底哪個方案更好呢?其實這也是個博弈,沒有最好,只有更好,有的時候我們需要結合兩種方法。
更好的方法是我們對外設計的系統要進行分層,對內將不同的功能分到不同的層次或者系統中,對外要針對客戶來包裝內部的分層和服務。
從上圖中我們看到,我們可以實現一個文件服務平臺,但是對外提供 API 的時候,還是要秉著簡單、易用的原則,就像上面的第2種思路一樣,這樣就可以有效的避免:客戶需要一隻手槍,我們給予一門大炮。
因此,當客戶意識遇到平臺意識的時候,我們不要著急,他們是不衝突的,有效的結合二者,會產出更有效的解決方案。
我們看到支付寶通過查詢對賬 API 下載地址,將對賬單下載引流到文件服務平臺進行下載,而微信提供同步下載對賬單的接口,但是,我相信微信已經對對賬單下載和交易系統的 API 進行了隔離,因為使用的 URI 不同,在7層代理上實現隔離是個很容易的事兒。
- 訪問這裡,查看微信支付對賬單下載 API。
- 訪問這裡,查看支付寶支付對賬單下載 API。
對於客戶意識和平臺意識,我想給大家提供另外一個案例——在我們對客戶提供的 SDK 中,如何初始化秘鑰的問題。
如果我們從技術平臺的角度來思考,通常我們會實現一個秘鑰提供者接口(KeyProvider),然後可以靈活的插入接口的實現,使用者可以實現這樣的一個接口來提供秘鑰,這看起來很酷,也是一種模板回調的設計模式,但是對於使用者來說並不一定很簡單。
首先,使用者得開發一個類來提供秘鑰,這個類還需要讀取文件或者數據庫來獲得秘鑰,這對有些開發者來說可能是一個負擔。因此,從客戶意識來說,這是一個半成品,並不像設計模式本身那樣閃閃發光。
因此,我們推薦從客戶意識來考慮如何實現這個需求,我們可以提供幾個默認的實現,一個秘鑰配置方式對應一個實現類,例如,從配置文件中取得秘鑰(ConfigFileKeyProvider),從數據庫中取得秘鑰(DbKeyProvider),這樣,客戶只需要通過配置選擇其中的一種使用接口,對於那些20%的特殊客戶的特殊需求,他們可以開發一個定製化的秘鑰提供類(CustomProvider)來從一個非常特殊的地方來獲取秘鑰,比如說加密機,這也是支持的,但是這顯然不是80%的通用場景。我們設計任何系統,平臺化的內容都是80%的通用需求,而我們的平臺要對20%的專用需求留下擴展點,如下圖所示。
因此,使用回調模式獲取密鑰是沒問題的,但是需要為80%的用戶提供通用的解決方案,並留出擴展點來實現20%的非通用需求。
B2B API 的安全策略
API 接口屬於典型的 B2B 的產品,對安全的要求比較特殊,需要驗證企業的身份,這和用戶端 App 的安全技術棧不同,B2B 的 API 產品通常通過簽名和加密來保證安全。
要實施簽名和加密安全策略,通常我們會進行抉擇是用簽名還是加密呢?用對稱加密還是非對稱加密?
這還是要從需求說起,安全需求也是需求的一種,我們總結安全需求無非這幾種:
- 防篡改
- 防偷窺
- 防洩漏
- 防抵賴
- 防中間人攻擊
要滿足這些安全需求,我們採用不同的安全策略和方法,我們從密碼學的歷史開始說起,它一共經歷了三個時代。
1.算法加密時代
在很久以前,沒有各種加密算法,為了防止軟件被盜用,通常在軟件中預留了一段加密算法,如果用戶輸入的註冊碼滿足算法的需求,就通過了校驗,這個時代叫做算法加密時代,以算法為核心來驗證身份,但是這種方法有明顯的缺陷,只要有足夠強大的技術力量,算法都是可以被破解的。
2.對稱加密時代
這一時代,發明了對稱加密算法,加解密方約定同一個秘鑰,加密方用密匙加密,解密方用同一個密匙解密,如果可以成功解密,說明加解密方都有權限訪問數據。對稱加密有兩個應用場景,一個就是簽名,一個就是用來加密,對稱加密和對稱簽名都是使用的對稱加密算法,但是目的不一樣,簽名就想蓋章一樣,防止被篡改,而對稱加密則是為了防止偷窺和防止洩漏。但是這一時代的對稱加密算法也有個明顯的缺點,就是加解密雙方約定了同一個秘鑰,加解密雙方理論上都是可以抵賴的,如果秘鑰洩露了,都可以說是對方洩露的,是無法判斷是誰把秘鑰洩露了。這一時代著名的算法有 3DES、AES。
3.非對稱加密時代
到了非對稱加密時代,解決了對稱加密帶來的抵賴問題,這一時代可以生成1對秘鑰,1對秘鑰由公鑰和私鑰組成,公鑰加密數據只能有私鑰來解密,私鑰加密數據只能由公鑰來解密,前者正式加密的應用場景,而後者是簽名的應用場景。著名的 SSL 就是使用的非對稱加密。這一時代著名的算法有 RSA。
瞭解了這個背景,我們現在來回顧一開始我們提出的安全需求,要滿足防篡改,只需要對稱的簽名即可,要滿足防洩露和防偷窺,只要使用對稱的加密即可,但是要滿足防止抵賴,必須使用非對稱簽名和非對稱加密。
但是要滿足防止中間人攻擊,那麼,我們需要使用雙向的非對稱安全驗證,通常使用雙向的 SSL 來保證。
在支付行業裡面的實踐中,為了安全以及合規需求,我們通常需要使用非對稱簽名來保證數據不被篡改,使用非對稱加密保證敏感數據不洩露,如果有出款需求,我們一般使用非對稱的雙向 SSL 證書來保證出款的安全。
以何種方式提供範圍查詢 API
一款好的支付 API 產品一般包含下單、查詢訂單、退款、查詢退款等核心操作,但是有的時候來自於客戶壓力,客戶想按照某一範圍來查詢訂單,例如下單時間範圍,那麼這樣的範圍查詢真的是支付平臺應該提供的功能嗎?
在這裡,我們不提倡為客戶提供這樣的範圍查詢,這其實是在為客戶做行業的業務訂單系統,客戶的業務訂單系統應該保存所有的訂單信息,我們提供的訂單查詢和退款查詢數據交易系統的訂單查詢接口,是單筆查詢,必須指定客戶訂單號或者我們支付系統的訂單號,不應該按照時間範圍查詢。這可以參考微信和支付寶的 API,兩者都沒有提供類似的接口。
那麼有的時候,確實有這樣的客戶需求,客戶就是想把我們的支付系統當做他們的訂單系統,他們需要查詢訂單了來到我們支付平臺來查詢,而且還可能是大客戶,我們得罪不起,因為除了微信支付寶以後,其他的支付公司都屬於乙方,並沒有多少的話語權,“yeap, fair enough”,這是個合理的需求,也是一個真實的客戶需求,因此,我們確實應該給予實現。
但是我們需要思考在哪裡實現,我們不應該在交易系統中對外提供範圍查詢,因為這會影響交易系統的性能,嚴重情況下範圍查詢會拖垮交易系統,因此這類的查詢系統應該在支付核心的上層系統中定義業務訂單系統,在訂單系統中為商戶提供多樣化的查詢需求,並與交易系統分離。由於這需要一定的開發成本和運維、運營成本,因此,我建議與客戶洽談進行一定的收費,這等於我們在為客戶做深入行業的業務訂單系統。
最後,如果我們真的為客戶提供了範圍查詢,一定要進行分頁處理,並提供一定的限流措施,因為在嚴重情況下範圍查詢的數據量比較大時,會產生巨大的流量,可能會堵塞機房內外的專線。
以何種方式來使用 APPID
從微信和支付寶的產品設計中,我們看到均使用了 APPID,當一個平臺做的越來越大,從開放平臺對外提供的 API 產品也越來越多,不同的 API 產品之間的權限需要進行控制,不能讓 A 產品的客戶訪問 B 產品功能,這就需要對不同的產品提供不同的 ID,APPID 就是滿足這個需求的。
一個客戶可以開通多個產品,每個產品對應一個 APPID,在使用 API 和我們的開放平臺對接的時候,必須提供 APPID,來驗證這個客戶是否開通了這個產品,這是很好的一個隔離模式的實踐。
然而,問題來了,在中小型公司裡,多數時候只有一款核心的應用通過 API 開放平臺來提供的,每次商戶提供了商編還得提供 APPID,或者讓商戶除了記住商編,再記住那麼長的一個 APPID 也是一個難事兒。
因此,這裡還是應用二八原則,對於通用的80%的需求,也就是隻開通一種產品的客戶,我們允許客戶選擇一種默認的產品,這樣客戶只要傳遞商戶 ID 即可,我們自動為這類商戶選擇模式的產品,方便商戶對接,減少商戶的對接成本。對於開通多個產品的商戶,他們可以傳遞 APPID。傳遞 APPID 的,就使用傳遞的 APPID 來校驗權限,這樣既滿足了通用需求,又滿足了專用需求。
撤銷、關閉和退款
在做設計評審的時候,總有小夥伴來問我,什麼時候設計撤銷、關閉和退款,其實這是3個非常容易混淆的接口。
- 退款,一般分成用戶發起的退款以及差錯退款,前者是支付成功後,用戶主動退款,而後者是支付成功後,發現重複支付或者有差錯,系統發起的退款。
- 關閉,訂單超過了有效期自動關閉,不再接受支付成功的回調消息。或者訂單未到有效期,商戶端主動發起關閉,關閉的訂單還沒有支付成功。
- 撤銷,無論支付是否成功,都可以發起撤銷,發起撤銷後,支付成功的交易要退款,支付未成功的交易,則保證不會再次成功。
根據上面的定義,我們看到,退款是在支付成功的前提下發起的,而撤銷則是在不知道支付是否成功的前提下發起的,訂單關閉是在訂單一定沒有支付成功前提下發起的。
但是,在有些產品設計上,這幾個功能有著千絲萬縷的關係,例如,有的支付產品通過撤銷來包裝退款,還有通過退款來包裝撤銷,這需要在具體的場景下具體分析。
那麼設計這幾個功能的黃金原則是:
- 退款是一個通用的業務功能,只要有用戶需求,我們就應該對外提供,內部不管是用銀行提供的退款接口還是撤銷接口都可以。
- 撤銷是針對面對面產品來提供的,一般是在面對面的交易過程中,支付狀態未知,但是用戶著急獲取結果,商戶端發起撤銷來終止交易。
現在,我們來看下微信與支付寶的設計。
對於微信公眾號支付,屬於在線的移動支付,我們看見提供了退款和訂單關閉的功能,如下圖所示。
對於支付寶的支付,屬於在線 PC 支付,我們看見同樣提供了退款和訂單關閉的功能,如下圖所示。
而對於屬於面對面支付的刷卡支付,提供了退款和撤銷等功能,如下圖所示。
[圖片上傳失敗...(image-1426f9-1524997292572)]
而對於支付寶當面付的產品,也提供了退款和撤銷等功能,如下圖所示。
同步還是異步的抉擇
這裡,我們來看下接口設計的核心問題,到底使用同步還是使用異步會更好。實際上,同步和異步也是一個博弈,各有優缺點,沒有哪個會更好,只不過都會有各自的場景,同步更多應用在事務較小,返回較快的場景,效率較高,異步通常應用在事務較大,業務邏輯處理比較複雜,不能在有限時間內返回結果的場景。有的時候我們需要結合同步和異步兩種方式來滿足業務需求。
在支付場景裡,我們通常使用兩種情況的結合來滿足業務場景,通常我們會提供下單接口,然後提供同步的查詢接口來查詢訂單的最新狀態,這是最終一致性的查詢模式。
查詢模式如下圖所示。
同時,我們會提供異步的回調接口,異步的通知商戶支付交易的狀態,如果商戶對接了回調通知,則能夠自動的得到支付成功的通知。
因此,有了同步的查單接口以及異步的通知接口,商戶的業務系統實現起來將會更加簡單和可靠。
在支付寶的當面付中,既提供了訂單查詢又提供了異步通知接口,如下圖所示。
同樣,在微信公眾號支付中,既提供了訂單查詢又提供了異步通知接口,如下圖所示。
關注、轉發、評論頭條號每天分享java知識,私信回覆“555”贈送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式資料
閱讀更多 Java牛人 的文章