You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
13 KiB
339 lines
13 KiB
// |
|
// 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 ()<SKPaymentTransactionObserver, SKProductsRequestDelegate> |
|
|
|
@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<SKPaymentTransaction *> *)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
|
|
|