"In App Purchasing" still wont work after trying all these steps - ios

i have carried out these several steps for five non consumable in app purchases to be available within my app but still nothing is showing up when i go to the TableViewController that i linked the IAP code to... (i would also like to give a big thanks to the tutorial i followed which has got me this far by raywenderlich)
-Made a new app ID
-Successfully followed all the steps for linking and downloading certificates
Changed the bundle ID on xcode for my project to the one i made for
my app on IOS developer portal
-Linked all the devices and made a test user account
-Made the non consumable IAP on itunes connect and used the Identifiers within the IAP coding
-Signed my phone out of my itunes account so tht its ready to be used with a test account
-Coding for the IAP looks to be correct with store kit imported and product identifiers implemented within the .m file
-Waited 24 hours for the newly created IAP to sync with itunes connect
-I DIDNT upload the binaries!
i have hosting with apple turned on but didnt upload anything - could
this be the issue?
i deleted the app on my phone and reinstalled for testing but still
nothing
All i get when i successfull run the build is a page that looks like its loading something but then nothing a blank page that i can pull down to refresh on but still nothing no IAP that i created.
Any suggestions of what else i can do or add and if you need me to upload all the codes for all the files i can do...
coding for IAP within the ViewController.h file
#import "Accounts/Accounts.h"
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#interface ViewController19 : UITableViewController
#end
Coding for IAP within the ViewController.m file
#import "DetailViewController.h"
#import "SecretsIAPHelper.h"
#import <StoreKit/StoreKit.h>
#interface ViewController19 () {
NSArray *_products;
NSNumberFormatter * _priceFormatter;
}
#end
#implementation ViewController19
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"XXXXXXXXXXXXX";
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(reload) forControlEvents:UIControlEventValueChanged];
[self reload];
[self.refreshControl beginRefreshing];
_priceFormatter = [[NSNumberFormatter alloc] init];
[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Restore" style:UIBarButtonItemStyleBordered target:self action:#selector(restoreTapped:)];
}
- (void)restoreTapped:(id)sender {
[[SecretsIAPHelper sharedInstance] restoreCompletedTransactions];
}
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:IAPHelperProductPurchasedNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)productPurchased:(NSNotification *)notification {
NSString * productIdentifier = notification.object;
[_products enumerateObjectsUsingBlock:^(SKProduct * product, NSUInteger idx, BOOL *stop) {
if ([product.productIdentifier isEqualToString:productIdentifier]) {
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:idx inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
*stop = YES;
}
}];
}
- (void)reload {
_products = nil;
[self.tableView reloadData];
[[SecretsIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
_products = products;
[self.tableView reloadData];
}
[self.refreshControl endRefreshing];
}];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SKProduct * product = (SKProduct *) _products[indexPath.row];
cell.textLabel.text = product.localizedTitle;
[_priceFormatter setLocale:product.priceLocale];
cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
if ([[SecretsIAPHelper sharedInstance] productPurchased:product.productIdentifier]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = nil;
} else {
UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buyButton.frame = CGRectMake(0, 0, 72, 37);
[buyButton setTitle:#"Buy" forState:UIControlStateNormal];
buyButton.tag = indexPath.row;
[buyButton addTarget:self action:#selector(buyButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = buyButton;
}
return cell;
}
- (void)buyButtonTapped:(id)sender {
UIButton *buyButton = (UIButton *)sender;
SKProduct *product = _products[buyButton.tag];
NSLog(#"Buying %#...", product.productIdentifier);
[[SecretsIAPHelper sharedInstance] buyProduct:product];
}
#end
Coding for IAP within the IAPHelper.h file
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
UIKIT_EXTERN NSString *const IAPHelperProductPurchasedNotification;
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
#interface IAPHelper : NSObject
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler;
- (void)buyProduct:(SKProduct *)product;
- (BOOL)productPurchased:(NSString *)productIdentifier;
- (void)restoreCompletedTransactions;
#end
Coding for IAP within the IAPHelper.m file
#import "IAPHelper.h"
#import <StoreKit/StoreKit.h>
NSString *const IAPHelperProductPurchasedNotification = #"IAPHelperProductPurchasedNotification";
// 2
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
// 3
#implementation IAPHelper {
SKProductsRequest * _productsRequest;
RequestProductsCompletionHandler _completionHandler;
NSSet * _productIdentifiers;
NSMutableSet * _purchasedProductIdentifiers;
}
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {
if ((self = [super init])) {
// Store product identifiers
_productIdentifiers = productIdentifiers;
// Check for previously purchased products
_purchasedProductIdentifiers = [NSMutableSet set];
for (NSString * productIdentifier in _productIdentifiers) {
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
if (productPurchased) {
[_purchasedProductIdentifiers addObject:productIdentifier];
NSLog(#"Previously purchased: %#", productIdentifier);
} else {
NSLog(#"Not purchased: %#", productIdentifier);
}
}
// Add self as transaction observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
- (BOOL)productPurchased:(NSString *)productIdentifier {
return [_purchasedProductIdentifiers containsObject:productIdentifier];
}
- (void)buyProduct:(SKProduct *)product {
NSLog(#"Buying %#...", product.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"Failed to load list of products.");
_productsRequest = nil;
_completionHandler(NO, nil);
_completionHandler = nil;
}
#pragma mark SKPaymentTransactionOBserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"completeTransaction...");
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"restoreTransaction...");
[self provideContentForProductIdentifier:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"failedTransaction...");
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];
}
- (void)restoreCompletedTransactions {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
#end
Coding for IAP within the SecretsIAPHelper.h file
#import "IAPHelper.h"
#interface SecretsIAPHelper : IAPHelper
+ (SecretsIAPHelper *)sharedInstance;
#end
Coding for IAP within the SecretsIAPHelper.m file
#import "SecretsIAPHelper.h"
#implementation SecretsIAPHelper
+ (SecretsIAPHelper *)sharedInstance {
static dispatch_once_t once;
static SecretsIAPHelper * sharedInstance;
dispatch_once(&once, ^{
NSSet * productIdentifiers = [NSSet setWithObjects:
#"com.designsbydeondrae.XXXXXXX.remove_ads",
#"com.designsbydeondrae.XXXXXXX.FoundationSkills",
#"com.designsbydeondrae.XXXXXXX.IntermediateSkills",
#"com.designsbydeondrae.XXXXXXX.AllSkills",
#"com.designsbydeondrae.XXXXXXX.AdvancedSkills",
nil];
sharedInstance = [[self alloc] initWithProductIdentifiers:productIdentifiers];
});
return sharedInstance;
}
#end
Coding for IAP within the DetailViewController.h file
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController
#property (strong, nonatomic) id detailItem;
#property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
#end
Coding for IAP within the DetailViewController.h file
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
#pragma mark - Managing the detail item
- (void)setDetailItem:(id)newDetailItem
{
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
self.detailDescriptionLabel.text = [self.detailItem description];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
So just to summarise...
Used StoreKit to access the In-App Purchase APIs and retrieve the
list which brings up nothing when i run the build
Specified the product identifiers for my app
Displaying the products which seems to be an issue though it does
show a screen like its about to load something then nothing just a
page with a pull to refresh on it

For any one that finds this post in the future, the reason no items where showing up when requesting IAP was due to content not being uploaded for hosted content.

Related

In App Purchase - App crash sometimes

I'm looking through the internet for days already and can not find solution to my problem. I have created an app and want you to remove the advertising through an in app purchase. My problem is that the app sometimes crashes when I click on the button "BuyProduct". I then get the error message "Thread 1: EXC_BAD_ACCESS (code = 1, address = 0x16e6f6980)" after the line [[SKPaymentQueue defaultQueue] addPayment:payment];.
Here my .h of my PurchasedViewController:
​#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#interface PurchasedViewController2 : UIViewController <SKPaymentTransactionObserver, SKProductsRequestDelegate>{
NSTimer *myTimer;
int countdown;
}
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSString *productID;
#property (strong, nonatomic) IBOutlet UILabel *productTitle;
#property (strong, nonatomic) IBOutlet UITextView *productDescription;
#property (strong, nonatomic) IBOutlet UIButton *buyButton;
#property (weak, nonatomic) IBOutlet UIButton *goBack;
#property (weak, nonatomic) IBOutlet UILabel *labelGray;
#property (weak, nonatomic) IBOutlet UILabel *labelCountdown;
- (IBAction)GoBack:(id)sender;
- (IBAction)BuyProduct:(id)sender;
- (IBAction)Restore:(id)sender;
-(void)getProductID:(UIViewController *)viewController;
-(void)UnlockPurchase;
-(void)update;
#end
here the .m
​#import "PurchasedViewController2.h"
#import "ViewController.h"
#interface PurchasedViewController2 ()
#property (strong, nonatomic) ViewController *homeViewController;
#end
#implementation PurchasedViewController2
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
countdown = 5;
_labelCountdown.text = [NSString stringWithFormat:#"%i",countdown];
_goBack.enabled = FALSE;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update) userInfo:nil repeats:YES];
// Do any additional setup after loading the view.
}
-(void)update{
countdown--;
if (countdown >=0) {
_labelCountdown.text = [NSString stringWithFormat:#"%i",countdown];
}
if (countdown <=0) {
_labelCountdown.hidden = TRUE;
_labelGray.hidden = TRUE;
_goBack.enabled = TRUE;
[myTimer invalidate];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)GoBack:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)BuyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)viewDidUnload{
[[SKPaymentQueue defaultQueue]removeTransactionObserver:self];
[super viewDidUnload];
}
- (IBAction)Restore:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
[self UnlockPurchase];
}
-(void)getProductID:(ViewController *)viewController{
_homeViewController = viewController;
if ([SKPaymentQueue canMakePayments]) {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
}
else{
_productDescription.text = #"Please enable in app purchase in your setting";
}
}
#pragma mark _
#pragma mark SKProductsRequestDelegate
-(void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSArray *products = response.products;
if (products.count != 0) {
_product = products[0];
_buyButton.enabled = YES;
_productTitle.text = _product.localizedTitle;
_productDescription.text = _product.localizedDescription;
}
else{
_productTitle.text = #"Product Not Found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products) {
NSLog(#"Product not Found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:[self UnlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
}
-(void)UnlockPurchase{
_buyButton.enabled = NO;
[_buyButton setTitle:#"Purchased" forState:UIControlStateDisabled];
[_homeViewController Purchased];
}
#end
and here is my call of the view:
​- (IBAction)PurchaseItem:(id)sender {
_purchaseController = [[PurchasedViewController2 alloc] initWithNibName:nil bundle:nil];
_purchaseController.productID =#"PS.PileUp.RemoveAds";
[[SKPaymentQueue defaultQueue] addTransactionObserver:_purchaseController];
[self presentViewController:_purchaseController animated:YES completion:NULL];
[_purchaseController getProductID:self];
}
Thank you very munch
Thx i solve it. I change the GoBack Method to:
- (IBAction)GoBack:(id)sender {
[[SKPaymentQueue defaultQueue]removeTransactionObserver:self];
[self dismissViewControllerAnimated:YES completion:NULL];
}
It looks like you have a race condition between pressing the BuyProduct button and creation of the _product. In general, you should be finished the SKProductsRequest before allowing the user to tap BuyProduct. That is, do you know _product has the value you want when BuyProduct is pressed?

How do I add a 'Restore Purchase' method to my In App Purchase

I have coded In App Purchasing into my app and have made it work so that a user can make a purchase by pressing a button.
I now want to create a button for the user to be able to Restore Purchase. I am using a Shared Instance in 'UpgradeViewController' to retrieve methods from 'IAHelper.h' so I have shown both pieces of code incase it helps
How do I add a 'Restore Purchase' method to my In App Purchase?
IAHelper.m
#import "IAPHelper.h"
// Add to top of file
NSString *const IAPHelperProductPurchasedNotification = #"IAPHelperProductPurchasedNotification";
#implementation IAPHelper
{
// 3
SKProductsRequest * _productsRequest;
// 4
RequestProductsCompletionHandler _completionHandler;
NSSet * _productIdentifiers;
NSMutableSet * _purchasedProductIdentifiers;
NSArray *_products;
}
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {
if ((self = [super init])) {
// Store product identifiers
_productIdentifiers = productIdentifiers;
// Check for previously purchased products
_purchasedProductIdentifiers = [NSMutableSet set];
for (NSString * productIdentifier in _productIdentifiers) {
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
if (productPurchased) {
[_purchasedProductIdentifiers addObject:productIdentifier];
NSLog(#"Previously purchased: %#", productIdentifier);
} else {
NSLog(#"Not purchased: %#", productIdentifier);
}
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products... %#", response.products);
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"Failed to load list of products.");
_productsRequest = nil;
_completionHandler(NO, nil);
_completionHandler = nil;
}
- (BOOL)productPurchased:(NSString *)productIdentifier {
return [_purchasedProductIdentifiers containsObject:productIdentifier];
}
- (void)buyProduct:(SKProduct *)product {
NSLog(#"Buying %#...", product.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"completeTransaction...");
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"restoreTransaction...");
[self provideContentForProductIdentifier:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"failedTransaction...");
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// Add new method
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];
}
- (NSArray*)getProductArrray {
return _products;
}
#end
UpgradeViewController
#import "UpgradeViewController.h"
#import "ECSlidingViewController.h"
#import "MenuViewController.h"
#import "RageIAPHelper.h"
#import <StoreKit/StoreKit.h>
#interface UpgradeViewController ()
{
NSMutableArray *_objects;
NSArray *_products;
NSNumberFormatter * _priceFormatter;
}
#property (retain, nonatomic) IBOutlet JSAnimatedImagesView *animatedImagesView;
#property (assign, nonatomic) IBOutlet UIButton *installFullAppButton;
#end
#implementation UpgradeViewController
#synthesize menuBtn, animatedImagesView = _animatedImagesView, scrolly, bannerView, labelPrice;
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (NSString *)publisherIdForAdSdkBannerView:(AdSdkBannerView *)banner {
return #"e0616d4190bff65279ed5c20de1b5653";
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[RageIAPHelper sharedInstance];
_products = nil;
[[RageIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
_products = products;
}
}];
// Price New
SKProduct * product = (SKProduct *) [_products objectAtIndex:0];
([[RageIAPHelper sharedInstance] productPurchased:product.productIdentifier]);
// Unlock your features code comes here
UIButton *buyButton = [[UIButton alloc] initWithFrame:CGRectMake(-1, 370, 320, 60)];
UIImage *btnImage = [UIImage imageNamed:#"upgrade-new.png"];
[buyButton setImage:btnImage forState:UIControlStateNormal];
[buyButton.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]];
[buyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[buyButton.titleLabel setShadowColor:[UIColor colorWithWhite:0.1 alpha:1.0]];
[buyButton.titleLabel setShadowOffset:CGSizeMake(0, -1)];
[buyButton addTarget:self action:#selector(buyButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
buyButton.tag = 0;
[[self view] addSubview:buyButton];
// Purchase Action End
[super viewDidLoad];
_priceFormatter = [[NSNumberFormatter alloc] init];
[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[_priceFormatter setLocale:product.priceLocale];
labelPrice.text = [_priceFormatter stringFromNumber:product.price];
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
self.menuBtn = [UIButton buttonWithType:UIButtonTypeCustom];
menuBtn.frame = CGRectMake(8, 30, 34, 24);
[menuBtn setBackgroundImage:[UIImage imageNamed:#"menuButton.png"] forState:UIControlStateNormal];
[menuBtn addTarget:self action:#selector(revealMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.menuBtn];
myWebView.opaque = NO;
myWebView.backgroundColor = [UIColor clearColor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)revealMenu:(id)sender
{
[self.slidingViewController anchorTopViewTo:ECRight];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.screenName = #"Upgrade";
}
#pragma mark - UI
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return (toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)buyButtonTapped:(id)sender {
UIButton *buyButton = (UIButton *)sender;
SKProduct *product = [_products objectAtIndex:buyButton.tag];
NSLog(#"Buying %#...", product.productIdentifier);
[[RageIAPHelper sharedInstance] buyProduct:product];
}
#end
Apple will refuse your app if you do not have a button to do this, well they did me even though they recognised the app automatically checked in background.
Any way if you add this to your helper;
- (void)restoreCompletedTransactions
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
And call like this from VC;
- (IBAction)restoreAction:(id)sender
{
[[RageIAPHelper sharedInstance] restoreCompletedTransactions];
}
After you have doen this;
- (void)restoreCompletedTransactions
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
And call like this from VC;
- (IBAction)restoreAction:(id)sender
{
[[RageIAPHelper sharedInstance] restoreCompletedTransactions];
}
This methods needs to be changed to this;
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products... %#", response.products);
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
// Check is the Handler exists
if(_completionHandler)
{
_completionHandler(YES, skProducts);
}
_completionHandler = nil;
}

In App Purchase Code Crashing during testing

I have added InApp Purchasing to my app but when trying to complete a test transaction using a Test User account, the app keeps crashing at the following code saying that Unrecognised Selector sent to Instance.
I have read up about this and feel it may be something to do with the fact I am using Auto Renewal Subscription product.
The code that seems to be related to the crash is this line:
[[NSNotificationCenter defaultCenter] postNotificationName:#"TransCancel" object: self];
I have provided my InAppPurchase code incase anyone can please please help me with this!!
InAppPurchaseSS.h
#import <Foundation/Foundation.h>
#import "StoreKit/StoreKit.h"
#define kProductPurchasedNotification #"ProductPurchased"
#define kProductPurchaseFailedNotification #"ProductPurchaseFailed"
#define kProductPurchaseCancelledNotification #"ProductPurchaseCancelled"
#interface InAppPurchaseSS : NSObject <SKProductsRequestDelegate,SKPaymentTransactionObserver,UIAlertViewDelegate>
{
SKProductsRequest* productsRequest;
SKProduct *proUpgradeProduct;
UIAlertView* waitingAlert;
BOOL isTransactionOngoing;
}
#property (retain) SKProductsRequest* productsRequest;
#property (retain) NSArray * products;
#property (retain) SKProductsRequest *request;
#property (assign) BOOL isTransactionOngoing;
+ (InAppPurchaseSS *) sharedHelper;
-(id)init;
- (void)buyProductIdentifier:(NSString *)productIdentifier;
- (BOOL)canMakePurchases;
-(void)restoreInAppPurchase;
- (void)collectProducts;
#end
InAppPurchaseSS.m
#import "InAppPurchaseSS.h"
#import "Reachability.h"
#implementation InAppPurchaseSS
#synthesize products;
#synthesize request;
#synthesize productsRequest;
#synthesize isTransactionOngoing;
static InAppPurchaseSS * _sharedHelper;
+ (InAppPurchaseSS *) sharedHelper {
if (_sharedHelper != nil) {
return _sharedHelper;
}
_sharedHelper = [[InAppPurchaseSS alloc] init];
return _sharedHelper;
}
-(id)init {
if( (self=[super init]))
{
isTransactionOngoing=NO;
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)collectProducts
{
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:#"my.inappads"]];
self.request.delegate = self;
[self.request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"Received products results...");
self.products = response.products;
self.request = nil;
NSLog(#"Number of product:%i : %#",[response.products count],response.products);
NSArray *product = response.products;
proUpgradeProduct = [product count] == 1 ? [[product firstObject] retain] : nil;
if (proUpgradeProduct)
{
NSLog(#"Product title: %#" , proUpgradeProduct.localizedTitle);
NSLog(#"Product description: %#" , proUpgradeProduct.localizedDescription);
NSLog(#"Product price: %#" , proUpgradeProduct.price);
NSLog(#"Product id: %#" , proUpgradeProduct.productIdentifier);
}
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
}
-(void)restoreInAppPurchase
{
Reachability *reach = [Reachability reachabilityForInternetConnection];
NetworkStatus netStatus = [reach currentReachabilityStatus];
if (netStatus == NotReachable) {
NSLog(#"No internet connection!");
[[[UIAlertView alloc] initWithTitle:#"No Internet" message:#"Sorry, no internet connection found" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil] show];
return;
}
waitingAlert = [[UIAlertView alloc] initWithTitle:#"Restoring..." message:#"Please Wait...\n\n" delegate:nil cancelButtonTitle:nil otherButtonTitles: nil];
[waitingAlert show];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)buyProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqual: #"my.inappads"]) {
NSLog(#"No IAP Product ID specified");
return;
}
Reachability *reach = [Reachability reachabilityForInternetConnection];
NetworkStatus netStatus = [reach currentReachabilityStatus];
if (netStatus == NotReachable) {
NSLog(#"No internet connection!");
[[[UIAlertView alloc] initWithTitle:#"No Internet" message:#"Sorry, no internet connection found" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil] show];
return;
}
isTransactionOngoing=YES;
waitingAlert = [[UIAlertView alloc] initWithTitle:#"Purchasing..." message:#"Please Wait...\n\n" delegate:nil cancelButtonTitle:nil otherButtonTitles: nil];
[waitingAlert show];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0) {
SKMutablePayment *payment = [[SKMutablePayment alloc] init];
payment.productIdentifier = productIdentifier;
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
else {
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
-(void)enableFeature
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"PurchaseSuccess"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
[waitingAlert dismissWithClickedButtonIndex:0 animated:YES];
NSLog(#"Restore completed transaction failed");
}
- (BOOL)canMakePurchases
{
return [SKPaymentQueue canMakePayments];
}
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
isTransactionOngoing=NO;
[waitingAlert dismissWithClickedButtonIndex:0 animated:YES];
// remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (wasSuccessful)
{
[self enableFeature];
[[[UIAlertView alloc] initWithTitle:#"Congratulations!!" message:#"You have succesfully Purchases." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil] show];
[[NSNotificationCenter defaultCenter] postNotificationName:#"successbuy" object:self];
}
else
{
[[[UIAlertView alloc] initWithTitle:#"Error!" message:transaction.error.localizedDescription delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil] show];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:#"TransCancel" object: self];
}
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"succesfull transaction");
[self finishTransaction:transaction wasSuccessful:YES];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"transaction is restored");
[self finishTransaction:transaction wasSuccessful:YES];
}
// called when a transaction has failed
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
isTransactionOngoing=NO;
NSLog(#"failed transaction called");
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction failed called");
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
[self finishTransaction:transaction wasSuccessful:NO];
}
else
{
[waitingAlert dismissWithClickedButtonIndex:0 animated:YES];
NSLog(#"user cancel transaction");
// this is fine, the user just cancelled, so don’t notify
[[NSNotificationCenter defaultCenter] postNotificationName:#"TransCancel" object: self];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
#pragma mark -
#pragma mark SKPaymentTransactionObserver methods
// called when the transaction status is updated
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
NSLog(#"transaction status updated");
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
- (void) dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[super dealloc];
}
#end
I am using the #TransCancel in the view that the user makes the purchase in, the code is like this:
// Purchase
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self selector: #selector(TransactionCancel) name:#"TransCancel" object: nil];
[center addObserver: self selector: #selector(TransactionComplete) name:#"successbuy" object: nil];
NSLog(#"%d",[[NSUserDefaults standardUserDefaults] boolForKey:#"PurchaseSuccess"]);
if([[NSUserDefaults standardUserDefaults] boolForKey:#"PurchaseSuccess"])
btn.hidden=YES;
else
btn.hidden=NO;
Error Log
2014-01-06 00:25:40.694 MyApp[2764:60b] transaction status updated
2014-01-06 00:25:40.695 MyApp[2764:60b] failed transaction called
2014-01-06 00:25:40.696 MyApp[2764:60b] Transaction failed called
2014-01-06 00:25:40.696 MyApp[2764:60b] Transaction error: Cannot connect to iTunes Store
2014-01-06 00:25:40.730 MyApp[2764:60b] -[MyViewController TransactionCancel]: unrecognized selector sent to instance 0x1666a350
MyViewController
#import "UpgradeViewController.h"
#import "ECSlidingViewController.h"
#import "MenuViewController.h"
#import "InAppPurchaseSS.h"
#define ProductIdentifier #"<my.inappads>"
#interface UpgradeViewController ()
#property (retain, nonatomic) IBOutlet JSAnimatedImagesView *animatedImagesView;
#property (weak, nonatomic) IBOutlet UIButton *installFullAppButton;
#end
#implementation UpgradeViewController
#synthesize menuBtn, animatedImagesView = _animatedImagesView, scrolly, bannerView, labelPrice;
- (NSString *)publisherIdForAdSdkBannerView:(AdSdkBannerView *)banner {
return #"e0616d4190bff65279ed5c20de1b5653";
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Price
SKProduct *product = [[[InAppPurchaseSS sharedHelper] products] lastObject];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:product.price];
// [self.installFullAppButton setTitle:formattedString forState:UIControlStateNormal];
labelPrice.text = formattedString;
// Purchase
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self selector: #selector(TransactionCancel) name:#"TransactionCancel" object: nil];
[center addObserver: self selector: #selector(TransactionComplete) name:#"successbuy" object: nil];
NSLog(#"%d",[[NSUserDefaults standardUserDefaults] boolForKey:#"PurchaseSuccess"]);
if([[NSUserDefaults standardUserDefaults] boolForKey:#"PurchaseSuccess"])
btn.hidden=YES;
else
btn.hidden=NO;
// Do any additional setup after loading the view.
//UIScrollView
self.scrolly.contentSize = CGSizeMake(320, 600);
//Image Transition
// self.animatedImagesView.delegate = self;
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
self.menuBtn = [UIButton buttonWithType:UIButtonTypeCustom];
menuBtn.frame = CGRectMake(8, 30, 34, 24);
[menuBtn setBackgroundImage:[UIImage imageNamed:#"menuButton.png"] forState:UIControlStateNormal];
[menuBtn addTarget:self action:#selector(revealMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.menuBtn];
myWebView.opaque = NO;
myWebView.backgroundColor = [UIColor clearColor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)revealMenu:(id)sender
{
[self.slidingViewController anchorTopViewTo:ECRight];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.screenName = #"Upgrade";
}
#pragma mark - Memory Management
- (void)viewDidUnload
{
// [self setAnimatedImagesView:nil];
[self setScrolly:nil];
// [self setAnimatedImagesView:nil];
[super viewDidUnload];
[self setBannerView:nil];
[super viewDidUnload];
}
- (void)dealloc
{
bannerView.delegate = nil;
}
-(IBAction)but:(id)sender
{
[[InAppPurchaseSS sharedHelper] buyProductIdentifier:ProductIdentifier];
}
-(IBAction)restore:(id)sender
{
[[InAppPurchaseSS sharedHelper] restoreInAppPurchase];
}
- (IBAction)dismissView:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
}
-(void)TransactionComplete
{
btn.hidden=YES;
}
-(void)TransactionCancel
{
btn.hidden=NO;
}
You have this line:
[center addObserver: self selector: #selector(TransactionCancel) name:#"TransCancel" object: nil];
Which means that self must implement the TransactionCancel method. The error indicates that the method doesn't exist on the MyViewController class.
The solution is to add the TransactionCancel method to the MyViewController class.

iOS inapp payments - what is the right way to confirm to the user when they have made a purchase?

I am confused about how to and when to tell the user that they completed the purchase successfully. I got my application rejected during the app review process for this reason:
1. Launch app
2. Tap on learn about the benefits of subscription
3. Tap on Subscribe
4. Tap on Confirm and enter iTunes password
5. No further action occurs
And I am not sure when and how to tell the user they entered their info correctly since that is confirmed on the iTunes server.
I have an IAPHelper class which looks like this:
//
// IAPHelper.m
// BusinessPlan
//
// Created by MacOSLion on 8/12/13.
//
//
// 1
#import "IAPHelper.h"
#import <StoreKit/StoreKit.h>
// 2
//#interface IAPHelper () <SKProductsRequestDelegate>
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation IAPHelper
{
// 3
SKProductsRequest * _productsRequest;
// 4
RequestProductsCompletionHandler _completionHandler;
NSSet * _productIdentifiers;
NSMutableSet * _purchasedProductIdentifiers;
}
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers
{
if ((self = [super init]))
{
// Store product identifiers
_productIdentifiers = productIdentifiers;
// Check for previously purchased products
_purchasedProductIdentifiers = [NSMutableSet set];
for (NSString * productIdentifier in _productIdentifiers)
{
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
if (productPurchased)
{
[_purchasedProductIdentifiers addObject:productIdentifier];
NSLog(#"Previously purchased: %#", productIdentifier);
// SET memory to yes and then use that later.
// Get user data.
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
// First time on the app, so set the user cookie.
[standardUserDefaults setBool:YES forKey:#"subscriber"];
// Saving
[[NSUserDefaults standardUserDefaults] synchronize];
}
else
{
NSLog(#"Not purchased: %#", productIdentifier);
}
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
// retrieve the product information from iTunes Connect
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler
{
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts)
{
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"Failed to load list of products.");
_productsRequest = nil;
_completionHandler(NO, nil);
_completionHandler = nil;
}
- (BOOL)productPurchased:(NSString *)productIdentifier
{
return [_purchasedProductIdentifiers containsObject:productIdentifier];
}
- (void)buyProduct:(SKProduct *)product
{
NSLog(#"Buying %#...", product.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"completeTransaction...");
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// SET memory to yes and then use that later.
// Get user data.
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
// First time on the app, so set the user cookie.
[standardUserDefaults setBool:YES forKey:#"subscriber"];
// Saving
[[NSUserDefaults standardUserDefaults] synchronize];
// Tell user that things are purchased.
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
// UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Success sending purchase request."
// message:#"Just press OK and wait a few moments while iTunes processes the request." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
//
// [message show];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"restoreTransaction...");
[self provideContentForProductIdentifier:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"failedTransaction...");
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Could not complete your transaction"
message:#"Please try again. If the error persists, please email support at: alex#problemio.com" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// Add to top of file
NSString *const IAPHelperProductPurchasedNotification = #"IAPHelperProductPurchasedNotification";
// Add new method
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier
{
//NSLog(#"Provifing content for subsciber: ");
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Subscribed successfully!"
message:#"Now you can ask questions right on the app, and get our monthly business content." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];
}
#end
And my class from which I start the transaction process:
#import "SubscriptionController.h"
// 1
#import "RageIAPHelper.h"
#import <StoreKit/StoreKit.h>
// 2
#interface SubscriptionController ()
{
NSArray *_products;
// Add new instance variable to class extension
NSNumberFormatter * _priceFormatter;
}
#end
#implementation SubscriptionController
// 3
- (void)viewDidLoad
{
[super viewDidLoad];
//self.refreshControl = [[UIRefreshControl alloc] init];
//[self.refreshControl addTarget:self action:#selector(reload) forControlEvents:UIControlEventValueChanged];
[self reload];
//[self.refreshControl beginRefreshing];
// Add to end of viewDidLoad
_priceFormatter = [[NSNumberFormatter alloc] init];
[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
self.view.backgroundColor = [UIColor colorWithWhite:0.859 alpha:1.000];
}
// 4
- (void)reload
{
_products = nil;
//[self.tableView reloadData];
[[RageIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products)
{
if (success)
{
_products = products;
//[self.tableView reloadData];
}
//[self.refreshControl endRefreshing];
}];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// 5
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"a");
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SKProduct * product = (SKProduct *) _products[indexPath.row];
cell.textLabel.text = product.localizedTitle;
// Add to bottom of tableView:cellForRowAtIndexPath (before return cell)
[_priceFormatter setLocale:product.priceLocale];
cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
if ([[RageIAPHelper sharedInstance] productPurchased:product.productIdentifier])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = nil;
}
else
{
UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buyButton.frame = CGRectMake(0, 0, 72, 37);
[buyButton setTitle:#"Buy" forState:UIControlStateNormal];
buyButton.tag = indexPath.row;
[buyButton addTarget:self action:#selector(buyButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = buyButton;
}
return cell;
}
//- (IBAction)subscribe:(id)sender
//{
// UIButton *buyButton = (UIButton *)sender;
// SKProduct *product = _products[buyButton.tag];
//
// NSLog(#"Buying %#...", product.productIdentifier);
// [[RageIAPHelper sharedInstance] buyProduct:product];
//}
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:IAPHelperProductPurchasedNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)productPurchased:(NSNotification *)notification
{
NSLog(#"PURCHASEDDDDDDDDD");
// NSString * productIdentifier = notification.object;
// [_products enumerateObjectsUsingBlock:^(SKProduct * product, NSUInteger idx, BOOL *stop)
// {
// if ([product.productIdentifier isEqualToString:productIdentifier])
// {
// // TODO:
// // Update how the button appears.
//
//
//// [self.table reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:idx inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
// *stop = YES;
// }
// }];
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Purchased successfully"
message:#":)" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
// PUSH TO CONFIRMATION
}
//- (IBAction)subscribe:(id)sender
//{
//
//}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (IBAction)createSub:(id)sender
{
UIButton *buyButton = (UIButton *)sender;
SKProduct *product = _products[buyButton.tag];
if ( product == nil)
{
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Pulling product data from iTunes..."
message:#"Please try again in a few moments." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
else
{
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Success sending purchase request."
message:#"Just press OK and wait a few moments while iTunes processes the request." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
NSLog(#"Buying %#...", product.productIdentifier);
[[RageIAPHelper sharedInstance] buyProduct:product];
}
}
#end
Thank you for your help!
You should have some sort of UI update to tell the user that the payment was successful and the feature is now available/unlocked. Typically, this is done either with an update in your views to correspond to the new content, or a UIAlertView if there are no visual changes made.

In App purchase not work on device

I try to make in app purchase on my app. But I have not result when I try to run it in device.
My code is following:
This is object needed for get info about products and purchase it.
.h
#import <Foundation/Foundation.h>
#import "StoreKit/StoreKit.h"
#define kProductsLoadedNotification #"ProductsLoaded"
#define kProductPurchasedNotification #"ProductPurchased"
#define kProductPurchaseFailedNotification #"ProductPurchaseFailed"
#interface StoreManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#property (strong, nonatomic) NSMutableSet * _purchasedProducts;
#property (strong, nonatomic) NSArray *products;
#property (strong, nonatomic) NSSet *_productIdentifiers;
#property (strong, nonatomic) SKProductsRequest *request;
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (void)requestProducts;
- (void)buyProduct:(SKProduct *)product;
- (void)completeTransaction:(SKPaymentTransaction *)transaction;
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
- (void)provideContent:(NSString *)productIdentifier;
- (void)recordTransaction:(SKPaymentTransaction *)transaction;
#end
.m
#import "StoreManager.h"
#implementation StoreManager
#synthesize _purchasedProducts;
#synthesize products;
#synthesize _productIdentifiers;
#synthesize request;
// Initializes the request object with the set of product identifiers.
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers{
self = [super init];
if(self){
self._productIdentifiers = productIdentifiers;
NSMutableSet *purchased = [NSMutableSet set];
for(NSString * productId in self._productIdentifiers){
BOOL flag = [[NSUserDefaults standardUserDefaults] boolForKey:productId];
if(flag){
[purchased addObject:productId];
NSLog(#"Previously purchased: %#", productId);
}
NSLog(#"Not purchased: %#", productId);
}
self._purchasedProducts = purchased;
}
return self;
}
// Request info for the product.
- (void)requestProducts{
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:self._productIdentifiers];
self.request.delegate = self;
[self.request start];
}
// Request fail.
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
NSLog(#"Fail request! Error: %#", error);
}
// Delegate method - did receive responce.
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
self.products = response.products;
self.request = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:self.products];
}
// Buy product.
- (void)buyProduct:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
// Payment transactions
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
}
}
- (void)recordTransaction:(SKPaymentTransaction *)transaction {
// TODO: Record the transaction on the server side...
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
[self recordTransaction: transaction];
[self provideContent: transaction.payment.productIdentifier];
NSLog(#"completeTransaction...");
}
- (void)provideContent:(NSString *)productIdentifier {
NSLog(#"Toggling flag for: %#", productIdentifier);
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[self._purchasedProducts addObject:productIdentifier];
[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:productIdentifier];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"restoreTransaction...");
[self recordTransaction: transaction];
[self provideContent: transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchaseFailedNotification object:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
#end
Singleton object
.h
#import <Foundation/Foundation.h>
#import "StoreManager.h"
#interface StoreWrapper : StoreManager
+ (StoreWrapper *)sharedInstance;
#end
.m
#import "StoreWrapper.h"
#implementation StoreWrapper
static StoreWrapper *gInstance = NULL;
- (id)init{
NSSet *productId = [NSSet setWithObjects:#"com.company.sb.pack1", #"com.company.sb.pack5", nil];
self = [super initWithProductIdentifiers:productId];
if(self){
}
return self;
}
+ (StoreWrapper *)sharedInstance{
if(gInstance == NULL){
gInstance = [[self alloc] init];
}
return gInstance;
}
#end
In the my view controller i call next:
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productsLoaded:) name:kProductsLoadedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
Reachability *reach = [Reachability reachabilityForInternetConnection];
NetworkStatus netStatus = [reach currentReachabilityStatus];
if (netStatus == NotReachable) {
NSLog(#"No internet connection!");
} else {
NSLog(#"Internet connection available!");
// Request product
[[StoreWrapper sharedInstance] requestProducts];
[activIndicator startAnimating];
}
[super viewWillAppear:animated];
}
for this point I send an request for get product info from server with my product id
next if product loaded my class get notification about this (and call following method).
- (void)productsLoaded:(NSNotification *)notification{
[activIndicator stopAnimating];
[activIndicator setHidden:YES];
NSArray *arr = [notification object];
for(int i = 0; i < [arr count]; i++){
SKProduct *product = [[StoreWrapper sharedInstance].products objectAtIndex:i];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(0, i*55, 100, 50)];
[btn setTitle:product.localizedTitle forState:UIControlStateNormal];
[btn setTag:i];
[btn addTarget:self action:#selector(buyProduct:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
}
For the button i add selector buyProduct (as shown above)
- (void)buyProduct:(id)sender{
NSLog(#"sender tag %d", [sender tag]);
int tag = [sender tag];
SKProduct *product = [[StoreWrapper sharedInstance].products objectAtIndex:tag];
[[StoreWrapper sharedInstance] buyProduct:product];
}
For iPhone Simulator this work wonderful, but on device I don have any button, because response.products not download.
All method are called, but delegate method *- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse )response not return response.products.
Thanks for your time and thanks for answer!
For resolving, this problem I reset my device and rerun the application.
This helps for me.

Resources