公司近期將收費的功能排期了胎署,由于項目做的是線上教育逮矛,提供的服務(wù)屬于虛擬物品。根據(jù)iOS官方的規(guī)定定血,虛擬物品交易只能使用iOS應(yīng)用內(nèi)支付赔癌,其他類似微信、支付寶官方都是明文規(guī)定不允許存在的澜沟。(注:虛擬物品才有此規(guī)定灾票,且iOS官方收稅30%;而有實體物品交易的茫虽,官方允許在提供應(yīng)用內(nèi)支付的前提下铝条,提供其他支付方式供用戶選擇。)
結(jié)合相關(guān)平臺規(guī)定席噩,我們最終確定支付方式為:Android端使用微信支付班缰,iOS使用IAP應(yīng)用內(nèi)支付。
微信支付
不得不說我們這一代程序員是幸運的悼枢,得益于國內(nèi)移動支付的迅猛發(fā)展埠忘,微信支付的流程閉環(huán)比iOS完善了N倍(iOS的槽點一篇文章都寫不完,稍后我再來吐)馒索;同時微信官方所提供的服務(wù)莹妒,至少在國內(nèi)網(wǎng)絡(luò)中,可以認(rèn)定為是百分百可靠的绰上。
- 微信支付的流程相對簡單:
- 客戶端向業(yè)務(wù)后臺發(fā)起一個購買請求
- 業(yè)務(wù)后臺到微信服務(wù)端生成一個訂單
- 將微信訂單信息和自身系統(tǒng)所需的業(yè)務(wù)數(shù)據(jù)整合后返回給客戶端
- 客戶端拿到微信支付信息后旨怠,通過WeChatOpensdk調(diào)起支付
- 在頁面中訂閱支付回調(diào),接受支付信息并做業(yè)務(wù)流程處理(如:進(jìn)入支付結(jié)果頁等流程)
- 最后請求后臺蜈块,由后臺主動去微信系統(tǒng)中查詢最終支付狀態(tài)鉴腻,交回給前端顯示結(jié)果迷扇。
(ps:后端在微信系統(tǒng)中主動查詢訂單轉(zhuǎn)態(tài)是同步的,可以馬上拿到支付結(jié)果)
- 接下來講講開發(fā)爽哎,F(xiàn)lutter使用的是fluwx插件蜓席,簡單易用。在項目中课锌,我對微信支付進(jìn)行了封裝厨内,代碼見下:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:fluwx/fluwx.dart' as fluwx;
class WechatPayment {
StreamSubscription _wxPay;
/// 關(guān)閉微信消息訂閱
void wxSubscriptionClose() => _wxPay?.cancel();
/// 調(diào)起微信支付面板
/// 這里的WxPayModel是業(yè)務(wù)層的數(shù)據(jù),即后臺返回的有關(guān)微信支付訂單的信息
void wxPay(WxPayModel wxPayModel, {VoidCallback onWxPaying, VoidCallback onSuccess, Function(String data) onError}) async {
// 跳轉(zhuǎn)微信支付前渺贤,告訴頁面進(jìn)入微信支付雏胃,頁面層可以做一些關(guān)閉加載框等的操作
onWxPaying?.call();
// 一些異常情況的處理
if (!await fluwx.isWeChatInstalled) return onError?.call('請安裝微信完成支付或使用蘋果手機支付');
if (wxPayModel.appId != Config.WX_APP_ID) return onError?.call('AppID不一致,請聯(lián)系管理員');
// 此方法筆者沒有做單例志鞍,因此支付前嘗試注銷監(jiān)聽丑掺,避免重復(fù)回調(diào)
_wxPay?.cancel();
// 支付回調(diào)
_wxPay = fluwx.weChatResponseEventHandler.listen((event) {
_wxPay?.cancel();
if (event is fluwx.WeChatPaymentResponse) {
if (event.isSuccessful) {
return onSuccess?.call();
} else {
return onError?.call(event.errCode == -1 ? '系統(tǒng)錯誤,請聯(lián)系管理員' : '您取消了支付');
}
}
});
// 發(fā)起支付
fluwx.payWithWeChat(
appId: wxPayModel.appId,
partnerId: wxPayModel.partnerId,
prepayId: wxPayModel.prepayId,
packageValue: wxPayModel.packageValue,
nonceStr: wxPayModel.nonceStr,
timeStamp: wxPayModel.timeStamp,
sign: wxPayModel.sign,
signType: wxPayModel.signType,
extData: wxPayModel.extData,
);
}
}
頁面端是這樣調(diào)用的
WechatPayment paymentUtils = new WechatPayment();
paymentUtils.wxPay(
state.model.wxPayModel,
onError: (String err) {
if (!mounted) return;
// 微信支付錯誤述雾,設(shè)置支付狀態(tài)為false,彈框即可
_isPaying = false;
SchedulerBinding.instance.addPostFrameCallback((_) {
CommonUtils.showToast(err, backgroundColor: Theme.of(context).errorColor);
});
},
onSuccess:(){
_isPaying = true;
},
onWxPaying: () {
// 啟動微信支付兼丰,設(shè)置支付狀態(tài)為true玻孟,關(guān)閉加載框
_isPaying = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context);
});
},
);
但是需要注意,微信的回調(diào)是異步的鳍征,并且有很多種情況是接收不到回調(diào)的黍翎,以下是確定收不到會調(diào)的情況。
微信調(diào)起支付頁面時艳丛,其實是跳轉(zhuǎn)到新的應(yīng)用匣掸,對于我們的應(yīng)用而言是觸發(fā)了前后臺切換的生命周期。
因此在檢測到應(yīng)用返回前臺氮双,并且支付狀態(tài)還在進(jìn)行中時碰酝,可以證明是收不到微信的支付狀態(tài)回調(diào),需要特殊處理下戴差。
收不到的情況有:
// ① 彈出支付框后使用系統(tǒng)返回鍵關(guān)閉送爸;
// ② 進(jìn)入微信支付密碼框后不輸入使用系統(tǒng)導(dǎo)航切回app或者系統(tǒng)返回鍵返回;
// ③ 進(jìn)入微信后直接返回桌面再回到應(yīng)用暖释;
// ④ 彈出支付框后鎖屏再開屏袭厂;
// ⑤ 彈出支付款后下拉任務(wù)欄;
// ⑥ 輸入密碼成功后球匕,直接返回桌面或者使用系統(tǒng)導(dǎo)航或者使用返回鍵返回app
// ⑦ 退出微信登錄纹磺,進(jìn)行支付后直接登錄微信,在登錄過程中回到app
// ⑧ 在系統(tǒng)應(yīng)用管理中雙開微信后亮曹,調(diào)起支付后不點擊任一個微信端橄杨,而是點擊取消
現(xiàn)在主流的做法是再支付頁面監(jiān)聽app的生命周期秘症,即由后臺切回前臺的時候,檢測下狀態(tài)讥珍,若還在支付中历极,直接進(jìn)入查詢結(jié)果頁面,由后臺去檢驗訂單衷佃,拿到結(jié)果顯示即可趟卸。(后臺主動查詢理論上還是存在微信服務(wù)端延時的問題,因此后臺進(jìn)行查詢的時候氏义,建議采取輪詢機制锄列,若是沒有支付成功的話,延時5秒后再確認(rèn)下更保險)
class _XXXPageState extends State<XXXPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); //添加觀察者
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); //銷毀觀察者
super.dispose();
}
/// 應(yīng)用狀態(tài)監(jiān)聽
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
{
if (Platform.isAndroid && _isPaying) {
_isPaying = false;
// 監(jiān)聽到時安卓設(shè)備并且支付還在進(jìn)行中惯悠,程序員要根據(jù)業(yè)務(wù)做一下處理
break;
}
default:
break;
}
super.didChangeAppLifecycleState(state);
}
}
到此邻邮,微信支付很愉快的解決了,以上代碼是抽象出來的工具類克婶,可以直接使用筒严;但是不涉及任何業(yè)務(wù)流程的開發(fā),這個需要使用者自己去補充情萤。
綜上鸭蛙,微信支付流程主線可簡單粗暴總結(jié)為:服務(wù)端生成訂單 → 客戶端調(diào)起支付 → 客戶端通知服務(wù)端核驗訂單 → 客戶端拿到最終結(jié)果 → 客戶端final支付。
整個過程形成閉環(huán)筋岛,有理有據(jù)娶视,數(shù)據(jù)都由后端去操作安全合理。(最重點是前端工作量簡直不要太少)睁宰。
可是肪获,iOS就不一樣了,簡直不要太惡心柒傻!
iOS IAP應(yīng)用內(nèi)支付
- IAP孝赫,即in-app Purchase,蘋果推出的App內(nèi)購買虛擬商品的方式红符,基于AppStore賬戶的支付方式寒锚。由于iOS整個體系都是基于自己的一套系統(tǒng)的(不像上面的微信支付,是第三方支付平臺)违孝,因此在開發(fā)之前刹前,我們需要到Apple開發(fā)者中心完成以下步驟:
1. 簽署協(xié)議和銀行業(yè)務(wù)
2. 在后臺創(chuàng)建App內(nèi)購買項目,這里所有的價格都是Apple規(guī)定好的雌桑,我們只有選擇的資格喇喉,沒辦法自定價格。創(chuàng)建完成后校坑,每個項目會有sku和productId
3. 添加沙盒測試員Apple
以上步驟參考內(nèi)容引自站內(nèi)大神:Geniune - 支付流程:應(yīng)用通過sku向服務(wù)端獲取商品列表 → 列表中取出對應(yīng)產(chǎn)品請求支付 → 進(jìn)入appStore支付 → 頁面監(jiān)聽支付回調(diào)拿到驗證票據(jù) → 業(yè)務(wù)后臺拿到應(yīng)用接收到的票據(jù)后去Apple官網(wǎng)進(jìn)行校驗即可拣技。
流程很簡單千诬,簡單到幾乎不用跟業(yè)務(wù)后臺打交代,但是坑卻隨之而來:
① 支付數(shù)據(jù)完全依賴前端應(yīng)用膏斤,很難跟業(yè)務(wù)后臺的訂單系統(tǒng)一一對應(yīng)徐绑;
② 針對①的問題,IAP支付支持傳遞skPayment對象莫辨,里面的applicationUsername經(jīng)常用來保存系統(tǒng)的OrderId傲茄;
但是應(yīng)用支付成功后收到的回調(diào)中,applicationUsername卻偶爾會出現(xiàn)為null的情況沮榜,沒有了對應(yīng)關(guān)系盘榨,就沒辦法核銷業(yè)務(wù)系統(tǒng)中的訂單從而為用戶充值;
③ iOS支付回調(diào)非常不穩(wěn)定蟆融,有時延遲嚴(yán)重草巡;且沒有任何注定查詢的方法;
④ iOS應(yīng)用內(nèi)支付有很多異常情況要處理型酥,最常見的就是沒有登錄山憨、沒有同意最新的iOS支付協(xié)議等,都會發(fā)送給app支付失敗的回調(diào)弥喉;
但是當(dāng)用戶登錄或是同意后郁竟,iOS系統(tǒng)又會觸發(fā)新的支付,導(dǎo)致舊的附帶業(yè)務(wù)訂單號的支付無效档桃,莫名又多出一個沒有訂單號的新支付;
⑤ 國內(nèi)網(wǎng)上資料極度缺乏憔晒,基本都是19年以前的藻肄,F(xiàn)lutter的文章更是少的可憐,可參考性不強拒担。
⑥ 測試文檔對于中斷購買的測試流程有巨坑嘹屯,后面菜單一定不要錯過~
通過查看文檔和不斷調(diào)試,我們發(fā)現(xiàn):
① 支付錯誤的回調(diào)从撼,基本能馬上收到州弟;
② 上面流程說到IAP支付需要手動結(jié)束支付流程。同時iOS規(guī)定不能對同一個skuId重復(fù)發(fā)起多次支付的低零,只要當(dāng)前skuId有沒有final的支付婆翔,再次發(fā)起都會失敗掏婶;
② 無論支付成功或失敗啃奴,只要app沒有主動對當(dāng)前支付進(jìn)行final,每次啟動app后雄妥,app都會收到這個支付信息的通知最蕾;
③ 關(guān)于applicationUsername依溯,只有在支付完成馬上收到回調(diào)的情況下,回調(diào)信息才會有這個信息瘟则;到②中的情況黎炉,肯定不會返回applicationUsername;
④ 沒有applicationUsername就意味著訂單對不上醋拧,因此我們需要進(jìn)行湊單機制慷嗜。
綜上,我們對異常處理有了確定方案:
① app發(fā)起支付后趁仙,需要將業(yè)務(wù)OrderId和skuId進(jìn)行持久化存儲(即卸載應(yīng)用都不會刪除的數(shù)據(jù))洪添;
②只要持久化存儲不為空,啟動app就需要馬上啟動監(jiān)聽雀费,以接收iOS系統(tǒng)的訂單推送干奢;
③ 支付出錯可以final當(dāng)前支付,但是支付成功必須明確接收到iOS推送并且后臺核驗成功后盏袄,才能final忿峻,并刪除持久化存儲。
最終辕羽,結(jié)合到業(yè)務(wù)系統(tǒng)和特殊情況的處理后逛尚,支付流程應(yīng)該如下:
- 業(yè)務(wù)后臺返回商品列表時,需要附加返回對應(yīng)的skuId
- app通過skuId請appStore請求商品信息
- app對商品發(fā)起支付刁愿,并將業(yè)務(wù)訂單號存儲在applicationUsername中绰寞,發(fā)起成功寫入持久化存儲,狀態(tài)為pending
- 接收iOS系統(tǒng)回調(diào)铣口,失敗馬上final支付滤钱,更改對應(yīng)持久化存儲狀態(tài)為cancle;成功拿到票據(jù)和業(yè)務(wù)OrderId發(fā)送給后臺
- 后臺調(diào)取Apple服務(wù)端接口脑题,傳入票據(jù)(票據(jù)其實儲存著最新的時間件缸,appStore用戶信息等)
- 后臺獲取到Apple返回的當(dāng)前appStore用戶所有支付的前100條記錄,拿到productId到數(shù)據(jù)庫有中匹配該用戶是否有未核銷的訂單叔遂,并對應(yīng)修改業(yè)務(wù)訂單狀態(tài)
- app確認(rèn)核銷成功他炊,final支付,并且刪除持久化存儲
同時還需要做一些特殊處理:
- app剛啟動時已艰,若是持久化存儲不為空痊末,需要馬上啟動iOS支付訂閱監(jiān)聽,以接收iOS對未完成訂單的推送哩掺;
- 由于iOS限制了同一個skuId不能重復(fù)發(fā)起支付舌胶,因此持久化存儲中,一個skuId永遠(yuǎn)只會有一條記錄疮丛。因此當(dāng)app接收到的支付推送applicationUsername為null幔嫂,采取湊單機制辆它,原則是:通過skuId找到存儲記錄,拿到其對應(yīng)的OrderId履恩,發(fā)給后臺核驗锰茉。
- 接下來進(jìn)入開發(fā),F(xiàn)utter采用的是in_app_purchase插件切心,官方提供的飒筑,支持google和IAP支付;而持久化存儲用的是flutter_secure_storage插件绽昏。
依據(jù)上面的流程协屡,我同樣封裝了工具類。而且由于可能會在多個地方調(diào)用起監(jiān)聽全谤,所有必須是單例模式肤晓,代碼如下:
import 'dart:async';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
// iOS支付單一實例
final iOSPayment = IOSPayment();
class IOSPayment {
/// 單例模式
static final IOSPayment _iosPayment = IOSPayment.init();
factory IOSPayment() {
return _iosPayment;
}
IOSPayment.init();
// 應(yīng)用內(nèi)支付實例
InAppPurchaseConnection purchaseConnection = InAppPurchaseConnection.instance;
FlutterSecureStorage storage = new FlutterSecureStorage();
// iOS訂閱監(jiān)聽
StreamSubscription<List<PurchaseDetails>> subscription;
/// 判斷是否可以使用支付
Future<bool> isAvailable() async => await purchaseConnection.isAvailable();
// 開始訂閱
void startSubscription() async {
if (subscription != null) return;
print('>>> start subscription');
// 支付消息訂閱
Stream purchaseUpdates = purchaseConnection.purchaseUpdatedStream;
subscription = purchaseUpdates.listen(
(purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('>>> pending');
// 業(yè)務(wù)代碼略:有訂單開始支付,向外部發(fā)出通知认然,并記錄到緩存中补憾;
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('>>> error');
// 業(yè)務(wù)代碼略:有訂單支付錯誤,向外部發(fā)出通知
// 下面是刪除
String value = await storage.read(key: purchaseDetails.productID);
String orderId = value.split('¥')[0];
writeStorage(purchaseDetails.productID, orderId, 'cancel');
finalTransaction(purchaseDetails);
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('>>> purchased');
String orderId = purchaseDetails.skPaymentTransaction.payment.applicationUsername;
if (orderId == null || orderId.isEmpty) {
// 如果applicationUsername為空卷员,執(zhí)行湊單
orderId = await foundRecentOrder(purchaseDetails.productID);
}
if (orderId.isEmpty) {
// 湊單失敗盈匾,找不到業(yè)務(wù)單號,結(jié)束
finalTransaction(purchaseDetails);
BlocProvider.of<PaymentUtilsBloc>(Application.navigatorState.currentContext).add(IosPayFailureEvent(errorMessage: '支付出錯啦毕骡,請稍后再試~'));
return;
}
// 業(yè)務(wù)代碼略:支付成功削饵,向外部發(fā)出通知
// 業(yè)務(wù)代碼略:開始核驗訂單,核驗結(jié)果由外部監(jiān)聽
);
}
}
});
},
onDone: () {
stopListen();
},
onError: (error) {
stopListen();
},
);
}
/// 檢查sku是否有對應(yīng)商品
Future<bool> checkProductBySku(String sku, {Function(String err) onError}) async {
if (!await isAvailable()) {
onError?.call('無法連接AppStore未巫,請稍后再試');
return false;
}
ProductDetailsResponse appStoreProducts = await purchaseConnection.queryProductDetails([sku].toSet());
if (appStoreProducts.productDetails.length == 0) {
onError?.call('沒有找到相關(guān)產(chǎn)品窿撬,請聯(lián)系管理員');
return false;
}
return true;
}
/// 啟動支付
void iosPay(String sku, String orderId, {Function(String err) onError}) async {
// 獲取商品列表
ProductDetailsResponse appStoreProducts = await purchaseConnection.queryProductDetails([sku].toSet());
// 發(fā)起支付
purchaseConnection
.buyNonConsumable(
purchaseParam: PurchaseParam(
productDetails: appStoreProducts.productDetails.first,
applicationUserName: orderId,
),
)
.then((value) {
if (value) {
// 只要能發(fā)起,就寫入
writeStorage(sku, orderId, 'pending');
}
}).catchError((err) {
onError?.call('當(dāng)前商品您有未完成的交易橱赠,請等待iOS系統(tǒng)核驗后再次發(fā)起購買尤仍。');
print(err);
});
}
writeStorage(String key, String value, String status) {
storage.write(key: key, value: '$value¥$status');
}
// 關(guān)閉交易
void finalTransaction(PurchaseDetails purchaseDetails) async {
await purchaseConnection.completePurchase(purchaseDetails);
// 每完成一張訂單進(jìn)行緩存的清除
if (!await checkStorage()) {
stopListen();
}
}
// 湊單機制
Future<String> foundRecentOrder(String sku) async {
String orderId = '';
String values = await storage.read(key: sku);
if (values != null) {
orderId = values.split('¥')[0];
}
return orderId;
}
// 校驗是否還有緩存
Future<bool> checkStorage() async {
Map<String, String> remainingValues = await storage.readAll();
return remainingValues.isNotEmpty;
}
// 關(guān)閉監(jiān)聽
stopListen() async {
subscription?.cancel();
subscription = null;
}
}
頁面調(diào)用時箫津,建議啟用定時器狭姨,因為iOS回調(diào)不穩(wěn)定,所以監(jiān)聽到應(yīng)用回到前臺時開始30秒計時苏遥;30秒內(nèi)沒有收到支付回調(diào)饼拍,需要做對應(yīng)提示,這一塊也是存業(yè)務(wù)流程田炭,我這里不做代碼展示师抄。下面代碼是如何調(diào)用上面工具類的:
iOSPayment.startSubscription();
iOSPayment.iosPay(
state.skuId,
state.model.orderId,
onError: (String err) {
if (!mounted) return;
// 支付遇到錯誤,馬上停止定時器教硫,并且關(guān)掉彈框
},
);
// 應(yīng)用啟動時
if (Platform.isIOS && await iOSPayment.checkStorage()) {
// 啟動訂閱:支付緩存未清除完畢叨吮、機型可使用應(yīng)用內(nèi)支付
iOSPayment.startSubscription(needDelayed: true);
}
測試IAP中斷購買的測試
- 這個測試是模擬用戶點擊購買協(xié)議的操作辆布,當(dāng)彈出系統(tǒng)協(xié)議彈框時,iOS會發(fā)出一個支付錯誤的消息茶鉴;這個時候我們的代碼會final這個支付,并且將持久化中對應(yīng)skuId的信息狀態(tài)改為cancel;
- 然后用戶同意后娩缰,iOS會再發(fā)起一個同樣的不帶OdrerId(是的熙含,被弄丟了。割粮。盾碗。。)的訂單舀瓢,用戶支付成功后廷雅,我們的代碼就會收到支付成功的沒有OdrerId的推送,在持久化存儲中執(zhí)行湊單機制后氢伟,再發(fā)給后臺核銷榜轿。
如何模擬這個流程呢?看看官方文檔描述,下面是譯文:
#### 設(shè)置測試
通過[登錄App Store Connect](https://help.apple.com/app-store-connect/#/devcd5016d31)啟用對Sandbox Apple ID的中斷購買朵锣,然后:
1. 在“用戶和訪問”中谬盐,單擊邊欄中沙箱下的“測試器”。在右側(cè)诚些,您可以查看您的Sandbox Apple ID飞傀。
2. 選擇您要為其啟用中斷購買的Sandbox Apple ID。如果已啟用诬烹,則會在“中斷購買”列下看到一個復(fù)選標(biāo)記砸烦。
3. 在出現(xiàn)的對話框中,選擇“此測試儀的中斷購買”绞吁。
#### 開始測試
1. 在測試設(shè)備上幢痘,使用已中斷購買的沙盒Apple ID登錄。
2. 在您的應(yīng)用中家破,選擇“購買”或“訂閱”進(jìn)行應(yīng)用內(nèi)購買颜说。
3. 觀察到系統(tǒng)顯示付款單。
4. 在您的代碼中汰聋,驗證付款隊列在狀態(tài)下是否收到新交易门粪。
5. 在設(shè)備上,驗證付款單烹困。
6. 在您的代碼中玄妈,觀察到付款失敗。付款隊列在狀態(tài)中接收更新的交易。
7. 檢查您的代碼調(diào)用是否將其從隊列中刪除拟蜻。
8. 在設(shè)備上绎签,觀察到系統(tǒng)顯示“條款和條件”,從而中斷了購買(因為您已配置了沙盒環(huán)境)酝锅。
9. 在設(shè)備上辜御,點擊以同意條款和條件。
10. 在您的代碼中屈张,驗證付款隊列接收到的新交易處于與失敗交易相同且數(shù)量相同的狀態(tài).
11. 在您的代碼中擒权,驗證收據(jù)。檢查您的應(yīng)用是否提供了服務(wù)或產(chǎn)品阁谆,然后致電碳抄。
12. 在設(shè)備上,用戶應(yīng)觀察到購買成功场绿。
也就是說在Apple后臺把沙盒測試賬號設(shè)置為中斷即可剖效。但是無論我怎么同意,收到的還是支付失敗的訂閱焰盗。其實是因為文檔寫漏了璧尸,中斷后app彈出同意協(xié)議彈框,也就是上面第8步熬拒,這個時候必須在后臺把中斷測試關(guān)了爷光,然后再執(zhí)行第九步。(就是這么狗血澎粟,官方文檔不給力蛀序,網(wǎng)上也沒有任何資料,最后還是在官方論壇活烙,看到某個QA的評論才找到的靈感徐裸。。啸盏。這里也感謝公司大佬花了半天專門找這方面的資料重贺。)
寫在最后
感謝大家孜孜不倦看到最后,這篇長文希望能幫助開發(fā)支付的小伙伴少踩一些坑回懦。
IAP的支付確實是很坑气笙,但如果站在iOS開發(fā)者的角度來看。其實也能理解:他們是做手機系統(tǒng)的粉怕,他們能保證系統(tǒng)內(nèi)部的所有支付流程健民,根本不care開發(fā)者的業(yè)務(wù)邏輯抒巢。
但無論如何贫贝,這種方式對于開發(fā)者,確實是極度不友善的;另外稚晚,還有一種流程崇堵,app發(fā)起支付后,只要有回調(diào)就馬上final客燕,成功就發(fā)給后臺鸳劳,由后臺去執(zhí)行湊單機制,這種對于前端其實更合理也搓,畢竟數(shù)據(jù)存在客戶端永遠(yuǎn)是不夠安全赏廓,但是這樣app就有可能對同一個skuId瘋狂發(fā)起購買,后臺湊單時傍妒,就做不到一一對應(yīng)幔摸。有利有弊吧~~~
小弟班門弄斧,希望能一起學(xué)習(xí)進(jìn)步2贰<纫洹!