本章内容出自《小程式開發不求人》電子書, 點選下載下傳完整版
支付寶小程式 API
簡介
支付寶小程式開發架構提供了豐富的 JSAPI(原生 API) 和 OpenAPI(開放能力 API),開發者可友善快捷地調用這些 API,詳情請參見
API 概覽。
- OpenAPI 是支付寶開放平台在小程式上開放的開放能力 API。通過 OpenAPI,小程式可以輕松實作使用者授權、擷取會員基礎資訊、擷取使用者手機号、小程式喚起支付、跳轉支付寶卡包、會員開卡授權等多種多樣的功能。
- JSAPI 按實作的功能分類,可分為界面、多媒體、緩存、檔案、位置、網絡、裝置、資料安全、分享、收藏、自定義通用菜單、小程式目前運作版本類型、自定義分析、更新管理等14 個大類。
本章我們将講述支付寶小程式比較常用的幾個 JSAPI,帶你走進 JSAPI 的奇妙世界。
如何秘密告白:小程式 HTTPS 網絡請求實作
木心的《從前慢》裡說:
記得早先少年時
大家誠誠懇懇
說一句 是一句
……
從前的日色變得慢
車,馬,郵件都慢
一生隻夠愛一個人
從前的鎖也好看
鑰匙精美有樣子
你鎖了 人家就懂了
木心懷念着過去那個通過郵件傳遞資訊的簡單時代。
當下的網絡時代雖然瞬息萬變,但傳遞資訊也是一樣的“說一句,是一句”,“你鎖了,人家就懂了”。
小程式經常需要往伺服器傳遞資料或者從伺服器拉取資訊。
當使用者通過小程式加載伺服器傳來的資訊時,整個網絡過程如下:
- 使用者通過小程式向伺服器發出 GET 請求,
- 伺服器發送一個響應,響應資訊包含一個資料檔案。

該過程叫做 HTTP。
上面的流程也許過于簡化,其實使用者與伺服器之間不可能面對面直接通話,因為它們相隔不是很近,甚至伺服器是在浏覽器的千裡之外,而用戶端浏覽器不可能直通伺服器。
每一次的網絡請求,小程式傳遞給伺服器的資訊,中間經過多重的資訊轉達。同理伺服器回應小程式的響應也是同樣的路徑。
通俗點說,就是傳紙條的原理。寫字條的同學需要把字條遞給旁邊第一個同學,然後第二個同學遞給第三個同學,以此類推,一直傳遞到最後的資訊接收者。
讓我們看看,在傳遞字條的過程中,如果資訊發出者想要給資訊最末尾的接受者告白,會發生什麼呢?
在 HTTP 狀态下,傳遞者都可以打開字條,檢視裡面的内容。而且發送資訊者無法知道傳輸路徑,一旦發生資訊竊取,甚至不知道是誰竊取的。
還是就是當資訊落入心懷不軌的人手中,或者篡改資訊内容,其後果不可設想。比如,把“你喜歡我嗎?”篡改成“你不喜歡我嗎?”。
為了避免這些情況發生,HTTP 安全版本應運而生,即 HTTPS。通過 HTTPS,傳送的每次資訊都被加上一個鎖。
該鎖配套的公鑰和密鑰僅小程式和伺服器知道,其他傳遞者無法擷取。是以,無論用戶端發送的資訊經過多個路由器,他人都無法讀取資訊内容。
用戶端發送初始資訊到伺服器時,在資訊内容中包含伺服器的名稱(在名為“伺服器名稱訓示”的字段中)。而伺服器運作商可以在同一台計算機上運作多個站點,是以運作商可以跟蹤到用戶端的通路軌迹。雖然初始的資訊已設定了加密,但是初始請求是仍未加密的。
這就是通過小程式 my.request 安全傳遞告白資訊的故事。本節将介紹如何使用my.request API 實作網絡請求,并介紹一些使用注意事項。
版本要求:基礎庫 1.11.0 或更高版本;支付寶用戶端 10.1.32 或更高版本,若版本較低,建議做
相容處理發起網絡請求:
- my.request 目前支援 GET/POST。
- my.request 目前隻支援 HTTPS 協定的請求。
使用說明:
- 請預先在 支付寶小程式管理中心 > 小程式詳情 > 設定 > 開發設定 > 伺服器域名白名單中配置域名白名單。小程式在以下 API 調用時隻能與白名單中的域名進行通訊:HTTP 請求(my.request)、上傳檔案(my.uploadFile)、下載下傳檔案(my.downloadFile)和WebSocket(my.connectSocket)。
支付寶小程式 API
- 添加伺服器域名白名單後,需要重新打包上傳生成體驗版,伺服器域名才會生效。
- 在 IDE 上進行調試時,請使用真機預覽調試。
- 支付寶用戶端已不再維護 my.httpRequest,建議使用 my.request。另外,釘釘用戶端尚不支援 my.request。若在釘釘用戶端開發小程式,則需要使用 my.httpRequest。
掃碼體驗
重要:
- 小程式開發過程中,可在開發工具内 詳情 > 域名資訊 > 忽略 httpRequest 域名合法性檢查 中選擇是否忽略域名合法性檢查,如果選擇忽略,則在模拟器、預覽以及真機調試場景不會校驗域名合法性,但小程式上線前必須確定通訊域名在白名單内,否則在正式版本無法調用。
- my.request 的請求頭預設值為 {'content-type': 'application/json'},而不是{'contenttype': 'application/x-www-form-urlencoded'}。此外,請求頭對象裡面的 key 和value 必須是 String 類型。
示例代碼
// dataType 為 json 示例
my.request({
url: 'https://httpbin.org/post',
method: 'POST',
data: {
from: '支付寶',
production: 'AlipayJSAPI',
},
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'});
}
});
// dataType 為 base64 示例
my.request({
url:
'https://gw.alipayobjects.com/mdn/miniapp_de/afts/img/A*G1kWSJbe2zEAAAAA
AAAAAABjARQnAQ',
method: 'GET',
dataType: 'base64',
success: (resp) => {
console.log('resp data length', resp.data.length);
console.log('resp data', resp.data); // 傳回格式類似于:
data:image/png;base64,iVBORw0KG...
},
fail: (err) => {
console.log('error', err);
},
});
入參
Object 類型,屬性如下:
data 參數說明
傳給伺服器的資料最終會是 String 類型,如果 data 不是 String 類型,會被轉換成 String 。轉換規則如下:
-
若方法為 GET,會将資料轉換成 query string:
encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)...
- 若方法為 POST 且 headers['content-type'] 為 application/json ,會對資料進行JSON 序列化
- 若方法為 POST 且 headers['content-type'] 為 application/x-www-formurlencoded ,會将資料轉換成 query string:encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)...
success 回調函數
入參為 Object 類型,屬性如下:
屬性 | 類型 | 描述 |
---|---|---|
data | String | 響應資料,格式取決于請求時的dataType 參數,如果 dataType 值為 base64時,傳回的是符合 data URIscheme 規範的内容字元串。 |
status | Number | 響應碼。 |
headers | Object | 響應頭。 |
傳回值 RequestTask
網絡請求任務對象。調用 my.request 後傳回的請求對象。
RequestTask.abort()
中斷請求任務。
// 傳回 RequestTask,可以調用 abort 方法取消請求
const task = my.request({url: 'https://httpbin.org/post'})
task.abort();
“抱歉,不是我的菜”:小程式掃碼點餐化解尴尬
月上柳梢頭,人約黃昏後。
周五到了,小心翼翼約她出來吃晚飯,她欣然應約。
餐廳位于徐彙區鬧中取靜的華山路,法式梧桐的點綴讓餐廳更顯典雅,也更富有異國情調。踏入餐廳,燈光是橘色的,餐具是藍的,桌椅也是藍的,讓人恍惚之間有到了愛琴海邊的錯覺,唯美的裝修風格、充滿歐洲風味的精緻美食,處處洋溢着地中海風情,真浪漫啊。
她翩翩而至,裙裾飛揚。
見到她我臉紅了。我緊張地問她要吃些什麼,又手忙腳亂地叫來服務員點完了菜,
臉上冒出了小汗珠。
窗外的小雨滴滴答答,窗内的我們顯得格外安靜。
我鼓起勇氣,打破沉默,小聲問道:“你……你對我印象如何?”
“抱歉,不是我的菜……”
此刻,我如同五雷轟頂,隻覺天旋地轉,眼前華光溢彩的餐廳瞬間變得黯淡了。
“你是不是點錯菜了?還是上錯菜了呢?”她指着桌上的法式田螺和奶油蘑菇湯,瞪大了眼睛問我。旁邊站着滿臉疑惑的上菜員。
如何化解點錯菜的尴尬呢?這時就需要使用支付寶小程式掃碼點餐的功能了。
為了讓使用者減少輸入,我們可以把複雜的資訊編碼成一個二維碼,利用 my.scanAPI 調起支付寶掃一掃,使用者掃碼之後,my.scan 的 success 回調會收到這個二維碼所對應的字元串資訊。
例如餐廳點餐的小程式,我們給餐廳中每個餐桌編号 1-100 号,把這個數字編碼到二維碼中,掃碼獲得編号之後,就可以知道是哪一桌點的菜,大大提高點餐體驗和效率。
// 利用 my.scanCode 擷取二維碼的資料
//page.js
Page({
// 點選“掃碼訂餐”的按鈕,觸發 tapScan 回調
tapScan: function() {
// 調用 my.login 擷取微信登入憑證
my.scanCode({
success: function(res) {
var num = res.result // 擷取到的 num 就是餐桌的編号
}
})
}
})
還有很多場景可以結合支付寶 App 掃碼能力做到很好的體驗,例如通過掃商品上的一維碼做一個商品展示的小程式;通過掃共享單車上的二維碼去開啟單車。我們可以多思考如何利用這個掃碼能力去替代一些繁瑣的輸入操作,讓我們的小程式變得更加便捷。
// API-DEMO page/API/scan-code/scan-code.json
{
"defaultTitle": "Scan"
}
<!-- API-DEMO page/API/scan-code/scan-code.axml-->
<view class="page">
<view class="page-section">
<form onSubmit="scanCode">
<view>
<button type="primary" onTap="scan">掃碼</button>
</view>
</form>
</view>
</view>
// API-DEMO page/API/scan-code/scan-code.js
Page({
scan() {
my.scan({
type: 'qr',
success: (res) => {
my.alert({ title: res.code });
},
});
}
})
定格甜蜜回憶:小程式上傳圖檔功能實作
還記得你們第一次手牽手出門嗎?
還記得你們第一次共享浪漫的燭光晚餐嗎?
還記得你們第一次依偎在電影院中約會嗎?
還記得你們第一次去迪士尼約會圓她一個公主夢嗎?
甜蜜的回憶,多想定格在時光裡。不如開發一個小程式,用于上傳存儲這些甜甜的照片吧!
本節将介紹如何通過 my,uploadFile API 實作小程式上傳圖檔的功能。
使用前提:
> 小程式詳情 >設定 > 開發設定 > 伺服器域名白名單 中配置域名白名單。小程式在以下 API 調用時隻能與白名單中的域名進行通訊:HTTP 請求(my.request)、上傳檔案(my.uploadFile)、下載下傳檔案(my.downloadFile)和 WebSocket(my.connectSocket)。
// API-DEMO page/upload-file/upload-file.json
{
"defaultTitle": "Upload File"
}
<!-- API-DEMO page/upload-file/upload-file.axml -->
<view class="page">
<button type="primary" onTap="uploadFile">上傳圖檔</button>
</view>
// API-DEMO page/API/upload-file/upload-file.js
Page({
uploadFile() {
my.chooseImage({
chooseImage: 1,
success: res => {
const path = res.apFilePaths[0];
console.log(path);
my.uploadFile({
url: 'http://httpbin.org/post',
fileType: 'image',
fileName: 'file',
filePath: path,
success: res => {
my.alert({ title: '上傳成功' });
},
fail: function(res) {
my.alert({ title: '上傳失敗' });
},
});
},
});
},
});
上傳檔案的後端代碼,相關 openAPI 請參考
alipay.offline.material.image.upload(上傳門店照片和視訊接口)@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
String path = req.getParameter("filePath");
//得到要下載下傳的檔案名
String fileName = URLEncoder.encode(req.getParameter("fileName"),"utf-8");
String fileType = path.substring(path.lastIndexOf('.')+1,path.length());
FileInputStream fis = new FileInputStream(path);
System.out.println("debugFileName: "+ fileName);
//下載下傳檔案存放路徑
String localPath = "";
FileOutputStream fs = new FileOutputStream(localPath + fileName
+"."+fileType);
resp.setHeader("content-disposition", "attachment;filename="+fileName);
resp.setHeader("content-type", fileType );
//執行 fileOutputStream 的輸出操作
int len = 1;
byte[] b = new byte[1024];
while((len=fis.read(b))!=-1){
fs.write(b, 0, len);
}
fs.close();
fis.close();
}
愛情不掉線:小程式擷取裝置的網絡狀态
地球不爆炸,愛情不掉線。宇宙不重新開機,我們不分離。
不想要和手機裡的女朋友掉線的你,如何擷取到她的手機網絡狀态呢?
大家都知道,手機連接配接到網際網路有幾種方式:WiFi、2G、3G、4G,包括很快到來的 5G。每種方式的上傳速度和下載下傳速度差異很大,它們的計費方式的差異也導緻使用者在使用網際網路服務的時候有不同的使用習慣。
WiFi 相對于其他幾種網絡連接配接方式,其速度會更快。WiFi 一般情況下,都是免費供使用者使用,而移動資料網絡是需要根據使用流量進行計費的。
考慮這樣一個場景,小程式需要下載下傳一些文檔,然後通過小程式的能力去預覽這個文檔,這些文檔可能檔案體積比較大,對于某些使用者來說,他們并不想耗費太多的資料流量去預覽文檔。這種情況下,可以通過小程式提供的擷取網絡狀态 APImy.getNetworkType,做一些更友好的體驗提示。
// API-DEMO page/API/get-network-type/get-network-type.json
{
"defaultTitle": "擷取手機網絡狀态"
}
<!-- API-DEMO page/API/get-network-type/get-network-type.axml-->
<view class="page">
<view class="page-section">
<view class="page-section-demo">
<view class="page-body-title">網絡狀态</view>
<block a:if="{{hasNetworkType === false}}">
<text class="page-body-text">未擷取</text>
<text class="page-body-text">點選按鈕可擷取網絡狀态</text>
</block>
<block a:if="{{hasNetworkType === true}}">
<text class="page-body-text-network-type">{{networkType}}</text>
</block>
</view>
<view class="page-section-btns">
<view onTap="getNetworkType">擷取手機網絡狀态</view>
<view onTap="clear">清空</view>
</view>
</view>
</view>
// API-DEMO page/API/get-network-type/get-network-type.js
Page({
data: {
hasNetworkType: false
},
onLoad() {
this.onChange = this.onChange.bind(this);
// my.onNetworkChange(this.onChange);
},
onChange(res){
console.log('onNetworkChange', res);
this.setData({
hasNetworkType: true,
networkType: res.networkType
});
},
onUnload() {
// my.offNetworkChange(this.onChange);
},
getNetworkType() {
my.getNetworkType({
success: (res) => {
this.setData({
hasNetworkType: true,
networkType: res.networkType
})
}
})
},
clear() {
this.setData({
hasNetworkType: false,
networkType: ''
})
},
});
/* API-DEMO page/API/get-network-type/get-network-type.acss */
.page-body-info {
height: 200rpx;
}
.page-body-text-network-type {
font-size: 80rpx;
font-family: Helvetica;
}