From 993e062ccf7607085921df820e9cecb647cd0a42 Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Tue, 19 May 2015 12:04:55 +0200 Subject: [PATCH] InApp: handle error cases and standardize notifications name --- Classes/InAppProductsManager.h | 43 ++-- Classes/InAppProductsManager.m | 271 ++++++++++++++++---------- Classes/InAppProductsViewController.m | 6 +- Classes/LinphoneManager.m | 14 +- Classes/WizardViewController.m | 20 +- 5 files changed, 213 insertions(+), 141 deletions(-) diff --git a/Classes/InAppProductsManager.h b/Classes/InAppProductsManager.h index 1789da022..b973336cd 100644 --- a/Classes/InAppProductsManager.h +++ b/Classes/InAppProductsManager.h @@ -26,16 +26,17 @@ @end -#define IAPNotReadyYet @"IAPNotReadyYet" // startup status, manager is not ready yet -#define IAPAvailableSucceeded @"IAPAvailableSucceeded" //no data -#define IAPAvailableFailed @"IAPAvailableFailed" //data: error_msg -#define IAPPurchaseTrying @"IAPPurchaseTrying" //data: product_id -#define IAPPurchaseFailed @"IAPPurchaseFailed" //data: product_id, error_msg -#define IAPPurchaseSucceeded @"IAPPurchaseSucceeded" //data: product_id, expires_date -#define IAPRestoreFailed @"IAPRestoreFailed" //data: error_msg -#define IAPRestoreSucceeded @"IAPRestoreSucceeded" //no data -#define IAPReceiptFailed @"IAPReceiptFailed" //data: error_msg -#define IAPReceiptSucceeded @"IAPReceiptSucceeded" //no data +#define kIAPNotReady @"IAPNotReady" // startup status, manager is not ready yet +#define kIAPReady @"IAPReady" //no data +#define kIAPPurchaseTrying @"IAPPurchaseTrying" //data: product_id +#define kIAPPurchaseCancelled @"IAPPurchaseCancelled" //data: product_id +#define kIAPPurchaseFailed @"IAPPurchaseFailed" //data: product_id, error_msg +#define kIAPPurchaseSucceeded @"IAPPurchaseSucceeded" //data: product_id, expires_date +#define kIAPPurchaseExpired @"IAPPurchaseExpired" //data: product_id, expires_date +#define kIAPRestoreFailed @"IAPRestoreFailed" //data: error_msg +#define kIAPRestoreSucceeded @"IAPRestoreSucceeded" //no data +#define kIAPReceiptFailed @"IAPReceiptFailed" //data: error_msg +#define kIAPReceiptSucceeded @"IAPReceiptSucceeded" //no data typedef NSString* IAPPurchaseNotificationStatus; // InAppProductsManager take care of any in app purchase accessible within Linphone @@ -51,21 +52,31 @@ typedef NSString* IAPPurchaseNotificationStatus; NSString *latestReceiptMD5; } -// needed because request:didFailWithError method is already used by SKProductsRequestDelegate... -@property (nonatomic, retain) InAppProductsXMLRPCDelegate *xmlrpc; + @property (nonatomic, retain) IAPPurchaseNotificationStatus status; @property (nonatomic, strong) NSMutableArray *productsAvailable; @property (nonatomic, strong) NSMutableArray *productsIDPurchased; +// TRUE when in app purchase capability is available - not modified during runtime @property (readonly) BOOL enabled; +// TRUE when manager is correctly set up - must first retrieve products available and validate current receipt on our server +@property (readonly) BOOL initialized; +// TRUE if manager is available for usage - will be FALSE if an operation is already in progress or if not initialized or not enabled +@property (readonly) BOOL available; - (BOOL)isPurchasedWithID:(NSString*)productId; -- (void)purchaseAccount:(NSString*)sipURI withPassword:(NSString*)password; +// Purchase an account. You should not use this if manager is not available yet. +- (BOOL)purchaseAccount:(NSString *)phoneNumber withPassword:(NSString *)password andEmail:(NSString*)email; +// Purchase a product. You should not use this if manager is not available yet. - (BOOL)purchaseWitID:(NSString *)productID; -// restore user purchases. Must be at first launch or a user action ONLY. -- (void)restore; -- (void)retrievePurchases; +// restore user purchases. You should not use this if manager is not available yet. Must be at a user action ONLY. +- (BOOL)restore; +// retrieve purchases on our server. You should not use this if manager is not available yet. +// Warning: on first run, this will open a popup to user to provide iTunes Store credentials +- (BOOL)retrievePurchases; + + // internal API only due to methods conflict - (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response; // internal API only due to methods conflict diff --git a/Classes/InAppProductsManager.m b/Classes/InAppProductsManager.m index f308eaf19..316e78d7a 100644 --- a/Classes/InAppProductsManager.m +++ b/Classes/InAppProductsManager.m @@ -31,10 +31,13 @@ #import "PhoneMainView.h" #import "InAppProductsViewController.h" -@implementation InAppProductsManager { - NSString *accountCreationSipURI; - NSString *accountCreationPassword; -} +@interface InAppProductsManager() + @property (retain, nonatomic) NSDictionary *accountCreationData; + // needed because request:didFailWithError method is already used by SKProductsRequestDelegate... + @property (nonatomic, retain) InAppProductsXMLRPCDelegate *xmlrpc; +@end + +@implementation InAppProductsManager // LINPHONE_CAPABILITY_INAPP_PURCHASE must be defined in Linphone Build Settings #if LINPHONE_CAPABILITY_INAPP_PURCHASE && !TARGET_IPHONE_SIMULATOR @@ -42,12 +45,12 @@ - (instancetype)init { if ((self = [super init]) != nil) { _enabled = (([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) && ([SKPaymentQueue canMakePayments]) && ([[LinphoneManager instance] lpConfigBoolForKey:@"enabled" forSection:@"in_app_purchase"])); + _initialized = false; + _available = false; if (_enabled) { - LOGE(@"Todo: //waiting for parent approval"); - LOGE(@"Todo: if cancel date, no purchase"); - _xmlrpc = [[InAppProductsXMLRPCDelegate alloc] init]; - _status = IAPNotReadyYet; + self.xmlrpc = [[InAppProductsXMLRPCDelegate alloc] init]; + _status = kIAPNotReady; [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; [self loadProducts]; } @@ -70,54 +73,68 @@ return false; } -- (SKProduct*) productIDAvailable:(NSString*)productID { - if (!_enabled) return nil; - for (SKProduct *product in _productsAvailable) { - if ([product.productIdentifier compare:productID options:NSLiteralSearch] == NSOrderedSame) { - return product; - } - } - return nil; -} - (BOOL)purchaseWitID:(NSString *)productID { - if (!_enabled) return FALSE; + if (!_enabled||!_initialized||!_available) { + NSDictionary* dict = @{@"product_id":productID, @"error_msg": NSLocalizedString(@"In apps not ready yet", nil)}; + [self postNotificationforStatus:kIAPPurchaseFailed withDict:dict]; + return FALSE; + } SKProduct *prod = [self productIDAvailable:productID]; if (prod) { NSDictionary* dict = @{@"product_id": productID}; - [self postNotificationforStatus:IAPPurchaseTrying withDict:dict]; + [self postNotificationforStatus:kIAPPurchaseTrying withDict:dict]; SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:prod]; + _available = false; [[SKPaymentQueue defaultQueue] addPayment:payment]; [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; return TRUE; } else { NSDictionary* dict = @{@"product_id": productID, @"error_msg": @"Product not available"}; - [self postNotificationforStatus:IAPPurchaseFailed withDict:dict]; + [self postNotificationforStatus:kIAPPurchaseFailed withDict:dict]; return FALSE; } } -- (void)purchaseAccount:(NSString *)sipURI withPassword:(NSString *)password { - if (!_enabled) return; - NSString* productID = [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]; - accountCreationSipURI = [sipURI retain]; - accountCreationPassword = [password retain]; - if ([self purchaseWitID:productID]) { - accountCreationPassword = nil; - accountCreationSipURI = nil; +- (BOOL)purchaseAccount:(NSString *)phoneNumber withPassword:(NSString *)password andEmail:(NSString*)email { + if (phoneNumber) { + NSString* productID = [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]; + self.accountCreationData = @{ @"phoneNumber":[phoneNumber retain], @"password":[password retain], @"email":[email retain] }; + + if (![self purchaseWitID:productID]) { + self.accountCreationData = nil; + } + return true; } + return false; } --(void)restore { - if (!_enabled) return; +-(BOOL)restore { + if (!_enabled||!_initialized||!_available) { + NSDictionary* dict = @{@"error_msg": NSLocalizedString(@"In apps not ready yet", nil)}; + [self postNotificationforStatus:kIAPRestoreFailed withDict:dict]; + return FALSE; + } LOGI(@"Restoring user purchases..."); //force new query of our server latestReceiptMD5 = nil; + _available = false; [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; + return TRUE; } -- (void)retrievePurchases { - if (!_enabled) return; - [self validateReceipt:nil]; +- (BOOL)retrievePurchases { + if (!_enabled|!_initialized|!_available) { + NSDictionary* dict = @{@"error_msg": NSLocalizedString(@"In apps not ready yet", nil)}; + [self postNotificationforStatus:kIAPRestoreFailed withDict:dict]; + return FALSE; + } else if ([[self getPhoneNumber] length] == 0) { + LOGW(@"Not retrieving purchase since not account configured yet"); + return FALSE; + } else { + _available = false; + [self validateReceipt:nil]; + return TRUE; + } } #pragma mark ProductListLoading @@ -137,28 +154,34 @@ _productsAvailable = [[NSMutableArray arrayWithArray: response.products] retain]; LOGI(@"Found %lu products available", (unsigned long)_productsAvailable.count); - + _initialized = true; if (response.invalidProductIdentifiers.count > 0) { for (NSString *invalidIdentifier in response.invalidProductIdentifiers) { LOGW(@"Found product Identifier with invalid ID '%@'", invalidIdentifier); } - NSDictionary* dict = @{@"error_msg": NSLocalizedString(@"Invalid products identifier", nil)}; - [self postNotificationforStatus:IAPAvailableFailed withDict:dict]; } else { - [self postNotificationforStatus:IAPAvailableSucceeded withDict:nil]; + _available = true; + [self postNotificationforStatus:kIAPReady withDict:nil]; } } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { - LOGE(@"Impossible to retrieve list of products: %@", [error localizedFailureReason]); - NSDictionary* dict = @{@"error_msg": error ? [error localizedDescription] : NSLocalizedString(@"Product not available", commit)}; - [self postNotificationforStatus:IAPAvailableFailed withDict:dict]; + LOGE(@"Impossible to retrieve list of products: %@", [error localizedDescription]); //well, let's retry... [self loadProducts]; } #pragma mark Other +- (SKProduct*) productIDAvailable:(NSString*)productID { + if (!_enabled||!_initialized) return nil; + for (SKProduct *product in _productsAvailable) { + if ([product.productIdentifier compare:productID options:NSLiteralSearch] == NSOrderedSame) { + return product; + } + } + return nil; +} - (void)requestDidFinish:(SKRequest *)request { if([request isKindOfClass:[SKReceiptRefreshRequest class]]) { @@ -188,47 +211,48 @@ return; } - LOGI(@"Found appstore receipt"); receiptBase64 = [[NSData dataWithContentsOfURL:receiptURL] base64EncodedStringWithOptions:0]; + LOGI(@"Found appstore receipt %@", [receiptBase64 md5]); + //only check the receipt if it has changed - if (latestReceiptMD5 == nil || ! [latestReceiptMD5 isEqualToString:[receiptBase64 md5]]) { + if (latestReceiptMD5 == nil || ![latestReceiptMD5 isEqualToString:[receiptBase64 md5]]) { // We must validate the receipt on our server NSURL *URL = [NSURL URLWithString:[[LinphoneManager instance] lpConfigStringForKey:@"receipt_validation_url" forSection:@"in_app_purchase"]]; XMLRPCRequest *request = [[XMLRPCRequest alloc] initWithURL: URL]; - // Happen when restoring user purchases at application start or if user click the "restore" button - if (transaction == nil) { - [request setMethod: @"get_expiration_date" withParameters:[NSArray arrayWithObjects: - @"", - receiptBase64, - @"", - @"apple", - nil]]; - } else if ([transaction.payment.productIdentifier isEqualToString:[[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]]) { + // transaction is null when restoring user purchases at application start or if user clicks the "restore" button + if (!transaction || [transaction.payment.productIdentifier isEqualToString:[[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]]) { //buying for the first time: need to create the account - if ([transaction.transactionIdentifier isEqualToString:transaction.originalTransaction.transactionIdentifier]) { + //if ([transaction.transactionIdentifier isEqualToString:transaction.originalTransaction.transactionIdentifier]) { + if (self.accountCreationData.count == 3) { [request setMethod: @"create_account_from_in_app_purchase" withParameters:[NSArray arrayWithObjects: @"", - accountCreationSipURI, - accountCreationPassword, + [_accountCreationData objectForKey:@"phoneNumber"], receiptBase64, @"", @"apple", + [_accountCreationData objectForKey:@"email"], nil]]; - accountCreationSipURI = nil; - accountCreationPassword = nil; - //simply renewing + self.accountCreationData = nil; + // otherwise simply renewing } else { + if ([[self getPhoneNumber] length] > 0) { [request setMethod: @"get_expiration_date" withParameters:[NSArray arrayWithObjects: - @"", + [self getPhoneNumber], receiptBase64, @"", @"apple", nil]]; + } else { + LOGW(@"No SIP URI configured, doing nothing"); + _available = true; + return; + } } } else { LOGE(@"Hum, not handling product with ID %@", transaction.payment.productIdentifier); + _available = true; return; } @@ -240,9 +264,26 @@ [request release]; } else { LOGW(@"Not checking receipt since it has already been done!"); + _available = true; } } +- (NSString*)getPhoneNumber { + NSString * phoneNumber = @""; + LinphoneProxyConfig *config = linphone_core_get_default_proxy_config([LinphoneManager getLc]); + if (config) { + const char *identity = linphone_proxy_config_get_identity(config); + if (identity) { + LinphoneAddress *addr = linphone_address_new(identity); + if (addr) { + phoneNumber = [NSString stringWithUTF8String:linphone_address_get_username(addr)]; + linphone_address_destroy(addr); + } + } + } + return phoneNumber; +} + - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for(SKPaymentTransaction * transaction in transactions) { switch (transaction.transactionState) { @@ -250,19 +291,31 @@ break; case SKPaymentTransactionStatePurchased: case SKPaymentTransactionStateRestored: { - [self validateReceipt: transaction]; + if (!_initialized) { + LOGW(@"Pending transactions before end of initialization, not verifying receipt"); + } else { + [self validateReceipt: transaction]; + } [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; break; } case SKPaymentTransactionStateDeferred: - //waiting for parent approval + LOGI(@"Waiting for parent approval..."); + //could do some UI stuff break; case SKPaymentTransactionStateFailed: { - NSString* errlast = [NSString stringWithFormat:@"Purchase of %@ failed: %@.",transaction.payment.productIdentifier,transaction.error.localizedDescription]; - LOGE(@"SKPaymentTransactionStateFailed: %@", errlast); - NSDictionary* dict = @{@"product_id": transaction.payment.productIdentifier, @"error_msg": errlast}; - [self postNotificationforStatus:IAPPurchaseFailed withDict:dict]; + _available = true; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + if (transaction.error.code == SKErrorPaymentCancelled) { + LOGI(@"SKPaymentTransactionStateFailed: cancelled"); + NSDictionary* dict = @{@"product_id": transaction.payment.productIdentifier}; + [self postNotificationforStatus:kIAPPurchaseCancelled withDict:dict]; + } else { + NSString* errlast = [NSString stringWithFormat:@"Purchase of %@ failed: %@.",transaction.payment.productIdentifier,transaction.error.localizedDescription]; + LOGE(@"SKPaymentTransactionStateFailed: %@", errlast); + NSDictionary* dict = @{@"product_id": transaction.payment.productIdentifier, @"error_msg": errlast}; + [self postNotificationforStatus:kIAPPurchaseFailed withDict:dict]; + } break; } } @@ -278,7 +331,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { if (error.code != SKErrorPaymentCancelled) { NSDictionary* dict = @{@"error_msg": [error localizedDescription]}; - [self postNotificationforStatus:IAPRestoreFailed withDict:dict]; + [self postNotificationforStatus:kIAPRestoreFailed withDict:dict]; } } @@ -288,52 +341,51 @@ -(void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status withDict:(NSDictionary*)dict { _status = status; - [[NSNotificationCenter defaultCenter] postNotificationName:status object:self userInfo:dict]; LOGI(@"Triggering notification for status %@", status); + [[NSNotificationCenter defaultCenter] postNotificationName:status object:self userInfo:dict]; } - (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response { if (!_enabled) return; + + _available = true; + LOGI(@"XMLRPC response %@: %@", [request method], [response body]); NSString* productID = [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]; // validation succeeded if(! [response isFault] && [response object] != nil) { - if([[request method] isEqualToString:@"get_expiration_date"]) { - //first remove it from list + if(([[request method] isEqualToString:@"get_expiration_date"]) + || ([[request method] isEqualToString:@"create_account_from_in_app_purchase"])) { [_productsIDPurchased removeObject:productID]; - - double expirationTime = [[response object] doubleValue] / 1000; - NSDate * expirationDate = [NSDate dateWithTimeIntervalSince1970:expirationTime]; - NSDate *now = [[NSDate alloc] init]; - if (([expirationDate earlierDate:now] == expirationDate) || (expirationTime < 1)) { - LOGI(@"Account has expired"); - [[PhoneMainView instance] changeCurrentView:[InAppProductsViewController compositeViewDescription]]; - expirationDate = [NSDate dateWithTimeIntervalSince1970:0]; - } else { - [_productsIDPurchased addObject:productID]; - } - NSDictionary* dict = @{@"product_id": productID, @"expires_date": expirationDate}; - [self postNotificationforStatus:IAPReceiptSucceeded withDict:dict]; - } else if([[request method] isEqualToString:@"create_account_from_in_app_purchase"]) { - [_productsIDPurchased removeObject:productID]; - - double timeinterval = [[response object] doubleValue] / 1000; - if (timeinterval != -2) { - NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:timeinterval]; + // response object can either be expiration date (long long number or an error string) + double timeinterval = [[response object] doubleValue]; + if (timeinterval != 0.0f) { + NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:timeinterval/1000]; NSDate *now = [[NSDate alloc] init]; + NSDictionary* dict = @{@"product_id": productID, @"expires_date": expirationDate}; if ([expirationDate earlierDate:now] == expirationDate) { - LOGI(@"Account has expired"); - [[PhoneMainView instance] changeCurrentView:[InAppProductsViewController compositeViewDescription]]; - expirationDate = [NSDate dateWithTimeIntervalSince1970:0]; + LOGW(@"Account has expired"); + [self postNotificationforStatus:kIAPPurchaseExpired withDict:dict]; } else { [_productsIDPurchased addObject:productID]; + [self postNotificationforStatus:kIAPPurchaseSucceeded withDict:dict]; } - NSDictionary* dict = @{@"product_id": productID, @"expires_date": expirationDate}; - [self postNotificationforStatus:IAPPurchaseSucceeded withDict:dict]; } else { - NSDictionary* dict = @{@"product_id": productID, @"error_msg": @"Unknown error"}; - [self postNotificationforStatus:IAPPurchaseFailed withDict:dict]; + NSString *error = [response object]; + LOGE(@"Failed with error %@", error); + NSString *errorMsg; + if ([error isEqualToString:@"ERROR_ACCOUNT_ALREADY_EXISTS"]) { + errorMsg=NSLocalizedString(@"You have already registered an account.", nil); + } else if ([error isEqualToString:@"ERROR_ACCOUNT_DOESNT_EXIST"]) { + errorMsg=NSLocalizedString(@"You have already purchased an account but it does not exist anymore.", nil); + } else if ([error isEqualToString:@"ERROR_PURCHASE_CANCELLED"]) { + errorMsg=NSLocalizedString(@"You cancelled your account.", nil); + } else { + errorMsg=[NSString stringWithFormat:NSLocalizedString(@"Unknown error (%@).", nil), error]; + } + NSDictionary* dict = @{@"product_id": productID, @"error_msg": NSLocalizedString(errorMsg, nil)}; + [self postNotificationforStatus:kIAPPurchaseFailed withDict:dict]; } } } else { @@ -354,13 +406,15 @@ latestReceiptMD5 = nil; NSDictionary* dict = @{@"error_msg": errorString}; - [self postNotificationforStatus:IAPReceiptFailed withDict:dict]; + [self postNotificationforStatus:kIAPPurchaseFailed withDict:dict]; } } - (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error { if (!_enabled) return; + _available = true; + LOGE(@"Communication issue (%@)", [error localizedDescription]); NSString *errorString = [NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [error localizedDescription]]; UIAlertView* errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue", nil) @@ -372,19 +426,24 @@ [errorView release]; latestReceiptMD5 = nil; NSDictionary* dict = @{@"error_msg": errorString}; - [self postNotificationforStatus:IAPReceiptFailed withDict:dict]; + [self postNotificationforStatus:kIAPReceiptFailed withDict:dict]; } #else -- (void)purchaseAccount:(NSString *)sipURI withPassword:(NSString *)password { LOGE(@"Not supported"); } -- (void)purchaseWithID:(NSString *)productId { LOGE(@"Not supported"); } -- (void)restore { LOGE(@"Not supported"); } -- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error { LOGE(@"Not supported"); } -- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response { LOGE(@"Not supported"); } -- (BOOL)isPurchasedWithID:(NSString *)productId { LOGE(@"Not supported"); return FALSE; } -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { LOGE(@"Not supported"); } -- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { LOGE(@"Not supported"); } -- (void)retrievePurchases { LOGE(@"Not supported"); } -- (BOOL)purchaseWitID:(NSString *)productID { return FALSE; } +-(void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status { + _status = status; + [[NSNotificationCenter defaultCenter] postNotificationName:status object:self userInfo:nil]; + LOGE(@"Not supported, triggering %@", status); +} +- (BOOL)purchaseAccount:(NSString *)phoneNumber withPassword:(NSString *)password andEmail:(NSString*)email { [self postNotificationforStatus:kIAPPurchaseFailed]; return false; } +- (BOOL)restore { [self postNotificationforStatus:kIAPRestoreFailed]; return false; } +- (BOOL)retrievePurchases { [self postNotificationforStatus:kIAPRestoreFailed]; return false; } +- (BOOL)purchaseWitID:(NSString *)productID { [self postNotificationforStatus:kIAPPurchaseFailed]; return FALSE; } +- (BOOL)isPurchasedWithID:(NSString *)productId { return FALSE; } + +- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error { } +- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response { } +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { } +- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { } #endif @end diff --git a/Classes/InAppProductsViewController.m b/Classes/InAppProductsViewController.m index b6eade4db..01bffdba3 100644 --- a/Classes/InAppProductsViewController.m +++ b/Classes/InAppProductsViewController.m @@ -35,7 +35,7 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - for (NSString* notification in [NSArray arrayWithObjects:IAPAvailableSucceeded, IAPRestoreSucceeded, IAPPurchaseSucceeded, IAPReceiptSucceeded, IAPPurchaseTrying, nil]) { + for (NSString* notification in [NSArray arrayWithObjects:kIAPReady, kIAPRestoreSucceeded, kIAPPurchaseSucceeded, kIAPReceiptSucceeded, kIAPPurchaseTrying, nil]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onIAPPurchaseNotification:) name:notification @@ -45,7 +45,7 @@ - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - for (NSString* notification in [NSArray arrayWithObjects:IAPAvailableSucceeded, IAPRestoreSucceeded, IAPPurchaseSucceeded, IAPReceiptSucceeded, IAPPurchaseTrying, nil]) { + for (NSString* notification in [NSArray arrayWithObjects:kIAPReady, kIAPRestoreSucceeded, kIAPPurchaseSucceeded, kIAPReceiptSucceeded, kIAPPurchaseTrying, nil]) { [[NSNotificationCenter defaultCenter] removeObserver:self name:notification object:nil]; @@ -55,7 +55,7 @@ - (void)onIAPPurchaseNotification:(NSNotification*)notif { InAppProductsManager *iapm = [[LinphoneManager instance] iapManager]; [[_tableController tableView] reloadData]; - [_waitView setHidden:([[iapm productsAvailable] count] != 0 && ![notif.name isEqualToString:IAPPurchaseTrying])]; + [_waitView setHidden:([[iapm productsAvailable] count] != 0 && ![notif.name isEqualToString:kIAPPurchaseTrying])]; } #pragma mark - UICompositeViewDelegate Functions diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index bdb2a7076..0ca716147 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -293,11 +293,12 @@ struct codec_name_pref_table codec_pref_table[]={ //set default values for first boot if (lp_config_get_string(configDb,LINPHONERC_APPLICATION_KEY,"debugenable_preference",NULL)==NULL){ #ifdef DEBUG - [self lpConfigSetBool:TRUE forKey:@"debugenable_preference"]; + [self lpConfigSetBool:TRUE forKey:@"debugenable_preference"]; #else [self lpConfigSetBool:FALSE forKey:@"debugenable_preference"]; #endif } + _iapManager = [[InAppProductsManager alloc] init]; [self migrateFromUserPrefs]; @@ -1232,7 +1233,6 @@ void networkReachabilityCallBack(SCNetworkReachabilityRef target, SCNetworkReach } } - #pragma mark - static LinphoneCoreVTable linphonec_vtable = { @@ -1370,8 +1370,6 @@ static LinphoneCoreVTable linphonec_vtable = { } linphone_core_enable_video(theLinphoneCore, FALSE, FALSE); } - // Query our in-app server when core is ready in order to retrieve InApp purchases - [_iapManager retrievePurchases]; LOGW(@"Linphone [%s] started on [%s]", linphone_core_get_version(), [[UIDevice currentDevice].model cStringUsingEncoding:[NSString defaultCStringEncoding]]); @@ -1483,6 +1481,7 @@ static BOOL libStarted = FALSE; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterrupted:) name:AVAudioSessionInterruptionNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(globalStateChangedNotificationHandler:) name:kLinphoneGlobalStateUpdate object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(configuringStateChangedNotificationHandler:) name:kLinphoneConfiguringStateUpdate object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inappReady:) name:kIAPReady object:nil]; /*call iterate once immediately in order to initiate background connections with sip server or remote provisioning grab, if any */ linphone_core_iterate(theLinphoneCore); @@ -2261,7 +2260,10 @@ static void audioRouteChangeListenerCallback ( } } -#pragma InApp Purchase - +#pragma mark - InApp Purchase events +- (void)inappReady:(NSNotification*)notif { + // Query our in-app server to retrieve InApp purchases + [_iapManager retrievePurchases]; +} @end diff --git a/Classes/WizardViewController.m b/Classes/WizardViewController.m index c1eea04bc..1d27f276b 100644 --- a/Classes/WizardViewController.m +++ b/Classes/WizardViewController.m @@ -172,15 +172,15 @@ static UICompositeViewDescription *compositeDescription = nil; object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inAppPurchaseNotification:) - name:IAPPurchaseSucceeded + name:kIAPPurchaseSucceeded object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inAppPurchaseNotification:) - name:IAPPurchaseTrying + name:kIAPPurchaseTrying object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inAppPurchaseNotification:) - name:IAPPurchaseFailed + name:kIAPPurchaseFailed object:nil]; } @@ -200,13 +200,13 @@ static UICompositeViewDescription *compositeDescription = nil; name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self - name:IAPPurchaseFailed + name:kIAPPurchaseFailed object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self - name:IAPPurchaseTrying + name:kIAPPurchaseTrying object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self - name:IAPPurchaseSucceeded + name:kIAPPurchaseSucceeded object:nil]; } @@ -905,11 +905,11 @@ static UICompositeViewDescription *compositeDescription = nil; NSString *paidAccountID = [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]; if (wasWaitingForInApp && [paidAccountID isEqualToString:[notification.userInfo objectForKey:@"product_id"]]) { - if ([notification.name isEqual:IAPPurchaseTrying]) { + if ([notification.name isEqual:kIAPPurchaseTrying]) { [waitView setHidden:false]; - } else if ([notification.name isEqual:IAPPurchaseFailed]) { + } else if ([notification.name isEqual:kIAPPurchaseFailed]) { [waitView setHidden:true]; - } else if ([notification.name isEqual:IAPPurchaseSucceeded]) { + } else if ([notification.name isEqual:kIAPPurchaseSucceeded]) { [waitView setHidden:true]; //now that the purchase is made, let's create the account. [self onPurchaseAccountClick:self]; @@ -933,7 +933,7 @@ static UICompositeViewDescription *compositeDescription = nil; NSString *identity = [self identityFromUsername:username]; [self checkUserExist:identity]; } else { - [iapm purchaseAccount:username withPassword:password]; + [iapm purchaseAccount:username withPassword:password andEmail:email]; // inAppPurchaseNotification will take care of bringing us to the next view now } }