// // TFIAPManager.m // TFReader // // Created by 谢腾飞 on 2020/12/10. // Copyright © 2020 xtfei_2011@126.com. All rights reserved. // #import "TFIAPManager.h" #import "TFSandBoxHelper.h" #import "NSDate+TFExtension.h" static NSString * const receiptKey = @"receipt_key"; static NSString * const dateKey = @"date_key"; static NSString * const userIdKey = @"userId_key"; dispatch_queue_t iap_queue() { static dispatch_queue_t as_iap_queue; static dispatch_once_t onceToken_iap_queue; dispatch_once(&onceToken_iap_queue, ^{ as_iap_queue = dispatch_queue_create([[NSString stringWithFormat:@"com.%@.queue", app_key] cStringUsingEncoding:NSUTF8StringEncoding], DISPATCH_QUEUE_CONCURRENT); }); return as_iap_queue; } @interface TFIAPManager () @property (nonatomic ,assign) BOOL isRequestFinished; // 判断请求是否完成 @property (nonatomic ,copy) NSString *receipt; // 交易成功后拿到的一个64编码字符串 @property (nonatomic ,copy) NSString *date; // 交易时间 @property (nonatomic ,copy) NSString *userId; // 交易人 @end @implementation TFIAPManager implementation_singleton(TFIAPManager) /*** 内购支付两个阶段: 1.app直接向苹果服务器请求商品,支付阶段; 2.苹果服务器返回凭证,app向公司服务器发送验证,公司再向苹果服务器验证阶段; */ /** 阶段一正在进中,app退出。 在程序启动时,设置监听,监听是否有未完成订单,有的话恢复订单。 */ // 开启监听 - (void)startManager { dispatch_async(iap_queue(), ^{ self.isRequestFinished = YES; [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; [self checkIAPFiles]; }); } - (void)stopManager { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; }); } #pragma mark - 开始请求苹果商品 - (void)requestProductWithId:(NSString *)productId { if (!TFUserInfoManager.isLogin) { [TFLoginOptionsViewController presentLoginView:nil]; [self filedWithErrorCode:TFIAP_FILEDCOED_NOTLOGGEDIN error:nil]; self.isRequestFinished = YES; // 商品信息错误 return; } if (self.isRequestFinished) { [TFPromptManager showPromptViewWithStatus:TFPromptStatusLoading promptTitle:TFLocalizedString(@"正在连接App Store")]; if ([SKPaymentQueue canMakePayments]) { // 用户允许app内购 if (productId.length) { self.isRequestFinished = NO; NSArray *product = [[NSArray alloc] initWithObjects:productId, nil]; NSSet *set = [NSSet setWithArray:product]; SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; productRequest.delegate = self; [productRequest start]; } else { [self filedWithErrorCode:TFIAP_FILEDCOED_EMPTYGOODS error:nil]; self.isRequestFinished = YES; // 商品信息错误 } } else { // 用户未允许app内购 [self filedWithErrorCode:TFIAP_FILEDCOED_NORIGHT error:nil]; self.isRequestFinished = YES; } } } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray *product = response.products; if (product.count == 0) { [self filedWithErrorCode:TFIAP_FILEDCOED_CANNOTGETINFORMATION error:nil]; self.isRequestFinished = YES; // 失败,请求完成 } else { // 发起购买请求 SKPayment *payment = [SKPayment paymentWithProduct:product[0]]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } } - (void)completeTransaction:(SKPaymentTransaction *)transaction { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; self.isRequestFinished = YES; // 成功,请求完成 } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { [self filedWithErrorCode:TFIAP_FILEDCOED_APPLECODE error:[error localizedDescription]]; self.isRequestFinished = YES; // 失败,请求完成 } #pragma mark - 购买操作后的回调 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing: // 正在交易 [TFPromptManager showPromptViewWithStatus:TFPromptStatusLoading promptTitle:TFLocalizedString(@"商品正在请求中")]; break; case SKPaymentTransactionStatePurchased: { // 交易完成 [self getReceipt]; // 获取交易成功后的购买凭证 [self saveReceipt]; // 存储交易凭证 [self checkIAPFiles]; // 发送凭证服务器验证是否有效 [self completeTransaction:transaction]; } break; case SKPaymentTransactionStateFailed: // 交易失败 [self failedTransaction:transaction]; break; case SKPaymentTransactionStateRestored: // 已经购买过该商品 [self restoreTransaction:transaction]; break; default: break; } } } // 获取交易成功后的购买凭证 - (void)getReceipt { NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl]; self.receipt = [receiptData base64EncodedStringWithOptions:0]; } - (void)failedTransaction:(SKPaymentTransaction *)transaction { if(transaction.error.code != SKErrorPaymentCancelled) { // 交易失败 [self filedWithErrorCode:TFIAP_FILEDCOED_BUYFILED error:nil]; } else { // 取消交易 [self filedWithErrorCode:TFIAP_FILEDCOED_USERCANCEL error:nil]; } [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; self.isRequestFinished = YES; // 失败,请求完成 } - (void)restoreTransaction:(SKPaymentTransaction *)transaction { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; self.isRequestFinished = YES; // 恢复购买,请求完成 } #pragma mark 将购买凭证存储到本地,验证凭证失败时,App再次启动后会重新验证购买凭证 - (void)saveReceipt { self.date = [NSDate chindDateFormate:[NSDate date]]; NSString *fileName = [TFUtilsHelper getUDID]; self.userId = @"UserID"; NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [TFSandBoxHelper iapReceiptPath], fileName]; NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys: self.receipt, receiptKey, self.date, dateKey, self.userId, userIdKey, nil]; [dic writeToFile:savedPath atomically:YES]; } - (void)checkIAPFiles { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error = nil; // 搜索该目录下的所有文件和目录 NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[TFSandBoxHelper iapReceiptPath] error:&error]; if (error == nil) { for (NSString *name in cacheFileNameArray) { if ([name hasSuffix:@".plist"]){ NSString *filePath = [NSString stringWithFormat:@"%@/%@", [TFSandBoxHelper iapReceiptPath], name]; [self sendAppStoreRequestBuyPlist:filePath]; } } } else { // 获取本地存储的购买凭证失败 [self.delegate filedWithErrorCode:TFIAP_FILEDCOED_APPLECODE andError:error.description]; } } // 验证支付凭证 - (void)sendAppStoreRequestBuyPlist:(NSString *)plistPath { NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:plistPath]; // 这里的参数请根据自己公司后台服务器接口定制,但是必须发送的是持久化保存购买凭证 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:[dic objectForKey:receiptKey], receiptKey, [dic objectForKey:dateKey], dateKey, [dic objectForKey:userIdKey], userIdKey, nil]; // 发送购买凭证至服务器 if ([params objectForKey:@"receipt_key"]) { [self applePayBackRequest:[params objectForKey:@"receipt_key"]]; } else { [self.delegate filedWithErrorCode:TFIAP_FILEDCOED_APPLECODE andError:TFLocalizedString(@"支付凭证验证失败!")]; } } // 验证成功,移除本地记录的购买凭证 - (void)removeReceipt { NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:[TFSandBoxHelper iapReceiptPath]]) { [fileManager removeItemAtPath:[TFSandBoxHelper iapReceiptPath] error:nil]; } } #pragma mark - 苹果凭证验证 - (void)applePayBackRequest:(NSString *)receipt { WS(weakSelf) [TFPromptManager showPromptViewWithStatus:TFPromptStatusLoading promptTitle:TFLocalizedString(@"获取相关支付凭证")]; // 统计充值来源数据 NSMutableDictionary *parameter = [NSMutableDictionary dictionary]; [parameter setObject:receipt forKey:@"receipt"]; if (self.production_id > 0) { [parameter setObject:[TFUtilsHelper formatStringWithInteger:self.production_id] forKey:@"book_id"]; switch (self.productionType) { case TFProductionTypeNovel: case TFProductionTypeAi: [parameter setObject:@"1" forKey:@"content_type"]; break; case TFProductionTypeComic: [parameter setObject:@"2" forKey:@"content_type"]; break; case TFProductionTypeAudio: [parameter setObject:@"3" forKey:@"content_type"]; break; default: break; } } [TFNetworkTools POST:Apple_Pay_Back parameters:[parameter copy] model:nil success:^(BOOL isSuccess, id _Nullable t_model, TFNetworkRequestModel * _Nonnull requestModel) { if (isSuccess) { [weakSelf requestSuccess]; } else { [weakSelf filedWithErrorCode:TFIAP_FILEDCOED_BUYFILED error:nil]; } } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [weakSelf filedWithErrorCode:TFIAP_FILEDCOED_BUYFILED error:nil]; }]; } #pragma mark 错误信息反馈 - (void)requestSuccess { [self removeReceipt]; [TFPromptManager showPromptViewWithStatus:TFPromptStatusSuccess promptTitle:TFLocalizedString(@"支付成功")]; // 发送全局购买成功通知 [[NSNotificationCenter defaultCenter] postNotificationName:Notification_Recharge_Success object:nil]; if (self.delegate && [self.delegate respondsToSelector:@selector(requestSuccess)]) { [self.delegate requestSuccess]; } } - (void)filedWithErrorCode:(NSInteger)code error:(NSString *)error { switch (code) { case TFIAP_FILEDCOED_APPLECODE: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:error]; break; case TFIAP_FILEDCOED_NORIGHT: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"请您开启内购支付")]; break; case TFIAP_FILEDCOED_EMPTYGOODS: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"商品获取出错")]; break; case TFIAP_FILEDCOED_CANNOTGETINFORMATION: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"商品获取出错")]; break; case TFIAP_FILEDCOED_USERCANCEL: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"您已取消交易")]; break; case TFIAP_FILEDCOED_BUYING: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"交易正在进行")]; break; case TFIAP_FILEDCOED_NOTLOGGEDIN: break; default: [TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"支付失败,请稍后重试")]; break; } if (self.delegate && [self.delegate respondsToSelector:@selector(filedWithErrorCode:andError:)]) { [self.delegate filedWithErrorCode:code andError:error]; } } @end