// // FGIAPManager.m // MaltBaby // // Created by FoneG on 2020/5/7. // #import "FGIAPService.h" #import "FGIAPServiceUtility.h" #import "NSObject+FGIsNullOrEmpty.h" static NSMutableDictionary *FGIAPServiceErrorMapsFromTransaction (SKPaymentTransaction *transaction) { NSMutableDictionary *errorMaps = [NSMutableDictionary dictionary]; [errorMaps setValue:transaction.transactionIdentifier?:@"" forKey:@"transactionIdentifier"]; [errorMaps setValue:transaction.originalTransaction.transactionIdentifier?:@"" forKey:@"originalTransaction.transactionIdentifier"]; [errorMaps setValue:transaction.payment.applicationUsername?:@"" forKey:@"applicationUsername"]; [errorMaps setValue:transaction.payment.productIdentifier?:@"" forKey:@"productIdentifier"]; return errorMaps; } @interface FGIAPService () @property (nonatomic, strong) id verifyTransaction; @property (nonatomic, copy) FGIAPManagerBuyBlock buyProductCompleteBlock; @property (nonatomic, strong) NSString *productIdentifier; @property (nonatomic, strong) SKPayment *APPStorePayment; @end @implementation FGIAPService - (instancetype)initWithTransaction:(id)verifyTransaction{ if (self = [super init]) { _verifyTransaction = verifyTransaction; if ([SKPaymentQueue defaultQueue]) { [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } } return self; } - (void)buyProduct:(SKProduct *)product onCompletion:(nonnull FGIAPManagerBuyBlock)completion{ if (![SKPaymentQueue canMakePayments]) { completion(@"Failed to obtain the internal purchase permission", FGIAPManagerPurchaseRusultFail); return; } if ([product.productIdentifier isNSStringAndNotEmpty]) { _productIdentifier = product.productIdentifier; _buyProductCompleteBlock = completion; SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; if ([SKPaymentQueue defaultQueue]) { [[SKPaymentQueue defaultQueue] addPayment:payment]; } }else{ completion(@"The selected payment does not exist", FGIAPManagerPurchaseRusultFail); } } - (void)tryShouldAddStorePayments{ if (_APPStorePayment && [SKPaymentQueue defaultQueue]) { [[SKPaymentQueue defaultQueue] addPayment:self.APPStorePayment]; } } #pragma mark - SKPaymentTransactionObserver - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions{ FGLog(@"%s %ld", __func__, [SKPaymentQueue defaultQueue].transactions.count); if (![[SKPaymentQueue defaultQueue].transactions isNSArrayAndNotEmpty]) { /// all transactions finished } } - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing: FGLog(@"Add an payment to the list"); break; case SKPaymentTransactionStatePurchased: FGLog(@"Customer paid successfully"); [self completeTransaction:transaction retryWhenreceiptURLisEmpty:YES]; break; case SKPaymentTransactionStateRestored: FGLog(@"The transaction has been recovered from the user's purchase history"); [self completeTransaction:transaction retryWhenreceiptURLisEmpty:YES]; break; case SKPaymentTransactionStateFailed: FGLog(@"Failure of commodity transaction"); [self failedTransaction:transaction withError:FGIAPServiceErrorTypeNone]; break; case SKPaymentTransactionStateDeferred: FGLog(@"Merchandise is suspended"); break; default: /// break; } } } - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product API_AVAILABLE(ios(11.0)){ BOOL shouldAddStorePayment = NO; if (_verifyTransaction && [_verifyTransaction respondsToSelector:@selector(paymentQueue:shouldAddStorePayment:forProduct:)]) { shouldAddStorePayment = [_verifyTransaction paymentQueue:queue shouldAddStorePayment:payment forProduct:product]; } if (shouldAddStorePayment == NO) { _APPStorePayment = payment; } return shouldAddStorePayment; } #pragma mark - SKRequestDelegate - (void)requestDidFinish:(SKRequest *)request{ if ([request isKindOfClass:[SKReceiptRefreshRequest class]]) { SKReceiptRefreshRequest *RefreshRequest = (SKReceiptRefreshRequest *)request; SKPaymentTransaction *transaction = [RefreshRequest.receiptProperties valueForKey:@"transaction"]; if (transaction) { [self completeTransaction:transaction retryWhenreceiptURLisEmpty:NO]; }else{ [self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist]; } } } - (void)request:(SKRequest *)request didFailWithError:(nonnull NSError *)error{ if ([request isKindOfClass:[SKReceiptRefreshRequest class]]) { SKReceiptRefreshRequest *RefreshRequest = (SKReceiptRefreshRequest *)request; SKPaymentTransaction *transaction = [RefreshRequest.receiptProperties valueForKey:@"transaction"]; [self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist]; } } #pragma mark - private method - (void)completeTransaction:(SKPaymentTransaction *)transaction retryWhenreceiptURLisEmpty:(BOOL)retry{ FGLog(@"%s %@ %@", __func__, transaction.transactionIdentifier, transaction.originalTransaction.transactionIdentifier); if (![transaction.transactionIdentifier isNSStringAndNotEmpty]) { [self failedTransaction:transaction withError:FGIAPServiceErrorTypeTransactionIdentifierNotExist]; return; } NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; if ([[NSFileManager defaultManager] fileExistsAtPath:receiptURL.path]) { NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *receiptDataText = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; [self checkReceipt:receiptDataText withTransaction:transaction handler:nil]; }else if (retry){ SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{@"transaction":transaction}]; receiptRefreshRequest.delegate = self; [receiptRefreshRequest start]; }else{ [self failedTransaction:transaction withError:FGIAPServiceErrorTypeReceiptNotExist]; } } - (void)failedTransaction:(SKPaymentTransaction *)transaction withError:(FGIAPServiceErrorType)error{ FGLog(@"%s Transaction error:%@ code:%ld", __func__, transaction.error.localizedDescription, transaction.error.code); BOOL finish = error == FGIAPServiceErrorTypeNone; if (finish && [SKPaymentQueue defaultQueue]) { [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } NSMutableDictionary *logStatistics = [NSMutableDictionary dictionaryWithDictionary:FGIAPServiceErrorMapsFromTransaction(transaction)]; if (self.verifyTransaction && [self.verifyTransaction respondsToSelector:@selector(pushServiceErrorLogStatistics:error:)]) { [logStatistics setValue:@(error) forKey:@"error"]; [self.verifyTransaction pushServiceErrorLogStatistics:logStatistics error:error]; } if (_buyProductCompleteBlock) { FGIAPManagerPurchaseRusult result = FGIAPManagerPurchaseRusultHalfSuccess; if (error == FGIAPServiceErrorTypeNone) { result = transaction.error.code == SKErrorPaymentCancelled ? FGIAPManagerPurchaseRusultCancel : FGIAPManagerPurchaseRusultFail; } _buyProductCompleteBlock(transaction.error.localizedDescription, result); } } - (void)checkReceipt:(NSString *)receipt withTransaction:(SKPaymentTransaction *)transaction handler:(FGIAPVerifyTransactionBlock)handler{ WS(wSelf); if (_verifyTransaction && [_verifyTransaction respondsToSelector:@selector(pushSuccessTradeReultToServer:transaction:complete:)]) { [_verifyTransaction pushSuccessTradeReultToServer:receipt transaction:transaction complete:^(NSString * _Nonnull message, NSError * _Nullable requestErr) { //polling verify transaction if (requestErr && requestErr.code != FGIAPServerOverdueErrorCode) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [wSelf checkReceipt:receipt withTransaction:transaction handler:handler]; }); return; } [wSelf finishTransaction:transaction result: FGIAPManagerPurchaseRusultSuccess message:message]; }]; }else{ NSAssert(NO, @"You must configure the method: - pushSuccessTradeReultToServer:transaction:complete:"); } } - (void)finishTransaction:(SKPaymentTransaction *)transaction result:(FGIAPManagerPurchaseRusult)result message:(NSString *)msg{ FGLog(@"%s finishTransaction:%@", __func__, transaction.transactionIdentifier); if ([SKPaymentQueue defaultQueue]) { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } if ([transaction.payment.productIdentifier isEqualToString:self.productIdentifier]) { self.productIdentifier = nil; if (_buyProductCompleteBlock) { _buyProductCompleteBlock(msg, result); } } } @end