漏單的原因
蘋果應(yīng)用內(nèi)支付漏單的原因有很多,例如:驗證階段钞它,app 閃退彭沼、app 斷網(wǎng)或者網(wǎng)絡(luò)不好、app 登錄超時
根本原因在于蘋果只負責收錢画侣,而收錢之后的校驗工作交給開發(fā)者自己來處理冰悠,開發(fā)者拿到憑證 receipt
后,去請求自己的服務(wù)器驗證棉钧。
處理漏單的過程
- 用戶已經(jīng)付費屿脐,此時提示購買完成
- 開發(fā)者拿著獲取的憑證
receipt
去請求自己的服務(wù)器,并且客戶端記錄并且本地記錄這條key
為transactionID
的交易相關(guān)信息(包括訂單號paymentID
, 交易號transactionID
)宪卿,另外憑證receipt
最好不要保存在客戶端 - 服務(wù)器拿到客戶端傳過來的交易相關(guān)信息后的诵,首先校驗訂單號是否正確,是否與用戶相匹配佑钾,避免用戶切換賬號的問題西疤。校驗訂單號通過后再去蘋果服務(wù)器校驗憑證,這里需要注意休溶,在蘋果審核的時候是走沙盒環(huán)境的代赁,生產(chǎn)走正式環(huán)境,服務(wù)端需要先正常流程驗證正式環(huán)境兽掰,當返回錯誤碼顯示環(huán)境錯誤的時候芭碍,走測試環(huán)境
- 正常情況下,校驗憑證通過孽尽,客戶端
finish
掉 這個transaction
窖壕,并且本地清除掉這條key
為transactionID
對應(yīng)的交易信息,但是杉女,當在請求服務(wù)端的時候瞻讽,出現(xiàn)程序閃退、網(wǎng)絡(luò)不好的時候熏挎,就校驗失敗了速勇,但是用戶的錢蘋果已經(jīng)扣掉,服務(wù)沒有開通的現(xiàn)象坎拐,也就是漏單了烦磁,如果這時用戶再次點擊這個productID
對應(yīng)的商品,就會報此應(yīng)用將免費恢復(fù)的提示廉白,這里只能在用戶下一次打開應(yīng)用的時候處理漏單流程了 - 當用戶再次啟動應(yīng)用程序的時候个初,當有未
finish
掉的transaction
時,應(yīng)用程序會自動調(diào)用 方法 paymentQueue: updatedTransactions: - 那我們通過什么來判斷這次校驗的是漏單流程還是正常流程呢猴蹂?
布爾值isCash
是判斷漏單流程的條件院溺,如果用戶是點擊購買按鈕進來的,isCash
為YES磅轻, 那我們可以判斷走正常流程珍逸,如果isCash
為NO逐虚,我們就走漏單流程,本地讀取之前保存的交易信息谆膳,再次請求服務(wù)端叭爱,校驗憑證
代碼
- .h 文件
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@interface LxInAppPurchaseManager : NSObject
typedef void (^LxInAppPurchaseProductCompleteSuccessBlock)(id data);
typedef void (^LxInAppPurchaseProductCompleteFailureBlock)(id data);
/**
訂單號,服務(wù)器生成的交易編號
*/
@property(nonatomic,copy)NSString *paymentID;
/**
是否為漏單漱病,如果為真走正常流程买雾,否則走漏單流程
*/
@property (nonatomic, assign)BOOL cash;
/**
單例類
@return 單例對象
*/
+ (instancetype)sharedManager;
/**
點擊某項內(nèi)購商品的購買按鈕
@param productID iTunes Connect 上商品的標識符
*/
- (void)payBtnPressed:(NSString *)productID completeSuccessBlock:(LxInAppPurchaseProductCompleteSuccessBlock)__successBlock
failureBlock:(LxInAppPurchaseProductCompleteFailureBlock)__failureBlock;
/**
開始監(jiān)聽
*/
- (void)startObserver;
/**
停止監(jiān)聽
*/
- (void)stopObserver;
@end
- .m文件
#import "LxInAppPurchaseManager.h"
#import "SignServiceVerifyReceiptTask.h"
#import "LxIapRequestContent.h"
#import "LxArchiverManager.h"
@interface LxInAppPurchaseManager()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic, assign)BOOL isObserver; // 程序是否添加監(jiān)聽
@property(nonatomic,copy)NSString *productID; // iTunes Connect上商品的標識符
@property (nonatomic, copy)NSString *transactionID;
@property (nonatomic, strong)SKProduct *product;
@property (nonatomic, copy)LxInAppPurchaseProductCompleteSuccessBlock successBlock;
@property (nonatomic, copy)LxInAppPurchaseProductCompleteFailureBlock failureBlock;
@end
@implementation LxInAppPurchaseManager
static LxInAppPurchaseManager *purchaseManager = nil;
+ (instancetype)sharedManager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
purchaseManager = [[LxInAppPurchaseManager alloc] init];
});
return purchaseManager;
}
- (void)payBtnPressed:(NSString *)productID completeSuccessBlock:(LxInAppPurchaseProductCompleteSuccessBlock)__successBlock
failureBlock:(LxInAppPurchaseProductCompleteFailureBlock)__failureBlock{
self.successBlock = __successBlock;
self.failureBlock = __failureBlock;
self.productID = productID;
if (!self.productID) {
[MBProgressHUD wyp_error:@"商品標識符不能為空" block:nil];
return;
}
// 判斷用戶是否允許應(yīng)用內(nèi)付費
if ([SKPaymentQueue canMakePayments]){
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productID]];
request.delegate = self;
[request start];
}else
[MBProgressHUD wyp_error:@"用戶不允許內(nèi)購" block:nil];
}
#pragma mark - 監(jiān)聽內(nèi)購
// 開始監(jiān)聽內(nèi)購
- (void)startObserver {
// isObserver 程序是否添加監(jiān)聽
if (!self.isObserver) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
NSLog(@"http://----------------------------- 開始監(jiān)聽內(nèi)購 ------------------------------//");
self.isObserver = YES;
}
}
// 移除監(jiān)聽內(nèi)購
- (void)stopObserver {
if (self.isObserver) {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
NSLog(@"http://----------------------------- 移除監(jiān)聽內(nèi)購 ------------------------------//");
self.isObserver = NO;
}
}
#pragma mark - SKProductsRequestDelegate
// 檢索成功后的回調(diào),從 App Store 獲取產(chǎn)品列表信息請求的響應(yīng)
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSArray *products = response.products;
if (products.count != 0) {
for (SKProduct *_product in products) {
if ([_product.productIdentifier isEqualToString:self.productID]) {
self.product = _product;
}
}
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:self.product];
// 服務(wù)器的交易編號杨帽,也就是訂單號 paymentID
payment.applicationUsername = self.paymentID;
// 發(fā)起購買
NSLog(@"http://----------------------------- 發(fā)起購買 ------------------------------//");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}else{
[MBProgressHUD wyp_error:@"無法獲取商品" block:nil];
}
}
#pragma mark - SKRequestDelegate
// 檢索失敗后的回調(diào)
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
[MBProgressHUD wyp_error:@"請求蘋果服務(wù)器失敗" block:nil];
}
// 檢索結(jié)束
- (void)requestDidFinish:(SKRequest *)request{
NSLog(@"http://----------------------------- 蘋果反饋信息結(jié)束 ------------------------------//");
}
#pragma mark -
// 當用戶購買的操作有結(jié)果時漓穿,就會觸發(fā)下面的回調(diào)函數(shù)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
self.transactionID = transaction.transactionIdentifier;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: // 交易成功
[self purchasedTransaction:transaction];
break;
case SKPaymentTransactionStateFailed: // 交易失敗
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: // 已經(jīng)購買過該商品,消耗型不支持恢復(fù),TODU
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing: // 已經(jīng)在商品列表中
NSLog(@"http://----------------------------- 等待支付 ------------------------------//");
break;
case SKPaymentTransactionStateDeferred: // 最終狀態(tài)未確定
NSLog(@"http://----------------------------- 最終狀態(tài)未確定 ------------------------------//");
break;
default:
break;
}
}
}
// 恢復(fù)購買
- (void)restoredTransation:(SKPaymentTransaction *)transaction{
[MBProgressHUD wyp_error:@"" block:^{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}];
}
// 付款失敗后注盈,結(jié)束交易
- (void)failedTransaction:(SKPaymentTransaction *)transaction{
NSString *detail = @"";
switch (transaction.error.code) {
case SKErrorUnknown:
NSLog(@"SKErrorUnknown");
detail = @"未知的錯誤";
break;
case SKErrorClientInvalid:
NSLog(@"SKErrorClientInvalid");
detail = @"當前蘋果賬戶無法購買商品(如有疑問晃危,可以詢問蘋果客服)";
break;
case SKErrorPaymentCancelled:
NSLog(@"SKErrorPaymentCancelled");
detail = @"購買已取消";
break;
case SKErrorPaymentInvalid:
NSLog(@"SKErrorPaymentInvalid");
detail = @"購買無效(如有疑問,可以詢問蘋果客服)";
break;
case SKErrorPaymentNotAllowed:
NSLog(@"SKErrorPaymentNotAllowed");
detail = @"當前蘋果設(shè)備無法購買商品(如有疑問老客,可以詢問蘋果客服)";
break;
case SKErrorStoreProductNotAvailable:
NSLog(@"SKErrorStoreProductNotAvailable");
detail = @"當前商品不可用";
break;
default:
NSLog(@"No Match Found for error");
detail = @"未知的錯誤";
break;
}
[MBProgressHUD wyp_error:detail block:^{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(@"http://----------------------------- 購買失敗 ------------------------------//");
NSLog(@"http://----------------------------- 交易失敗訂單號開始 ------------------------------//");
NSLog(@"transaction.payment.applicationUsername:%@",transaction.payment.applicationUsername);
NSLog(@"http://----------------------------- 交易失敗訂單號結(jié)束 ------------------------------//");
}];
}
// 付款成功后僚饭,請求服務(wù)端
- (void)purchasedTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"http://----------------------------- 交易成功訂單號開始 ------------------------------//");
NSLog(@"transaction.payment.applicationUsername:%@",transaction.payment.applicationUsername);
NSLog(@"http://----------------------------- 交易成功訂單號結(jié)束 ------------------------------//");
NSLog(@"http://----------------------------- 購買成功驗證訂單 ------------------------------//");
NSLog(@"http://----------------------------- 獲得憑證receipt ------------------------------//");
// 獲取憑證
NSData *data = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
NSString *receipt = [data base64EncodedStringWithOptions:0];
NSLog(@"http://----------------------------- receipt 開始 ------------------------------//");
NSLog(@"receipt(base64String):%@",receipt);
NSLog(@"http://----------------------------- receipt 結(jié)束 ------------------------------//");
if (!receipt) {
[MBProgressHUD wyp_error:@"獲取憑證失敗" block:^{
return;
}];
}
if (self.cash) {
NSLog(@"http://----------------------------- 處理正常流程 ------------------------------//");
NSMutableDictionary *requestContent = [NSMutableDictionary dictionary];
[requestContent setValue:receipt forKey:@"receiptData"];
[requestContent setValue:self.transactionID forKey:@"transactionId"];
[requestContent setValue:self.paymentID forKey:@"paymentId"];
//把這個信息存起到本地
LxIapRequestContent *iapRequestContent = [LxIapRequestContent instanceWithDic:requestContent error:nil];
[[LxArchiverManager shareManager] saveArchiverData:iapRequestContent andKey:transaction.transactionIdentifier];
self.cash = NO;
[self tellServerBuySuccessWithTradeInfo:requestContent transaction:transaction];
}else{
NSLog(@"http://----------------------------- 處理漏單流程 ------------------------------//");
//從本地讀取交易信息
LxIapRequestContent *iapRequestContent = [[LxArchiverManager shareManager] queryArchiverDataWithKey:transaction.transactionIdentifier];
NSMutableDictionary *requestContent = [NSMutableDictionary dictionary];
[requestContent setValue:receipt forKey:@"receiptData"];
[requestContent setValue:iapRequestContent.transactionId forKey:@"transactionId"];
[requestContent setValue:iapRequestContent.paymentId forKey:@"paymentId"];
[self tellServerBuySuccessWithTradeInfo:requestContent transaction:transaction];
}
}
#pragma mark - 請求服務(wù)端,記錄交易
// 驗證交易
- (void)tellServerBuySuccessWithTradeInfo:(NSMutableDictionary *)tradeInfo transaction:(SKPaymentTransaction *)transaction{
[SignServiceVerifyReceiptTask startWithDic:tradeInfo success:^(NSDictionary *__data, id __task) {
[[LxArchiverManager shareManager] clearArchiverDataWithKey:transaction.transactionIdentifier];
BOOL result = [[__data objectForKey:@"payResult"] boolValue];
NSString *msg = [__data objectForKey:@"message"];
if(!result){
[MBProgressHUD wyp_error:msg block:^{
NSLog(@"http://----------------------------- 驗證失敗,購買結(jié)束 ------------------------------//");
if (self.failureBlock) {
self.failureBlock(__data);
}
return ;
}];
}else{
[MBProgressHUD wyp_success:msg block:^{
NSLog(@"http://----------------------------- 驗證成功,購買結(jié)束 ------------------------------//");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (self.successBlock) {
self.successBlock(__data);
}
}];
}
} failure:^(id __data, id __task) {
[MBProgressHUD wyp_error:@"驗證失敗,購買結(jié)束" block:^{
NSLog(@"http://----------------------------- 驗證失敗,訂單結(jié)束 ------------------------------//");
if (self.failureBlock) {
self.failureBlock(__data);
}
}];
}];
}
@end
如有問題胧砰,歡迎留言討論