From 3cb6acb6db95ecc49cf488f6a72688e7101bce56 Mon Sep 17 00:00:00 2001 From: Roman Stetsenko Date: Wed, 4 Mar 2015 11:43:05 +0200 Subject: [PATCH] Added error handling, organize methods --- IAPHelper/IAPHelper.h | 31 +++-- IAPHelper/IAPHelper.m | 275 +++++++++++++++++++----------------------- 2 files changed, 140 insertions(+), 166 deletions(-) diff --git a/IAPHelper/IAPHelper.h b/IAPHelper/IAPHelper.h index a0606e5..8435abb 100755 --- a/IAPHelper/IAPHelper.h +++ b/IAPHelper/IAPHelper.h @@ -6,41 +6,40 @@ // Copyright 2011 Ray Wenderlich. All rights reserved. // + #import #import "StoreKit/StoreKit.h" -typedef void (^IAPProductsResponseBlock)(SKProductsRequest* request , SKProductsResponse* response); - -typedef void (^IAPbuyProductCompleteResponseBlock)(SKPaymentTransaction* transcation); - -typedef void (^checkReceiptCompleteResponseBlock)(NSString* response,NSError* error); +typedef void (^IAPRequestProductsCompletionBlock)(SKProductsRequest *request, SKProductsResponse *response, NSError *error); +typedef void (^IAPBuyProductCompletionBlock)(SKPaymentTransaction *transcation, NSError *error); +typedef void (^IAPCheckReceiptCompletionBlock)(NSString *response, NSError *error); +typedef void (^IAPRestoreProductsCompletionBlock)(SKPaymentQueue *payment, NSError *error); -typedef void (^resoreProductsCompleteResponseBlock) (SKPaymentQueue* payment,NSError* error); @interface IAPHelper : NSObject @property (nonatomic,strong) NSSet *productIdentifiers; -@property (nonatomic,strong) NSArray * products; +@property (nonatomic,strong) NSArray *products; @property (nonatomic,strong) NSMutableSet *purchasedProducts; @property (nonatomic,strong) SKProductsRequest *request; -@property (nonatomic) BOOL production; +@property (nonatomic,assign) BOOL production; -- (void)requestProductsWithCompletion:(IAPProductsResponseBlock)completion; - (id)initWithProductIdentifiers:(NSSet *)productIdentifiers; -- (void)buyProduct:(SKProduct *)productIdentifier onCompletion:(IAPbuyProductCompleteResponseBlock)completion; - -- (void)restoreProductsWithCompletion:(resoreProductsCompleteResponseBlock)completion; +- (void)requestProductsWithCompletion:(IAPRequestProductsCompletionBlock)completion; +- (void)buyProduct:(SKProduct *)productIdentifier onCompletion:(IAPBuyProductCompletionBlock)completion; +- (void)provideContent:(NSString *)productIdentifier; - (BOOL)isPurchasedProductsIdentifier:(NSString*)productID; -- (void)checkReceipt:(NSData*)receiptData onCompletion:(checkReceiptCompleteResponseBlock)completion; +- (void)restoreProductsWithCompletion:(IAPRestoreProductsCompletionBlock)completion; -- (void)checkReceipt:(NSData*)receiptData AndSharedSecret:(NSString*)secretKey onCompletion:(checkReceiptCompleteResponseBlock)completion; - -- (void)provideContent:(NSString *)productIdentifier; +- (void)checkReceipt:(NSData*)receiptData onCompletion:(IAPCheckReceiptCompletionBlock)completion; +- (void)checkReceipt:(NSData*)receiptData AndSharedSecret:(NSString*)secretKey onCompletion:(IAPCheckReceiptCompletionBlock)completion; - (void)clearSavedPurchasedProducts; - (void)clearSavedPurchasedProductByID:(NSString*)productIdentifier; + @end + diff --git a/IAPHelper/IAPHelper.m b/IAPHelper/IAPHelper.m index 81fe7e8..96750c4 100755 --- a/IAPHelper/IAPHelper.m +++ b/IAPHelper/IAPHelper.m @@ -6,24 +6,29 @@ // Copyright 2011 Ray Wenderlich. All rights reserved. // + #import "IAPHelper.h" #import "NSString+Base64.h" #import "SFHFKeychainUtils.h" + #if ! __has_feature(objc_arc) #error You need to either convert your project to ARC or add the -fobjc-arc compiler flag to IAPHelper.m. #endif @interface IAPHelper() -@property (nonatomic,copy) IAPProductsResponseBlock requestProductsBlock; -@property (nonatomic,copy) IAPbuyProductCompleteResponseBlock buyProductCompleteBlock; -@property (nonatomic,copy) resoreProductsCompleteResponseBlock restoreCompletedBlock; -@property (nonatomic,copy) checkReceiptCompleteResponseBlock checkReceiptCompleteBlock; -@property (nonatomic,strong) NSMutableData* receiptRequestData; +@property (nonatomic,copy) IAPRequestProductsCompletionBlock requestProductsBlock; +@property (nonatomic,copy) IAPBuyProductCompletionBlock buyProductCompleteBlock; +@property (nonatomic,copy) IAPRestoreProductsCompletionBlock restoreCompletedBlock; +@property (nonatomic,copy) IAPCheckReceiptCompletionBlock checkReceiptCompleteBlock; + +@property (nonatomic,strong) NSMutableData *receiptRequestData; + @end + @implementation IAPHelper - (id)initWithProductIdentifiers:(NSSet *)productIdentifiers { @@ -58,9 +63,28 @@ - (id)initWithProductIdentifiers:(NSSet *)productIdentifiers { return self; } --(BOOL)isPurchasedProductsIdentifier:(NSString*)productID -{ +- (void)requestProductsWithCompletion:(IAPRequestProductsCompletionBlock)completion { + self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers]; + _request.delegate = self; + self.requestProductsBlock = completion; + + [_request start]; +} + +- (void)buyProduct:(SKProduct *)productIdentifier onCompletion:(IAPBuyProductCompletionBlock)completion { + self.buyProductCompleteBlock = completion; + + self.restoreCompletedBlock = nil; + SKPayment *payment = [SKPayment paymentWithProduct:productIdentifier]; + [[SKPaymentQueue defaultQueue] addPayment:payment]; +} + +- (void)provideContent:(NSString *)productIdentifier { + [SFHFKeychainUtils storeUsername:productIdentifier andPassword:@"YES" forServiceName:@"IAPHelper" updateExisting:YES error:nil]; + [_purchasedProducts addObject:productIdentifier]; +} +- (BOOL)isPurchasedProductsIdentifier:(NSString*)productID { BOOL productPurchased = NO; NSString* password = [SFHFKeychainUtils getPasswordForUsername:productID andServiceName:@"IAPHelper" error:nil]; @@ -68,110 +92,149 @@ -(BOOL)isPurchasedProductsIdentifier:(NSString*)productID { productPurchased = YES; } - - return productPurchased; -} - -- (void)requestProductsWithCompletion:(IAPProductsResponseBlock)completion { - - self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers]; - _request.delegate = self; - self.requestProductsBlock = completion; - - [_request start]; + return productPurchased; } -- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { +- (void)restoreProductsWithCompletion:(IAPRestoreProductsCompletionBlock)completion { + //clear it + self.buyProductCompleteBlock = nil; - self.products = response.products; - self.request = nil; - - if(_requestProductsBlock) { - _requestProductsBlock (request,response); - } - + self.restoreCompletedBlock = completion; + [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } -- (void)recordTransaction:(SKPaymentTransaction *)transaction { - // TODO: Record the transaction on the server side... +- (void)checkReceipt:(NSData*)receiptData onCompletion:(IAPCheckReceiptCompletionBlock)completion { + [self checkReceipt:receiptData AndSharedSecret:nil onCompletion:completion]; } -- (void)provideContent:(NSString *)productIdentifier { +- (void)checkReceipt:(NSData*)receiptData AndSharedSecret:(NSString*)secretKey onCompletion:(IAPCheckReceiptCompletionBlock)completion { + self.checkReceiptCompleteBlock = completion; - [SFHFKeychainUtils storeUsername:productIdentifier andPassword:@"YES" forServiceName:@"IAPHelper" updateExisting:YES error:nil]; + NSError *jsonError = nil; + NSString *receiptBase64 = [NSString base64StringFromData:receiptData length:[receiptData length]]; - [_purchasedProducts addObject:productIdentifier]; + NSData *jsonData = nil; - + if(secretKey !=nil && ![secretKey isEqualToString:@""]) { + + jsonData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithObjectsAndKeys:receiptBase64,@"receipt-data", + secretKey,@"password", + nil] + options:NSJSONWritingPrettyPrinted + error:&jsonError]; + + } + else { + jsonData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithObjectsAndKeys: + receiptBase64,@"receipt-data", + nil] + options:NSJSONWritingPrettyPrinted + error:&jsonError + ]; + } + + + // NSString* jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + NSURL *requestURL = nil; + if(_production) + { + requestURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]; + } + else { + requestURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]; + } + + NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:requestURL]; + [req setHTTPMethod:@"POST"]; + [req setHTTPBody:jsonData]; + + NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self]; + if(conn) { + self.receiptRequestData = [[NSMutableData alloc] init]; + } else { + NSError* error = nil; + NSMutableDictionary* errorDetail = [[NSMutableDictionary alloc] init]; + [errorDetail setValue:@"Can't create connection" forKey:NSLocalizedDescriptionKey]; + error = [NSError errorWithDomain:@"IAPHelperError" code:100 userInfo:errorDetail]; + if(_checkReceiptCompleteBlock) { + _checkReceiptCompleteBlock(nil,error); + } + } } - (void)clearSavedPurchasedProducts { - for (NSString * productIdentifier in _productIdentifiers) { [self clearSavedPurchasedProductByID:productIdentifier]; } - } - (void)clearSavedPurchasedProductByID:(NSString*)productIdentifier { - [SFHFKeychainUtils deleteItemForUsername:productIdentifier andServiceName:@"IAPHelper" error:nil]; [_purchasedProducts removeObject:productIdentifier]; } +#pragma mark - Helpers +- (void)recordTransaction:(SKPaymentTransaction *)transaction { + // TODO: Record the transaction on the server side... +} + + - (void)completeTransaction:(SKPaymentTransaction *)transaction { - - - [self recordTransaction: transaction]; - [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; if(_buyProductCompleteBlock) { - _buyProductCompleteBlock(transaction); + _buyProductCompleteBlock(transaction, nil); } - } - (void)restoreTransaction:(SKPaymentTransaction *)transaction { - - [self recordTransaction: transaction]; - - if (transaction.originalTransaction) - [self provideContent: transaction.originalTransaction.payment.productIdentifier]; - else - [self provideContent: transaction.payment.productIdentifier]; - + [self provideContent: transaction.originalTransaction.payment.productIdentifier]; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; - + if(_buyProductCompleteBlock!=nil) { - _buyProductCompleteBlock(transaction); + _buyProductCompleteBlock(transaction, nil); } - } - (void)failedTransaction:(SKPaymentTransaction *)transaction { - if (transaction.error.code != SKErrorPaymentCancelled) { NSLog(@"Transaction error: %@ %ld", transaction.error.localizedDescription,(long)transaction.error.code); } - [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; if(_buyProductCompleteBlock) { - _buyProductCompleteBlock(transaction); + _buyProductCompleteBlock(transaction, transaction.error); } +} + +#pragma mark - SKProductsRequestDelegate +- (void)request:(SKRequest *)request didFailWithError:(NSError *)error { + self.request = nil; + if(_requestProductsBlock) { + _requestProductsBlock((SKProductsRequest *)request, nil, error); + } } -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions -{ +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { + self.products = response.products; + self.request = nil; + + if(_requestProductsBlock) { + _requestProductsBlock (request,response, nil); + } +} + +#pragma mark - SKPaymentTransactionObserver +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) @@ -190,29 +253,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)tran } } -- (void)buyProduct:(SKProduct *)productIdentifier onCompletion:(IAPbuyProductCompleteResponseBlock)completion { - - self.buyProductCompleteBlock = completion; - - self.restoreCompletedBlock = nil; - SKPayment *payment = [SKPayment paymentWithProduct:productIdentifier]; - [[SKPaymentQueue defaultQueue] addPayment:payment]; - -} - --(void)restoreProductsWithCompletion:(resoreProductsCompleteResponseBlock)completion { - - //clear it - self.buyProductCompleteBlock = nil; - - self.restoreCompletedBlock = completion; - [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; - - -} - - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { - NSLog(@"Transaction error: %@ %ld", error.localizedDescription,(long)error.code); if(_restoreCompletedBlock) { _restoreCompletedBlock(queue,error); @@ -220,7 +261,6 @@ - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedW } - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { - for (SKPaymentTransaction *transaction in queue.transactions) { switch (transaction.transactionState) @@ -228,10 +268,7 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue case SKPaymentTransactionStateRestored: { [self recordTransaction: transaction]; - if (transaction.originalTransaction) - [self provideContent: transaction.originalTransaction.payment.productIdentifier]; - else - [self provideContent: transaction.payment.productIdentifier]; + [self provideContent: transaction.originalTransaction.payment.productIdentifier]; } default: break; @@ -244,74 +281,12 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue } -- (void)checkReceipt:(NSData*)receiptData onCompletion:(checkReceiptCompleteResponseBlock)completion -{ - [self checkReceipt:receiptData AndSharedSecret:nil onCompletion:completion]; -} -- (void)checkReceipt:(NSData*)receiptData AndSharedSecret:(NSString*)secretKey onCompletion:(checkReceiptCompleteResponseBlock)completion -{ - - self.checkReceiptCompleteBlock = completion; - - NSError *jsonError = nil; - NSString *receiptBase64 = [NSString base64StringFromData:receiptData length:[receiptData length]]; - - - NSData *jsonData = nil; - - if(secretKey !=nil && ![secretKey isEqualToString:@""]) { - - jsonData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithObjectsAndKeys:receiptBase64,@"receipt-data", - secretKey,@"password", - nil] - options:NSJSONWritingPrettyPrinted - error:&jsonError]; - - } - else { - jsonData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithObjectsAndKeys: - receiptBase64,@"receipt-data", - nil] - options:NSJSONWritingPrettyPrinted - error:&jsonError - ]; - } - - -// NSString* jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - NSURL *requestURL = nil; - if(_production) - { - requestURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]; - } - else { - requestURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]; - } - - NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:requestURL]; - [req setHTTPMethod:@"POST"]; - [req setHTTPBody:jsonData]; - - NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self]; - if(conn) { - self.receiptRequestData = [[NSMutableData alloc] init]; - } else { - NSError* error = nil; - NSMutableDictionary* errorDetail = [[NSMutableDictionary alloc] init]; - [errorDetail setValue:@"Can't create connection" forKey:NSLocalizedDescriptionKey]; - error = [NSError errorWithDomain:@"IAPHelperError" code:100 userInfo:errorDetail]; - if(_checkReceiptCompleteBlock) { - _checkReceiptCompleteBlock(nil,error); - } - } -} - --(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { +#pragma mark - NSURLConnectionDelegate +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"Cannot transmit receipt data. %@",[error localizedDescription]); if(_checkReceiptCompleteBlock) { - _checkReceiptCompleteBlock(nil,error); + _checkReceiptCompleteBlock(nil, error); } } @@ -328,7 +303,7 @@ -(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSString *response = [[NSString alloc] initWithData:self.receiptRequestData encoding:NSUTF8StringEncoding]; if(_checkReceiptCompleteBlock) { - _checkReceiptCompleteBlock(response,nil); + _checkReceiptCompleteBlock(response, nil); } }