|
@@ -0,0 +1,379 @@
|
|
|
|
+//
|
|
|
|
+// KDIAPManager.m
|
|
|
|
+// KDChat
|
|
|
|
+//
|
|
|
|
+// Created by JYJ on 2019/6/14.
|
|
|
|
+// Copyright © 2019 dcjf. All rights reserved.
|
|
|
|
+//
|
|
|
|
+
|
|
|
|
+#import "KDIAPManager.h"
|
|
|
|
+#import <StoreKit/StoreKit.h>
|
|
|
|
+//#import "KDMainPresenter.h"
|
|
|
|
+#import "XSNetwork.h"
|
|
|
|
+#import <Adjust/Adjust.h>
|
|
|
|
+
|
|
|
|
+@interface KDIAPManager() <SKPaymentTransactionObserver,SKProductsRequestDelegate> {
|
|
|
|
+ IAPCompletionHandle _handle;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@property (nonatomic, strong) NSString* productId;
|
|
|
|
+
|
|
|
|
+@property (nonatomic, strong) NSString* orderId;
|
|
|
|
+
|
|
|
|
+/** presenter */
|
|
|
|
+//请求网络的.
|
|
|
|
+//@property (nonatomic, strong) KDMainPresenter *presenter;
|
|
|
|
+
|
|
|
|
+@end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@implementation KDIAPManager
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ 单例模式
|
|
|
|
+
|
|
|
|
+ @return HZIAPManager
|
|
|
|
+ */
|
|
|
|
++ (instancetype)shareIAPManager {
|
|
|
|
+ static KDIAPManager *IAPManager = nil;
|
|
|
|
+ static dispatch_once_t onceToken;
|
|
|
|
+ dispatch_once(&onceToken, ^{
|
|
|
|
+ IAPManager = [[KDIAPManager alloc] init];
|
|
|
|
+ });
|
|
|
|
+ return IAPManager;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+- (instancetype)init {
|
|
|
|
+ if (self = [super init]) {
|
|
|
|
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
|
|
|
+ }
|
|
|
|
+ return self;
|
|
|
|
+}
|
|
|
|
+- (void)dealloc {
|
|
|
|
+ [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#pragma mark -- Method
|
|
|
|
+/** 检测客户端与服务器漏单情况处理*/
|
|
|
|
++ (void)checkOrderStatus {
|
|
|
|
+ NSDictionary *orderInfo = [KDIAPManager getReceiptData];
|
|
|
|
+
|
|
|
|
+ if (orderInfo != nil) {
|
|
|
|
+ NSString *orderId = orderInfo[@"orderId"];
|
|
|
|
+ id receipt = orderInfo[@"receipt"];
|
|
|
|
+ [[KDIAPManager shareIAPManager] verifyPurchaseForServiceWithOrderId:orderId receipt:receipt];
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#pragma mark -- 结束上次未完成的交易
|
|
|
|
+- (void)removeAllUncompleteTransactionsBeforeNewPurchase {
|
|
|
|
+ NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
|
|
|
|
+ if (transactions.count >= 1) {
|
|
|
|
+ for (SKPaymentTransaction* transaction in transactions) {
|
|
|
|
+ if (transaction.transactionState == SKPaymentTransactionStatePurchased ||
|
|
|
|
+ transaction.transactionState == SKPaymentTransactionStateRestored) {
|
|
|
|
+ [[SKPaymentQueue defaultQueue]finishTransaction:transaction];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ NSLog(@"没有历史未消耗订单");
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+- (void)startIAPWithOrderId:(NSString *)orderId productID:(NSString *)productID completeHandle: (IAPCompletionHandle)handle {
|
|
|
|
+ _handle = handle;
|
|
|
|
+ if(productID && productID.length > 0) {
|
|
|
|
+ if ([SKPaymentQueue canMakePayments]) {
|
|
|
|
+ [self removeAllUncompleteTransactionsBeforeNewPurchase];
|
|
|
|
+ self.orderId = orderId;
|
|
|
|
+ // 允许内购
|
|
|
|
+ self.productId = productID;
|
|
|
|
+ NSSet *set = [NSSet setWithObjects:productID, nil];
|
|
|
|
+ SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
|
|
|
|
+ request.delegate = self;
|
|
|
|
+ // 获取内购项目信息
|
|
|
|
+ [request start];
|
|
|
|
+ } else {
|
|
|
|
+ // 不允许内购
|
|
|
|
+ [self handleActionWithType:IAPResultNotArrow data:nil];
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ NSLog(@"内购项目ID错误");
|
|
|
|
+ [XSNetwork errorWithCode:-3 message:@"产品暂时不可用,请检查苹果后台"];
|
|
|
|
+ [self handleActionWithType:IAPResultIDError data:nil];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+- (void)handleActionWithType:(IAPResultType)type data:(NSData *)data{
|
|
|
|
+ switch (type) {
|
|
|
|
+ case IAPResultSuccess:
|
|
|
|
+ NSLog(@"购买成功");
|
|
|
|
+ break;
|
|
|
|
+ case IAPResultFailed:
|
|
|
|
+ NSLog(@"购买失败");
|
|
|
|
+ break;
|
|
|
|
+ case IAPResultCancle:
|
|
|
|
+ NSLog(@"用户取消购买");
|
|
|
|
+ break;
|
|
|
|
+ case IAPResultVerFailed:
|
|
|
|
+ NSLog(@"订单校验失败");
|
|
|
|
+ break;
|
|
|
|
+ case IAPResultVerSuccess:
|
|
|
|
+ NSLog(@"订单校验成功");
|
|
|
|
+ break;
|
|
|
|
+ case IAPResultNotArrow:
|
|
|
|
+ NSLog(@"不允许程序内付费");
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ if(_handle){
|
|
|
|
+ _handle(type, data);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#pragma mark -- SKProductsRequestDelegate
|
|
|
|
+/**
|
|
|
|
+ 收到产品信息的回调
|
|
|
|
+
|
|
|
|
+ @param request 请求的信息
|
|
|
|
+ @param response 返回的产品信息
|
|
|
|
+ */
|
|
|
|
+- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
|
|
|
|
+
|
|
|
|
+ // 商品所在数组
|
|
|
|
+ NSArray *productArr = response.products;
|
|
|
|
+ if (productArr.count > 0) {
|
|
|
|
+// SKProduct *product = nil;
|
|
|
|
+ for (SKProduct *product in productArr) {
|
|
|
|
+ if ([product.productIdentifier isEqualToString:self.productId]) {
|
|
|
|
+// product = p;
|
|
|
|
+ SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
|
|
|
|
+ payment.quantity = 1;
|
|
|
|
+// payment.applicationUsername
|
|
|
|
+
|
|
|
|
+// payment.applicationUsername = _orderId;
|
|
|
|
+ // 发起内购
|
|
|
|
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ [self handleActionWithType:IAPResultIDError data:nil];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// if ([response.invalidProductIdentifiers containsObject:self.productId]) {
|
|
|
|
+// self.callback(nil, [XSNetwork errorWithCode:-3 message:@"产品暂时不可用,请检查苹果后台"]);
|
|
|
|
+// return;
|
|
|
|
+// }
|
|
|
|
+//
|
|
|
|
+// for (SKProduct* product in response.products) {
|
|
|
|
+//
|
|
|
|
+// SKMutablePayment* payment = [SKMutablePayment paymentWithProduct:product];;
|
|
|
|
+// payment.applicationUsername = self.orderId;
|
|
|
|
+// [SKPaymentQueue.defaultQueue addPayment:payment];
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#pragma mark -- SKPaymentTransactionObserver
|
|
|
|
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
|
|
|
|
+ // 获取结果
|
|
|
|
+ // 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号
|
|
|
|
+ for (SKPaymentTransaction *trans in transactions) {
|
|
|
|
+ switch (trans.transactionState) {
|
|
|
|
+ case SKPaymentTransactionStatePurchasing:
|
|
|
|
+ NSLog(@"商品添加进列表");
|
|
|
|
+ break;
|
|
|
|
+ case SKPaymentTransactionStatePurchased:
|
|
|
|
+ {
|
|
|
|
+ NSLog(@"交易完成");
|
|
|
|
+ [self completeTransaction:trans];
|
|
|
|
+ [[SKPaymentQueue defaultQueue] finishTransaction:trans];
|
|
|
|
+ //adjust统计内购支付事件
|
|
|
|
+ NSString *kRechargeEventToken = [NSBundle mainBundle].infoDictionary[@"kRechargeEventToken"];
|
|
|
|
+ ADJEvent *event = [ADJEvent eventWithEventToken:kRechargeEventToken];
|
|
|
|
+ //[event setRevenue:1.0 currency:@"EUR"];
|
|
|
|
+ [event setTransactionId:trans.transactionIdentifier]; //避免重复
|
|
|
|
+ [Adjust trackEvent:event];
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case SKPaymentTransactionStateFailed:
|
|
|
|
+ NSLog(@"交易失败");
|
|
|
|
+ [self failedTransaction:trans];
|
|
|
|
+ [[SKPaymentQueue defaultQueue] finishTransaction:trans];
|
|
|
|
+ break;
|
|
|
|
+ case SKPaymentTransactionStateRestored:
|
|
|
|
+ NSLog(@"已经购买过商品");
|
|
|
|
+ [[SKPaymentQueue defaultQueue] finishTransaction:trans]; //消耗型商品不用写
|
|
|
|
+ break;
|
|
|
|
+ case SKPaymentTransactionStateDeferred:
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+/**
|
|
|
|
+ 内购完成
|
|
|
|
+
|
|
|
|
+ @param transaction 内购项目体
|
|
|
|
+ */
|
|
|
|
+- (void)completeTransaction:(SKPaymentTransaction *)transaction {
|
|
|
|
+ NSString *productIdentifier = transaction.payment.productIdentifier;
|
|
|
|
+ NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
|
|
+ NSData *data = [NSData dataWithContentsOfURL:recepitURL];
|
|
|
|
+ id receiptString = [data base64EncodedStringWithOptions:0];
|
|
|
|
+
|
|
|
|
+ if ([productIdentifier length] > 0 && data) {
|
|
|
|
+
|
|
|
|
+ if (receiptString && self.orderId.length > 0) {
|
|
|
|
+ //1.保存订单号和报文到本地.
|
|
|
|
+ [self saveReceiptData:@{@"receipt":receiptString,
|
|
|
|
+ @"orderId":self.orderId ?:@""}];
|
|
|
|
+
|
|
|
|
+ //2.向自己的服务器验证购买凭证.
|
|
|
|
+ [self verifyPurchaseForServiceWithOrderId:self.orderId
|
|
|
|
+ receipt:receiptString];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ [self handleActionWithType:IAPResultVerFailed data:nil];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+// // 自己向苹果发送验证
|
|
|
|
+// [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+///服务器验证购买凭证.
|
|
|
|
+- (void)verifyPurchaseForServiceWithOrderId:(NSString *)orderId
|
|
|
|
+ receipt:(id)receiptString {
|
|
|
|
+ if (orderId == nil && !receiptString) {
|
|
|
|
+ [self handleActionWithType:IAPResultIDError data:nil];
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ [XSNetwork payWithReceipt:receiptString orderId:orderId complete:^(id object, NSError *error) {
|
|
|
|
+
|
|
|
|
+ //TTOO:网络异常,服务器校验receiptString失败,就会出现掉单问题.需要保存订单号到本地,下次启动app,继续校验服务器.
|
|
|
|
+ NSLog(@"订单校验接口返回:error=%@",error);
|
|
|
|
+
|
|
|
|
+ if (error != nil) {
|
|
|
|
+ NSLog(@"3333. 服务器返回订单校验失败.");
|
|
|
|
+ //订单校验失败.
|
|
|
|
+ [self handleActionWithType:IAPResultVerFailed data:nil];
|
|
|
|
+ }else{
|
|
|
|
+
|
|
|
|
+ NSLog(@"3333. 服务器返回订单校验成功.");
|
|
|
|
+ //订单校验成功,删除本地保存的订单号和苹果返回的data凭证.
|
|
|
|
+ [self handleActionWithType:IAPResultSuccess data:nil];
|
|
|
|
+ [self removeLocReceiptData];
|
|
|
|
+ }
|
|
|
|
+ }];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ 交易失败
|
|
|
|
+
|
|
|
|
+ @param transaction 内购项目体
|
|
|
|
+ */
|
|
|
|
+- (void)failedTransaction:(SKPaymentTransaction *)transaction {
|
|
|
|
+ if (transaction.error.code != SKErrorPaymentCancelled) {
|
|
|
|
+ [self handleActionWithType:IAPResultFailed data:nil];
|
|
|
|
+ } else {
|
|
|
|
+ [self handleActionWithType:IAPResultCancle data:nil];
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#pragma mark -- 本地保存一次支付凭证
|
|
|
|
+static NSString *const kSaveReceiptData = @"kSaveReceiptData";
|
|
|
|
+
|
|
|
|
+- (void)saveReceiptData:(NSDictionary *)receiptData {
|
|
|
|
+ [[NSUserDefaults standardUserDefaults] setValue:receiptData forKey:kSaveReceiptData];
|
|
|
|
+ [[NSUserDefaults standardUserDefaults]synchronize];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
++ (NSDictionary *)getReceiptData {
|
|
|
|
+ return [[NSUserDefaults standardUserDefaults] valueForKey:kSaveReceiptData];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+- (void)removeLocReceiptData {
|
|
|
|
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:kSaveReceiptData];
|
|
|
|
+ [[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag {
|
|
|
|
+ //交易验证
|
|
|
|
+ NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
|
|
+ NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
|
|
|
|
+
|
|
|
|
+ NSError *error;
|
|
|
|
+ NSDictionary *requestContents = @{@"receipt-data": [receipt base64EncodedStringWithOptions:0]};
|
|
|
|
+ NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
|
|
|
|
+ if (!requestData) { // 交易凭证为空验证失败
|
|
|
|
+ [self handleActionWithType:IAPResultVerFailed data:nil];
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
|
|
|
|
+ //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
|
|
|
|
+
|
|
|
|
+ NSString *serverString = @"https://buy.itunes.apple.com/verifyReceipt";
|
|
|
|
+ if (flag) {
|
|
|
|
+ serverString = @"https://sandbox.itunes.apple.com/verifyReceipt";
|
|
|
|
+ }
|
|
|
|
+ NSURL *storeURL = [NSURL URLWithString:serverString];
|
|
|
|
+ NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
|
|
|
|
+ [storeRequest setHTTPMethod:@"POST"];
|
|
|
|
+ [storeRequest setHTTPBody:requestData];
|
|
|
|
+ storeRequest.timeoutInterval = 20;
|
|
|
|
+ [storeRequest addValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
|
|
|
|
+ NSURLSession *session = [NSURLSession sharedSession];
|
|
|
|
+ NSURLSessionDataTask *task = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
|
|
|
+ if (error) {
|
|
|
|
+ // 无法连接服务器,购买校验失败
|
|
|
|
+ [self handleActionWithType:IAPResultVerFailed data:nil];
|
|
|
|
+ } else {
|
|
|
|
+ NSError *error;
|
|
|
|
+ NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
|
|
|
+ if (!jsonResponse) {
|
|
|
|
+ // 苹果服务器校验数据返回为空校验失败
|
|
|
|
+ [self handleActionWithType:IAPResultVerFailed data:nil];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 先验证正式服务器,如果正式服务器返回21007再去苹果测试服务器验证,沙盒测试环境苹果用的是测试服务器
|
|
|
|
+ NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
|
|
|
|
+ if (status && [status isEqualToString:@"21007"]) {
|
|
|
|
+ [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
|
|
|
|
+ } else if(status && [status isEqualToString:@"0"]){
|
|
|
|
+ [self handleActionWithType:IAPResultVerSuccess data:nil];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }];
|
|
|
|
+ [task resume];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//- (KDMainPresenter *)presenter {
|
|
|
|
+// if (!_presenter) {
|
|
|
|
+// self.presenter = [[KDMainPresenter alloc] init];
|
|
|
|
+// }
|
|
|
|
+// return _presenter;
|
|
|
|
+//}
|
|
|
|
+
|
|
|
|
+@end
|