因爲在公司的交易支付部門搬磚,所以To C端的前端支付頁面,基本由我這邊負責javascript
通常來講,一次正常的交易流程,用戶會通過幾個階段:php
其中收銀臺做爲交易成功的最後一千米,其承擔的職責之重可想而知html
咱們先來看看市面上常見網站的收銀臺:前端
嗶哩嗶哩會員購:java
觸屏端web
pc端ajax
app端json
慕課網:小程序
觸屏端後端
pc端
app端
能夠看出,收銀臺頁面通常要適配3個終端:pc端,觸屏端,app端。所以,主流的第三方支付平臺(微信,支付寶,花唄分期,京東白條)也須要能支持這三種場景的支付
接下來,咱們就來分析下不一樣支付渠道在不一樣終端下,支付的實現方式
因爲支付涉及部門核心業務,所以就不拿公司線上的收銀臺作講解了。支付交互流程,主要參考嗶哩嗶哩會員購和慕課網(沒有利益相關)
<font color="#6495ed">注意:本文只考慮前端支付業務的實現,後端支付業務的實現,暫不考慮</font>
花唄分期其實就是支付寶的拓展,原理基本一致,就不重複累贅
交互方式1:
在pc端點擊支付寶支付,網頁新打開一個頁面(window.open()
),這個頁面指向的是支付寶官方收銀臺頁面
交互方式2:
在pc端點擊支付寶支付,網頁展現一個二維碼,須要用戶打開支付寶app進行掃碼支付
b站提供了一個很巧妙的思路:把微信,支付寶,qq三個支付二維碼統一成了一個二維碼。(原理後面會講解,本質是調用不一樣容器的JSAPI
)
兩種交互方式,點擊支付按鈕時,其實都是把當前的訂單號以及一些相關信息發給後端
const payNum = '123abc'; ajax({ url: '/api/alipay', // 支付api type: 'POST', data: { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }).then((res) => { const { payUrl } = res; // 交互方法1: // payUrl若是是支付寶的收銀臺,則新打開一個頁面 // payUrl通常是 https://mapi.alipay.com/gateway.do 這種,通常會帶上return_url參數和其餘各類數據,頁面最後被重定向到支付寶收銀臺 window.open(payUrl); // 交互方式2: // payUrl若是是支付寶的掃碼地址,則建立一個二維碼彈窗 // payUrl通常是 https://qr.alipay.com/bax06893swswc4inaknv505d 這種,頁面最後被重定向到支付寶收銀臺,該收銀臺能夠喚起支付寶app qrcode({ width: 175, height: 175, url: payUrl }); }).catch((err) => { console.log('提交失敗') })
因爲支付是異步進行的,因此須要前端去查詢該筆訂單是否支付成功
對於交互方式1,因爲支付頁面已經轉移到支付寶收銀臺,因此在支付寶收銀臺支付成功後,支付寶收銀臺會自動跳轉回return_url
(return_url
是咱們當初跳到支付寶收銀臺時帶上的參數,通常指向支付成功頁)。
不過因爲咱們使用的是window.open
打開的新頁面,因此當用戶回到咱們的收銀臺時,咱們須要打開一個彈框,主動詢問用戶是否支付成功。若是用戶點擊了支付完成,咱們須要查詢該筆訂單是否真正支付成功。
// 打開支付寶收銀臺 window.open(payUrl); // 在當前頁面打開彈窗,詢問用戶是否支付成功 createFinishWindow()
對於交互方式2,因爲仍然是在當前頁進行掃碼支付,所以建立二維碼彈窗後,咱們立刻就要輪詢進行查詢訂單狀態
const payNum = '123abc' // 建立一個二維碼彈窗 qrcode({ width: 175, height: 175, url: payUrl }); // 輪詢查詢訂單狀態 function getPayStatus() { ajax({ url: '/api/getPayStatus', // 支付狀態api data: { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }, type: 'POST' ).then((res) => { if (res.payStatus === 0) { // 支付成功,跳到成功頁 window.location.href = `/success/${payNum}`; clearTimeout(statusTimeId); } else { // 還未支付,繼續輪詢 statusTimeId = setTimeout(getPayStatus, 3000); } }).catch((err) => { // 接口報錯,繼續輪詢 statusTimeId = setTimeout(getPayStatus, 3000); }) } let statusTimeId = setTimeout(getPayStatus, 3000);
交互方式:
在觸屏端點擊支付寶支付,頁面直接跳轉到支付寶收銀臺,該頁面會嘗試喚起手機上的支付寶app
其實觸屏端原理和pc端基本同樣,只不過在觸屏端,有可能須要本身拼裝一個form表單,而不是直接跳到連接(固然主要看後端的實現)
const payNum = '123abc'; // 模擬表單提交 function formSubmit(formData, url) { const form = $('<form method="post" target="_self"></form>'); form.attr('action', url); let input; $.each(formData, function (i, v) { input = $('<input type="hidden">'); input.attr("name", i); input.attr("value", v); form.append(input); }); $(document.body).append(form); form.submit(); form.remove(); } ajax({ url: '/api/alipay', // 支付api type: 'POST', data: { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }).then((res) => { const { formData, url } = res; if (formData) { // 須要前端本身構建表單 formSubmit(formData, url) } else { // 直接跳轉連接(後端已經拼裝好表單) window.location.href = url; } }).catch((err) => { console.log('提交失敗') })
支付成功後,同理支付寶會跳轉到return_url
的地址
須要注意:在微信瀏覽器裏,支付寶是不能被喚起的(微信生態圈平常封殺)
解決方法:
方法一:微信環境隱藏支付寶入口
方法二:微信環境,點擊支付寶支付,引導用戶使用其餘瀏覽器打開頁面
若是咱們能誘導用戶使用支付寶客戶端的掃一掃
打開咱們觸屏端的收銀臺頁面,那麼其實咱們也可使用支付寶提供的JSAPI
喚起收銀臺
這也是b站實現微信,支付寶,qq同一個二維碼都能付款的原理,這三個客戶端都提供了本身的JSAPI
,用戶用不一樣的客戶端掃碼,都會進入同一個頁面(b站實現),這個中轉頁根據容器環境,調用不一樣JSAPI
的支付功能
關於jsbridge
的知識,能夠查看我以前的文章jsbridge初探
JSAPI
的簡單示例
function ready(callback) { // 若是jsbridge已經注入則直接調用 if (window.AlipayJSBridge) { callback && callback(); } else { // 若是沒有注入則監聽注入的事件 document.addEventListener('AlipayJSBridgeReady', callback, false); } } ready(function () { // 顯示一個提示框 AlipayJSBridge.call('toast', { content: 'hello' }); });
喚起收銀臺須要使用Alipay JSSDK
<script src="https://gw.alipayobjects.com/as/g/h5-lib/alipayjsapi/3.1.1/alipayjsapi.inc.min.js"></script> <button id="J_btn" class="btn btn-default">支付</button> <script> var btn = document.querySelector('#J_btn'); btn.addEventListener('click', function(){ ap.tradePay({ tradeNO: '201802282100100427058809844' }, function(res){ ap.alert(res.resultCode); }); }); </script>
如今的app基本都是Hybrid App
,若是在app端,你的收銀臺頁面不是原生實現的,那麼就能夠直接使用webview加載觸屏端的線上收銀臺便可
手機網站支付產品不建議在APP端使用
這是支付寶官網文檔建議的,所以若是你但願獲得最佳的支付體驗,建議客戶端的開發同窗接入支付寶SDK,固然這部分已經超出了前端的範圍
不過通常在app端中,咱們仍然使用webview加載觸屏端的前端頁面,只不過在app中,咱們的前端代碼,經過jsbridge
,調用客戶端的支付方法便可
const payNum = '123abc'; // 支付回調函數 window.ali_pay_callback = function(res) { if (res.status === 0) { // 支付成功 } else { // 支付失敗 } } // APPSDK是webview注入的全局對象,能夠調用原生方法 APPSDK.invoke('ali_pay', { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }, 'ali_pay_callback');
小程序支付和APP支付的支付流程與體驗基本一致,能夠在當前頁面喚起支付寶收銀臺
const payNum = '123abc'; my.request({ url: 'https://demo.com/api/alipay',// 須加httpRequest域白名單 method: 'POST', data: { // data裏的key、value是開發者自定義的 from: '支付寶', payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }, dataType: 'json', success: function(res) { my.alert({content: 'success'}); }, fail: function(res) { my.alert({content: 'fail'}); }, complete: function(res) { my.hideLoading(); my.alert({content: 'complete'}); } });
咱們從pc端,觸屏端,app端三個方面瞭解了支付寶支付的基本原理。能夠看出:支付的前端實現,其實並不複雜,而真正的難點在於後端支付系統的實現。至於最難的支付寶喚起問題,其實支付寶收銀臺自身已經實現了喚起功能,無需咱們實現
交互方式:
因爲微信並無像支付寶提供了pc端的官方收銀臺,因此點擊微信支付,咱們通常都是直接彈出二維碼彈窗,要求用戶進行掃碼支付,用戶掃碼則能夠直接喚起微信支付。彈出二維碼的同時,咱們須要當即輪詢查詢支付狀態。
const payNum = '123abc'; ajax({ url: '/api/weixinpay', // 支付api type: 'POST', data: { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }).then((res) => { const { qrUrl } = res; // qrUrl是微信的掃碼地址,通常是 weixin://wxpay/bizpayurl?pr=P1oi4x6 ,這段schema經過微信掃一掃能夠喚起微信支付 qrcode({ width: 175, height: 175, url: qrUrl }); // 開始輪詢支付結果 // 代碼省略,能夠參考以前的支付寶pc端實現 }).catch((err) => { console.log('提交失敗') })
交互方式:
在觸屏端點擊微信支付,頁面直接跳轉到微信支付中間頁,該頁面會嘗試喚起微信支付
與支付寶收銀臺不一樣的是,微信支付中間頁在調起微信收銀臺後超過5秒,會自動跳轉回redirect_url
,所以沒法保證頁面回跳時,支付流程已結束,因此商戶設置的redirect_url
地址不能自動執行查單操做,應讓用戶去點擊按鈕觸發查單操做
// 代碼省略,基本和支付寶的觸屏端同樣 // 微信支付中轉頁通常是這種格式的url地址 // https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx111408048537349a5434e53d1930739300&package=1982317760&redirect_url=https://m.imooc.com/myorder
須要注意:微信支付中轉頁通常不能直接用瀏覽器訪問,由於中轉頁須要判斷referer
是不是商戶申請H5時提交的受權域名。若是你直接用瀏覽器訪問,referer
爲空,致使頁面並不會加載成功。若是是APP裏調起H5支付,須要在webview中手動設置referer
還有一種取巧的方法,咱們能夠不使用微信中轉頁,直接在當前頁喚起支付
// 後端直接返回一段schema const schema = `weixin://wap/pay?appid%3Dwxd6841de60b02faef%26noncestr%3D095525b24fc94111a3663068c8dc8a90%26package%3DWAP%26prepayid%3Dwx091027118037832f961440d31092022500%26sign%3D2CF5A14607C6AAEDE382758CA87B973F%26timestamp%3D1591669631` // 移動端就能喚起微信支付 window.location.href = schema;
不過這種方法,schema
容易被第三方app的webveiw
攔截,從而調起支付失敗。好比在微博訪問收銀臺,若是使用該方法,就會喚起微信失敗。所以,仍是建議使用微信中轉頁,由中轉頁喚起微信比較保險。固然,支付寶裏無論用啥方法,都沒法進行微信支付(相愛相殺)。
若是咱們的收銀臺頁面是在微信瀏覽器裏打開的,那麼咱們可使用微信提供的JSAPI
喚起支付
const payNum = '123abc'; function onBridgeReady(wxJsApiParam) { window.WeixinJSBridge.invoke( 'getBrandWCPayRequest', wxJsApiParam,//josn串 function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { // 支付成功 location.href = `/success/${payNum}`; } else if (res.err_msg == "get_brand_wcpay_request:fail") { // 支付失敗 } } ); } function weixinPay(wxJsApiParam) { if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', function () { onBridgeReady(wxJsApiParam) }, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', function () { onBridgeReady(wxJsApiParam) }); document.attachEvent('onWeixinJSBridgeReady', function () { onBridgeReady(wxJsApiParam) }); } } else { onBridgeReady(wxJsApiParam); } } ajax({ url: '/api/weixin_jsapi', // 支付api type: 'POST', data: { payNum: payNum, // 訂單號 other: 'demo', // 其餘參數 }).then((res) => { const { jsapiData } = res; // jsapiData是一串json字符串,裏面包含了appId,paySign等各類數據,用來調起微信支付 weixinPay(JSON.parse(jsapiData)); }).catch((err) => { console.log('提交失敗') })
使用JSAPI
須要咱們有微信公衆平臺,由於下單必傳的參數openid
,須要咱們在公衆平臺設置獲取openid的域名,才能獲取成功
除了使用微信瀏覽器內置的WeixinJSBridge
對象,咱們也可使用JSSDK
<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <script> wx.chooseWXPay({ timestamp: 0, nonceStr: '', package: '', signType: '', paySign: '', success: function (res) { // 支付成功後的回調函數 } }); </script>
H5支付不建議在APP端使用,如須要在APP中使用微信支付,請接APP支付
微信官方文檔一樣不建議在APP端使用觸屏端的支付方式,所以最好接入微信SDK。前端一樣可使用jsbridge
調用客戶端的微信支付方法,能夠參考前面支付寶的app端
方式。
小程序支付其實和微信JSAPI
支付很是相似,都須要先獲取到Openid
,調用相同的API
wx.requestPayment({ timeStamp: '', nonceStr: '', package: '', signType: 'MD5', paySign: '', success (res) { }, fail (res) { } })
微信支付在JSAPI
和小程序的流程上比較複雜些,由於涉及到公衆號access_token
openid
等一系列權限的獲取。不過總的來講,複雜難度主要仍是在後端方面。
除了主流的微信支付和支付寶支付,咱們有可能還須要對接一些其餘第三方支付平臺,好比:QQ,PayPal, 銀聯,京東白條,各大銀行等等,固然原理也是大同小異。
同時,咱們也可使用第三方聚合支付平臺,好比度小滿支付,這些平臺已經集成好了各大銀行信用卡和存儲卡支付功能,咱們能夠很容易的接入sdk,節約開發成本。
web支付因爲開發條件要求很高(至少要有註冊公司),所以大部分同窗平常工做接觸並非不少。固然本文也僅僅是回顧了下平常開發中,前端在web支付中的一些常見套路。
真正實際項目裏,咱們仍然會面臨不少問題和難點,這時就須要咱們見招拆招了。