工作中用flutter做過一個(gè)數(shù)字藏品的項(xiàng)目冻押,蘋果端需要加入內(nèi)購(gòu)才能上線發(fā)布杯拐,本文主要寫一下使用flutter插件in_app_purchase: ^3.0.7
實(shí)現(xiàn)蘋果內(nèi)購(gòu)功能以及遇到的問題旁舰。純屬技術(shù)交流,歡迎評(píng)論交流.
主要思路:
1.在蘋果開發(fā)賬號(hào)上面建好虛擬商品。
2.將在蘋果開發(fā)賬號(hào)上面建好的虛擬商品錄入后臺(tái)系統(tǒng),便于后端通過接口返回給前端對(duì)應(yīng)的商品id拨匆。
3.使用in_app_purchase
插件,通過后端返回的商品id讯检,查詢到商品信息琐鲁,再將商品信息提交給蘋果服務(wù)器發(fā)起支付請(qǐng)求卫旱,再將蘋果服務(wù)器返回的支付結(jié)果傳給自己的服務(wù)器。
4.自己的服務(wù)器通過前端傳給的支付結(jié)果去進(jìn)行發(fā)貨以及其他操作围段。
詳細(xì)操作:
關(guān)于怎么在蘋果開發(fā)賬號(hào)上面建商品過程有些繁瑣顾翼,不是本文的重點(diǎn)內(nèi)容在這里就不詳細(xì)說明了,可以網(wǎng)上搜搜奈泪,有很多博主寫的很詳細(xì)适贸,貼一個(gè)圖示。
in_app_purchase
的使用
in_app_purchase
是谷歌官方出的插件涝桅,相對(duì)來說還是很堅(jiān)挺的拜姿,所以用的還是比較放心。我是用的in_app_purchase: ^3.0.7 #內(nèi)購(gòu)
版本冯遂,直接導(dǎo)入項(xiàng)目使用即可蕊肥。
當(dāng)點(diǎn)到某個(gè)商品獲取到后臺(tái)返回的ProductId,調(diào)用下面的方法债蜜,仔細(xì)看注釋即可
/// 蘋果內(nèi)購(gòu) (購(gòu)買)
Future<void> applePay(Map dataMap) async {
/// _inAppPurchase是否有效
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
return;
}
/// 如果是iOS設(shè)備進(jìn)行設(shè)置代理晴埂,接口蘋果服務(wù)器的回調(diào)。
if (Platform.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
}
/// 獲取后臺(tái)返回的產(chǎn)品id
if (dataMap['iosProductId'] == null) {
Loading.dissmiss();
AppDialog.showShucangText(title: '暫無產(chǎn)品');
return;
}
_kProductIds.add(dataMap['ProductId']);
/// 查詢后臺(tái)返回的ProductId是否在蘋果服務(wù)器上注冊(cè)了
final ProductDetailsResponse productDetailResponse =
await _inAppPurchase.queryProductDetails(_kProductIds.toSet());
/// 查詢不到說明沒注冊(cè)
if (productDetailResponse.error != null) {
Loading.dissmiss();
AppDialog.showShucangText(title: '獲取產(chǎn)品信息失敗');
return;
}
/// 查詢不到商品詳情說明沒注冊(cè)
if (productDetailResponse.productDetails.isEmpty) {
Loading.dissmiss();
AppDialog.showShucangText(title: '暫無產(chǎn)品');
return;
}
_products = productDetailResponse.productDetails;
/// 查詢成功
ProductDetails productDetails = _products[0];
FlutterKeychain.put(key: "iosPrice", value: productDetails.price);
late PurchaseParam purchaseParam;
purchaseParam = PurchaseParam(
productDetails: productDetails,
/// 添加自己服務(wù)器上生成的訂單
applicationUserName:
'${dataMap['orderNo11']}' + '${dataMap['orderNo22']}');
/// 向蘋果服務(wù)器發(fā)起支付請(qǐng)求
_inAppPurchase.buyConsumable(purchaseParam: purchaseParam);
}
注意點(diǎn):
上面的 purchaseParam
就是向蘋果服務(wù)器發(fā)起支付請(qǐng)求時(shí)傳遞的參數(shù)寻定,在purchaseParam
這個(gè)對(duì)象里儒洛,可以設(shè)置自定義參數(shù),來方便判斷那筆訂單狼速。
監(jiān)聽蘋果服務(wù)器的回調(diào)
Future<void> _listenToPurchaseUpdated(
List<PurchaseDetails> purchaseDetailsList) async {
for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.status == PurchaseStatus.pending) {
/// 等待購(gòu)買中
} else if (purchaseDetails.status == PurchaseStatus.canceled) {
/// 取消訂單
_inAppPurchase.completePurchase(purchaseDetails);
Loading.dissmiss();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
/// 購(gòu)買出錯(cuò)
Loading.dissmiss();
AppDialog.showShucangText(title: '購(gòu)買出錯(cuò)');
_inAppPurchase.completePurchase(purchaseDetails);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
/// 調(diào)用后臺(tái)接口,發(fā)放商品
deliverProduct(purchaseDetails);
}
}
}
}
當(dāng) purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored
說明在蘋果服務(wù)器上發(fā)起請(qǐng)求成功了琅锻,然后再通知一下自己的服務(wù)器去發(fā)貨然后一定記得要調(diào)用下這個(gè)方法_inAppPurchase.completePurchase(purchaseDetails)不然下一次就拉不起蘋果支付來了
就萬事大吉了。但是你想多了向胡,測(cè)試可不這樣整恼蓬。
這張圖片相信大家很熟悉吧,當(dāng)在蘋果服務(wù)器上付款成功后變會(huì)彈出這個(gè)彈窗僵芹,只有當(dāng)用戶點(diǎn)擊“好”時(shí)才會(huì)觸發(fā)成功的回掉处硬,也就是才會(huì)代碼才會(huì)走到這個(gè)purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored
判斷里面來,那如果這時(shí)候拇派,測(cè)試同學(xué)荷辕,不點(diǎn)擊“好”直接退到App后臺(tái),把App直接殺死或者把App卸載了件豌,那自己的服務(wù)器是不知道用戶在蘋果服務(wù)器上已經(jīng)完成付款了疮方,是不會(huì)給用戶發(fā)貨的,這也就是老生常談的丟單問題
茧彤。
丟單問題處理
使用
_inAppPurchase.purchaseStream是用來監(jiān)聽消息隊(duì)列的回調(diào)的,也就是所有訂單的狀態(tài)以及信息回調(diào),in_app_purchase這個(gè)屬性的文檔中這么說到:
IMPORTANT! You must subscribe to this stream as soon as your app launches,
preferably before returning your main App Widget in main(). Otherwise you
will miss purchase updated made before this stream is subscribed to.
重要骡显!你必須在應(yīng)用程序啟動(dòng)后立即訂閱此流,
最好在main()中返回主應(yīng)用程序小部件之前。否則你
將錯(cuò)過訂閱此流之前更新的購(gòu)買惫谤。
也就是說當(dāng)我們的App在第一次啟動(dòng)的時(shí)候可以訂閱此流來完成補(bǔ)單的操作壁顶,但是如果用戶是之前丟單了,然后把App又卸載了石挂,再次下載打開App后并沒有進(jìn)行登錄操作博助,那用戶的登錄信息都拿不到怎么進(jìn)行補(bǔ)單操作呢?
補(bǔ)單解決方案
讓后端出一個(gè)補(bǔ)單的接口痹愚,在補(bǔ)單時(shí)只需要傳一個(gè)訂單號(hào)即可,那App都刪除了蛔糯,之前的訂單號(hào)客戶端怎么獲取呢拯腮?使用flutter_keychain
來實(shí)現(xiàn),flutter_keychain
就是使用的iOS的鑰匙串來實(shí)現(xiàn)的蚁飒,當(dāng)用戶在蘋果服務(wù)器下單時(shí)动壤,在鑰匙串中保存后端生成的訂單號(hào),然后再商品成功發(fā)貨后刪除鑰匙串里面的訂單號(hào)淮逻,完成一個(gè)完整的購(gòu)買過程琼懊,再購(gòu)買時(shí)任何一環(huán)出了問題鑰匙串里面緩存的訂單號(hào)都不會(huì)被清空,這樣在App下一次啟動(dòng)時(shí)爬早,在首頁或者main函數(shù)中使用_inAppPurchase.purchaseStream
監(jiān)聽哼丈,在拿到flutter_keychain
中保存的訂單號(hào)完成補(bǔ)單過程。
注意點(diǎn)
1.在完成蘋果服務(wù)器付款流程后通知到自己服務(wù)器接口也就是驗(yàn)單的接口返回的是成功或者不成功都要調(diào)用_inAppPurchase.completePurchase(purchaseDetails)這個(gè)方法筛严,不然下次就掉不起蘋果支付來了醉旦,當(dāng)然肯定會(huì)在失敗的判斷里面寫明白讓用戶自己去走蘋果退款流程的文案(概率較小,但是也得考慮)
2.商品類型如果是非消耗品的話桨啃,在下單完之后一定寫一個(gè)按鈕供點(diǎn)擊調(diào)用復(fù)原的方法车胡,要是不復(fù)原的話每次下的訂單,訂單號(hào)都是一樣的照瘾,我在開發(fā)時(shí)就遇到了這個(gè)問題匈棘,折騰了半天也發(fā)現(xiàn)是這個(gè)問題。
結(jié)尾
放上一張整個(gè)支付流程的圖示吧