公司項目需要會員購買功能,當(dāng)時一聽到這個要求,就知道和內(nèi)部購買有關(guān),于是開始在網(wǎng)上大量查找相關(guān)資料,也包括其他開發(fā)者面臨的問題。
不過....但是....在實際上線后還是碰到了意想不到的問題,真是措手不及??!委屈
iOS13.3.1最新bug,如果事先未處理好會導(dǎo)致的漏單,這點在最下方詳細(xì)介紹。
在網(wǎng)上找到了一個比較厲害的第三方庫[RMStore](https://github.com/robotmedia/RMStore),雖然我項目中確實最終導(dǎo)入了它,但是感覺它針對我們普通購買功能來說,代碼顯的有些冗余了。介于人家良好的口碑,最終采用他了,不過需要在它里面增加自己的代碼哦。
我們采用的是服務(wù)器接口驗證流程,這樣應(yīng)該更加靠譜一些。
發(fā)起內(nèi)購
并將我們服務(wù)器生成的預(yù)訂單號存儲起來且一并傳入
- (void)addPayment:(NSString*)productIdentifier
user:(NSString*)userIdentifier
success:(void (^)(SKPaymentTransaction *transaction))successBlock
failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock
{
SKProduct *product = [self productForIdentifier:productIdentifier];
if (product == nil)
{
RMStoreLog(@"unknown product id %@", productIdentifier)
if (failureBlock != nil)
{
NSError *error = [NSError errorWithDomain:RMStoreErrorDomain code:RMStoreErrorCodeUnknownProductIdentifier userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Unknown product identifier", @"RMStore", @"Error description")}];
failureBlock(nil, error);
}
return;
}
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
if ([payment respondsToSelector:@selector(setApplicationUsername:)])
{
= userIdentifier;
}
RMAddPaymentParameters *parameters = [[RMAddPaymentParameters alloc] init];
if(userIdentifier){
= userIdentifier;
}
= successBlock;
= failureBlock;
_addPaymentParameters[productIdentifier] = parameters;
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
在支付成功后,將預(yù)訂單號保存起來,并與蘋果的訂單號綁定起來,并存儲到keychain中
#pragma mark Transaction State
- (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
RMStoreLog(@"transaction purchased with product %@", );
if( != nil){
RMAddPaymentParameters *parameters = _addPaymentParameters[];
if(parameters){
//如果這個參數(shù)存在,則肯定是通過主動發(fā)起購買請求引起的
//在支付成功后,將parameters中的預(yù)訂單號存起來,并與蘋果的訂單號綁定起來,并存儲到keychain中
if( && ){
[SAMKeychain setPassword: forService:@"qixiubaodian.server" account:];
NSString *openid = [[BDUserManager manager] getCurrentUserInfo].;
i > 0){
///每次用戶支付完之后,將記錄保存一份到有盟平臺
[[BDEventMonitor monitor] event:Event_Purchase_Buy attributes:@{@"openid":openid,@"orderNumber":.copy}];
///本地數(shù)據(jù)庫也保存一份
[[BDLocalityDataManager manager] savePruchaseTransactionIdentifier: orderNumber: openId:openid];
}
}
}
}
if != nil)
{
[ verifytransaction:transaction success:^{
[self didVerifyTransaction:transaction queue:queue];
} failure:^(NSError *error) {
[self didFailTransaction:transaction queue:queue error:error];
}];
}else{
RMStoreLog(@"WARNING: no receipt verification");
[self didVerifyTransaction:transaction queue:queue];
}
}
最后就仿照RMStore中的驗證代理,實現(xiàn)我們服務(wù)器驗證接口即可。
重點來了...可能的原因
1、我們在前面將服務(wù)器生產(chǎn)的預(yù)訂單號存在applicationUsername上,但是在實際上線中,發(fā)現(xiàn)在需要驗證的時候,一定概率上取出來的為nil值。
2、根據(jù)很多用戶的反饋,發(fā)現(xiàn)用戶在成功支付后,竟然沒有支付成功的回調(diào),就是)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue沒有被調(diào)用。當(dāng)然這個問題猜測是這樣的,因為線上的問題只能根據(jù)事先埋下的點來分析了。既然在驗證的時候取不到預(yù)訂單號,那么最終我們采取的是在內(nèi)購發(fā)起之前就將生成號的預(yù)訂單號保存到本地,只有當(dāng)用戶取消或者支付驗證成功后,才從本地移除。
我這邊做了一個持續(xù)驗證的管理著,只有隊列中有沒有完成的訂單,則間隔一段時間不停的去驗證,直到驗證成功為止,因此,就算本地有多個預(yù)訂單號也沒有關(guān)系。
這一操作上線后,丟單率從30%一下子降到了幾乎為0。
然后還是有些用戶比較極端,在付款成功后,可能由于網(wǎng)絡(luò)差的緣故沒能充值會員成功,用戶竟然將app卸載然后重裝了。針對這個問題我們采取了如下思路:
1、只有用戶點擊購買的時候,將獲取的預(yù)訂單號存儲到keychain中
2、假如用戶取消支付,則在取消支付的回調(diào)中將keychain值刪除,或者更新為空字符串
3、假設(shè)用戶成功支付后,并且有回調(diào)成功,且后臺充值回調(diào)也完成,則將keychain值更新或者刪除
經(jīng)過以上步驟,即使漏單了,并且用戶卸載后重裝app,都可以重新調(diào)用找回預(yù)訂單號進(jìn)行重新充值會員。當(dāng)然了,如果用戶卸載后換手機(jī)那就沒轍了。
蘋果內(nèi)購反正就是這樣坑,我們只能盡可能降低丟單率,但是誰也無法保證沒有丟單。
下面再貼一下我這邊驗證的方法:
- (void)verifyTransaction:(SKPaymentTransaction*)transaction
success:(void (^)())successBlock
failure:(void (^)(NSError *error))failureBlock
{
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 從沙盒中獲取到購買憑據(jù)
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *resultText = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
i == 0){
NSString *errorText = [NSString stringWithFormat:@"獲取的憑證為空,transactionIdentifier:->%@",];
if(transaction. != nil){
errorText = [errorText stringByAppendingFormat:@"applicationUsername:->%@",transaction.];
}
NSError *error = [NSError errorWithDomain:errorText code:RMStoreErrorCodeUnableToCompleteVerification userInfo:@{NSLocalizedDescriptionKey:@"獲取的憑證為空"}];
if(failureBlock){
failureBlock(error);
}
//加入任務(wù)隊列,持續(xù)進(jìn)行驗證請求
[[BDPurchaseManager manager] verifityPurchaseWhenFailWithParams:nil transaction:transaction];
[Bugly reportError:error];
return;
}
//蘋果的訂單ID
NSString *identifier = ;
// 2.5.1第一版內(nèi)購中,由于applicationUsername返回空值,導(dǎo)致漏單,針對部分用戶后臺手動添加了,所以有些則需要進(jìn)行數(shù)據(jù)清除
NSString *uid = [[BDUserManager manager] getCurrentUserInfo].;
i > 0){
if(([identifier isEqualToString:@"420000514257547"] && [uid isEqualToString:@"269378"])){
NSError *error = [NSError errorWithDomain:@"已經(jīng)支付過" code:BDPruchaseManagerERrorCodeVerifiHadPay userInfo:@{NSLocalizedDescriptionKey:@"已經(jīng)支付過"}];
if(failureBlock){
failureBlock(error);
}
return;
}
}
//自家服務(wù)器生成的預(yù)訂單號
NSString *orderId = transaction.;
__block BOOL orderNumberFromFirstTable = false;
i == 0){
//則從本地數(shù)據(jù)庫進(jìn)行查找
orderId = [[BDLocalityDataManager manager] purchaseOrderNumberWithTransactionIdentifier:identifier];
i == 0){
//從keychain上找找有沒有這個對應(yīng)的預(yù)訂單號
NSString *savedOrderNumber = [SAMKeychain passwordForService:@"qixiubaodian.server" account:identifier];
if(savedOrderNumber != nil && != 0){
orderId = savedOrderNumber;
}
//如果keychain上還是沒有,則從本地的初始預(yù)訂單中尋找
i == 0){
i > 0){
orderId = [[BDLocalityDataManager manager] getFirstOrderNumberWithUserId:uid];
i > 0){
orderNumberFromFirstTable = true;
}
}
}
}
}
i == 0){
NSError *error = [NSError errorWithDomain:@"預(yù)訂單號為空" code:RMStoreErrorCodeUnableToCompleteVerification userInfo:@{NSLocalizedDescriptionKey:@"預(yù)訂單號為空"}];
if(failureBlock){
failureBlock(error);
}
//加入任務(wù)隊列,持續(xù)進(jìn)行驗證請求
[[BDPurchaseManager manager] verifityPurchaseWhenFailWithParams:nil transaction:transaction];
NSError *buglyError = [NSError errorWithDomain:[NSString stringWithFormat:@"預(yù)訂單號為空 -> transactionIdentifier:%@,票據(jù):...",identifier] code:0 userInfo:nil];
[Bugly reportError:buglyError];
return;
}
//生成一個加密Key,用resultText拼接key之后在md5加密即可
NSString *secretText = [resultText stringByAppendingString:BD_PURCHASE_SGIN_KEY];
//生成了加密value
NSString *sginValue = [BDPublicWays MD5EncodedString:secretText];
///接下去就是那這些參數(shù)調(diào)用自家服務(wù)器驗證接口了
如何在App Store中顯示要推薦的內(nèi)購商品
1、需要在App Store后臺勾選推廣按鈕
勾選推廣
2、需要實現(xiàn)- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product
一般返回false即可,表示自己處理,如果返回true,則表示系統(tǒng)幫你處理,(我沒有試過哈,別人說的...)
內(nèi)購遇到的問題就這些了,如果各位有什么問題可以留言回復(fù)哈!
2020年4月1號最新更新:
在iOS13.3.1中公司線上出現(xiàn)了幾粒漏單,系統(tǒng)全部為13.3.1,通過事先代碼埋點和本人精明大腦的分析,終于得出一個結(jié)論,那就是:在支付成功回調(diào)結(jié)果
didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
transaction的transactionIdentifier大概率為nil,這應(yīng)該是iOS13.3.1的bug,但是如果緩存機(jī)制處理的好的話,是不會影響最終的結(jié)果,壞就壞在在緩存處理中我出現(xiàn)了一個失誤而導(dǎo)致漏單。好了,這次更新內(nèi)容導(dǎo)致為止,希望可以幫到大家。
2020年4月24號最新更新:
最近線上依然存在漏單情況,通過對用戶的電話回訪和自己實測分析得出,有些情況會導(dǎo)致明明支付成功,但是回調(diào)顯示fail,不知道是不是RMStore的問題,或者是蘋果的bug問題,但也可能是自己的問題。以下場景有可能導(dǎo)致支付成功但是回調(diào)顯示失?。?用戶點擊內(nèi)購 -> 彈出商品購買頁->用戶輸入密碼或指紋支付->顯示支付成功->用戶appstore沒有綁定付款方式->用戶在手機(jī)系統(tǒng)設(shè)置頁面進(jìn)行綁定操作->系統(tǒng)跳轉(zhuǎn)到appstore進(jìn)行再次驗證支付->支付成功。
在以上操作可能發(fā)現(xiàn),app一直在后臺,因為有可能在切會app時導(dǎo)致回調(diào)提示失敗。因此我另外建了一張表,只要發(fā)起內(nèi)購請求前就將訂單號存儲到表中,直到自己這邊驗證成功并給用戶充值后才將這個訂單號從數(shù)據(jù)庫中移除。至此,內(nèi)購漏單問題告一段落。
2021年4月9號最新更新
線上發(fā)生了同一個用戶的三次漏單,
第二個漏單與第一次漏單間隔了4秒,第三次漏單與第二次漏單間隔了15秒。這個地方有點奇怪,我們UI處理上不太可能存在4秒之內(nèi)連續(xù)支付的情況。
最終這三次在下面回掉中:
- (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
全部為nil,
可以分析出:用戶三次支付后,都未成功充值到具體的商品,但是用戶居然至此之后就沒有登陸過了,是用戶太任性了還是用戶本身操作有什么玄機(jī)嗎?
2021年4月15號更新
在一次測試環(huán)境中 發(fā)現(xiàn)了一個奇怪的bug,在完成一個訂單后,且執(zhí)行了finishTransaction操作,但是在下次啟動的時候,在下面的回調(diào)中
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
居然又返回了同一個productIdentifier的訂單,我擦,這是什么鬼,然后我又強(qiáng)制調(diào)用finishTransaction操作,下次啟動時后仍然存在!幸虧這個是在 測試環(huán)境下,如果正式環(huán)境下不得出亂子啊。估計蘋果是在測試啥么玩意吧!
后續(xù)有問題將繼續(xù)跟蹤并更新。
目前更新于2021年8月4日,暫未發(fā)現(xiàn)新的問題!
1.《蘋果怎么弄指紋支付密碼錯誤?終于找到答案了iOS內(nèi)購中碰到的問題與解決方案13.3.1最新問題》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《蘋果怎么弄指紋支付密碼錯誤?終于找到答案了iOS內(nèi)購中碰到的問題與解決方案13.3.1最新問題》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/gl/3076521.html