diff --git a/Classes/DialerViewController.m b/Classes/DialerViewController.m index 4141ed3dd..9d1554211 100644 --- a/Classes/DialerViewController.m +++ b/Classes/DialerViewController.m @@ -292,6 +292,7 @@ static UICompositeViewDescription *compositeDescription = nil; LOGE(@"Cannot sent logs: file is NULL"); return; } + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; NSString *filename = [appName stringByAppendingString:@".gz"]; NSString *mimeType = @"text/plain"; diff --git a/Classes/InAppProductsManager.h b/Classes/InAppProductsManager.h index 03927caf1..352e7ae79 100644 --- a/Classes/InAppProductsManager.h +++ b/Classes/InAppProductsManager.h @@ -20,15 +20,28 @@ #import #import -extern NSString *const kInAppProductsReady; +extern NSString *const kLinphoneIAPurchaseNotification; -@interface InAppProductsManager : NSObject { +@interface InAppProductsManager : NSObject -} +#define IAPAvailableSucceeded @"IAPAvailableSucceeded" +#define IAPAvailableFailed @"IAPAvailableFailed" +#define IAPPurchaseFailed @"IAPPurchaseFailed" +#define IAPPurchaseSucceeded @"IAPPurchaseSucceeded" +#define IAPRestoreFailed @"IAPRestoreFailed" +#define IAPRestoreSucceeded @"IAPRestoreSucceeded" +typedef NSString* IAPPurchaseNotificationStatus; -@property (readonly) NSArray *inAppProducts; +@property (nonatomic, retain) IAPPurchaseNotificationStatus status; +@property (nonatomic, copy) NSString *errlast; + +@property (nonatomic, strong) NSMutableArray *productsAvailable; +@property (nonatomic, strong) NSMutableArray *productsPurchased; +@property (nonatomic, strong) NSMutableArray *productsRestored; - (void)loadProducts; - (BOOL)isPurchased:(SKProduct*)product; - (void)purchaseWithID:(NSString*)productId; +- (void)restore; + @end diff --git a/Classes/InAppProductsManager.m b/Classes/InAppProductsManager.m index 3a0faa3a3..8ec9adcf4 100644 --- a/Classes/InAppProductsManager.m +++ b/Classes/InAppProductsManager.m @@ -20,7 +20,7 @@ #import "InAppProductsManager.h" #import "Utils.h" -NSString *const kInAppProductsReady = @"InAppProductsReady"; +NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotification"; @implementation InAppProductsManager { bool ready; @@ -39,7 +39,7 @@ NSString *const kInAppProductsReady = @"InAppProductsReady"; return; } //TODO: move this list elsewhere - NSArray * list = [[NSArray alloc] initWithArray:@[@"test.tunnel"]]; + NSArray * list = [[[NSArray alloc] initWithArray:@[@"test.tunnel"]] autorelease]; SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:list]]; @@ -49,39 +49,108 @@ NSString *const kInAppProductsReady = @"InAppProductsReady"; - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { - _inAppProducts = [response.products retain]; - LOGI(@"Found %lu products purchasable", (unsigned long)_inAppProducts.count); + _productsAvailable= [[NSMutableArray arrayWithArray: response.products] retain]; - for (NSString *invalidIdentifier in response.invalidProductIdentifiers) { - LOGE(@"Product Identifier with invalid ID %@", invalidIdentifier); + LOGI(@"Found %lu products available", (unsigned long)_productsAvailable.count); + + if (response.invalidProductIdentifiers.count > 0) { + for (NSString *invalidIdentifier in response.invalidProductIdentifiers) { + LOGW(@"Found product Identifier with invalid ID '%@'", invalidIdentifier); + } + [self postNotificationforStatus:IAPAvailableFailed]; + } else { + [self postNotificationforStatus:IAPAvailableSucceeded]; } - ready = true; - - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - _inAppProducts, @"products", - nil]; - - dispatch_async(dispatch_get_main_queue(), ^(void){ - [[NSNotificationCenter defaultCenter] postNotificationName:kInAppProductsReady object:self userInfo:dict]; - }); -} - -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { - } - (BOOL)isPurchased:(SKProduct*)product { - for (SKProduct *prod in _inAppProducts) { + for (SKProduct *prod in _productsPurchased) { if (prod == product) { - LOGE(@"Is %@ bought? assuming NO", product.localizedTitle); - return false; //todo + bool isBought = true; + LOGE(@"%@ is %s bought.", product.localizedTitle, isBought?"":"NOT"); + return isBought; } } return false; } - (void)purchaseWithID:(NSString *)productId { - + for (SKProduct *product in _productsAvailable) { + if ([product.productIdentifier compare:productId options:NSLiteralSearch] == NSOrderedSame) { + SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; + [[SKPaymentQueue defaultQueue] addPayment:payment]; + return; + } + } + [self postNotificationforStatus:IAPPurchaseFailed]; } -@end \ No newline at end of file +-(void)restore { + _productsRestored = [[NSMutableArray alloc] initWithCapacity:0]; + [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; +} + +- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { + for(SKPaymentTransaction * transaction in transactions) { + switch (transaction.transactionState) { + case SKPaymentTransactionStatePurchasing: + break; + case SKPaymentTransactionStatePurchased: + case SKPaymentTransactionStateRestored: + [_productsPurchased addObject:transaction]; + [self completeTransaction:transaction forStatus:IAPPurchaseSucceeded]; + break; + default: + _errlast = [NSString stringWithFormat:@"Purchase of %@ failed.",transaction.payment.productIdentifier]; + [self completeTransaction:transaction forStatus:IAPPurchaseFailed]; + break; + } + } +} + +- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { + for(SKPaymentTransaction * transaction in transactions) { + NSLog(@"%@ was removed from the payment queue.", transaction.payment.productIdentifier); + } +} + +- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { + if (error.code != SKErrorPaymentCancelled) { + _errlast = [error localizedDescription]; + [self postNotificationforStatus:IAPRestoreFailed]; + } +} + +- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue +{ + LOGI(@"All restorable transactions have been processed by the payment queue."); +// for (SKPayment *payment in queue) { +// [queue transactions] +// [_productsRestored addObject:payment.productIdentifier]; +// } + + for (SKPaymentTransaction *transaction in queue.transactions) { + NSString *productID = transaction.payment.productIdentifier; + [_productsRestored addObject:productID]; + NSLog (@"product id is %@" , productID); + } +} + +-(void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status { + _status = status; + [[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneIAPurchaseNotification object:self]; + LOGI(@"Triggering notification for status %@", status); +} + +-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(IAPPurchaseNotificationStatus)status { + if (transaction.error.code != SKErrorPaymentCancelled) { + [self postNotificationforStatus:status]; + } else { + _status = status; + } + + // Remove the transaction from the queue for purchased and restored statuses + [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; +} + +@end diff --git a/Classes/InAppProductsTableViewController.m b/Classes/InAppProductsTableViewController.m index 0101ab94f..ec5d4362d 100644 --- a/Classes/InAppProductsTableViewController.m +++ b/Classes/InAppProductsTableViewController.m @@ -20,7 +20,25 @@ - (void)viewWillAppear:(BOOL)animated { iapm = [[LinphoneManager instance] iapManager]; currentExpanded = -1; - [iapm loadProducts]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onIAPPurchaseNotification:) + name:kLinphoneIAPurchaseNotification + object:nil]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:kLinphoneIAPurchaseNotification + object:nil]; +} + +- (void)onIAPPurchaseNotification:(NSNotification*)notif { + if ([[iapm status] isEqual: IAPAvailableSucceeded]) { + [[self tableView] reloadData]; + } } #pragma mark - Table view data source @@ -30,7 +48,7 @@ } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [iapm inAppProducts].count; + return [iapm productsAvailable].count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { @@ -39,7 +57,7 @@ if (cell == nil) { cell = [[[InAppProductsCell alloc] initWithIdentifier:kCellId maximized:(currentExpanded == indexPath.row)] autorelease]; } - SKProduct *prod = [[[[LinphoneManager instance] iapManager] inAppProducts] objectAtIndex:indexPath.row]; + SKProduct *prod = [[[[LinphoneManager instance] iapManager] productsAvailable] objectAtIndex:indexPath.row]; [cell.ptitle setText: [prod localizedTitle]]; [cell.pdescription setText: [prod localizedDescription]]; [cell.pprice setText: [NSString stringWithFormat:@"%@", [prod price]]]; @@ -75,6 +93,7 @@ [alert release]; } else { //try to purchase item, and if successfull change the switch + LOGI(@"Trying to purchase %@", cell.productID); [[[LinphoneManager instance] iapManager] purchaseWithID: cell.productID]; } } diff --git a/Classes/InAppProductsViewController.h b/Classes/InAppProductsViewController.h index 632240657..85fbd4e6d 100644 --- a/Classes/InAppProductsViewController.h +++ b/Classes/InAppProductsViewController.h @@ -14,5 +14,6 @@ } @property (nonatomic, retain) IBOutlet InAppProductsTableViewController* tableController; +- (IBAction)onRestoreClicked:(UIButton *)sender; @end diff --git a/Classes/InAppProductsViewController.m b/Classes/InAppProductsViewController.m index 67dab35cd..980350927 100644 --- a/Classes/InAppProductsViewController.m +++ b/Classes/InAppProductsViewController.m @@ -67,4 +67,7 @@ static UICompositeViewDescription *compositeDescription = nil; return compositeDescription; } +- (IBAction)onRestoreClicked:(UIButton *)sender { + [[[LinphoneManager instance] iapManager] restore]; +} @end \ No newline at end of file diff --git a/Classes/InAppProductsViewController.xib b/Classes/InAppProductsViewController.xib index d6893f211..e90f0bf64 100644 --- a/Classes/InAppProductsViewController.xib +++ b/Classes/InAppProductsViewController.xib @@ -16,8 +16,8 @@ - - + + @@ -25,6 +25,16 @@ + diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index b5cd31873..2fcd65cd3 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -271,7 +271,8 @@ struct codec_name_pref_table codec_pref_table[]={ [LinphoneLogger logc:LinphoneLoggerError format:"cannot register route change handler [%ld]",lStatus]; } - + _iapManager = [[InAppProductsManager alloc] init]; + NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"]; self.messagePlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil] autorelease]; @@ -1283,8 +1284,6 @@ static LinphoneCoreVTable linphonec_vtable = { [_contactSipField release]; _contactSipField = [[self lpConfigStringForKey:@"contact_im_type_value" withDefault:@"SIP"] retain]; - _iapManager = [[InAppProductsManager alloc] init]; - fastAddressBook = [[FastAddressBook alloc] init]; linphone_core_set_root_ca(theLinphoneCore, lRootCa); diff --git a/linphone-Info.plist b/linphone-Info.plist index 1fd8768b6..964a1563d 100644 --- a/linphone-Info.plist +++ b/linphone-Info.plist @@ -53,7 +53,7 @@ CFBundleVersion - 1 + 2 LSRequiresIPhoneOS UIApplicationExitsOnSuspend