I have a quick question to a strange glitch in my In-App Purchase set up:
My ViewController has 2 buttons: A and B. When the user clicks one of these buttons, i am having my productID set to the chosen item. I then have a Buy button that the user should click to purchase the item. The issue I am having is that I can not seem to get the SKProductRequest to start the productsRequest method with the correct item.
In other words, the productRequest to Apple is always lagging 1 behind the correct product. Here is my code i have implemented:
ViewController.h
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#interface ViewController : UIViewController <SKPaymentTransactionObserver, SKProductsRequestDelegate>
/// In app purchase crap
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) SKProductsRequest *request;
#property (retain, nonatomic) NSString *productID;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
int start;
- (void)viewDidLoad {
[super viewDidLoad];
// Attempt to get request aligned with item selected.. :\
start = YES;
_productID = #"com.example.IAP.itemA";
[self Buy:self];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Buy:(id)sender {
// Check for user to be able to make payment
if ([SKPaymentQueue canMakePayments]) {
_request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:_productID]];
_request.delegate = self;
[_request start];
} else {
NSLog(#"Please enable In App Purchasing in your settings");
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
if (products.count != 0) {
NSLog(#"Products available: %#", _product.localizedTitle);
_product = products[0];
if (start != YES) {
[self beginPaymentQueue];
}
else {
start = NO;
}
} else {
NSLog(#"Products not found.");
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products) {
NSLog(#"Product not found: %#", product);
}
}
- (void)beginPaymentQueue {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (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];
break; }
default:
break;
}
}
}
- (void)UnlockPurchase {
NSLog(#"Purchase Successful!");
}
// Item Buttons
- (IBAction)buttonA:(id)sender {
_productID = #"com.example.IAP.itemA";
}
- (IBAction)buttonB:(id)sender {
_productID = #"com.example.IAP.itemB";
}
Not sure what to do... I have tried calling the CanPurchase method in the viewDidLoad method, I have even tried switch statements inside the methods for the productID, but nothing worked.
If someone could suggest a solution or try and help I would be very appreciative. I have been trying to fix this for days. Thanks!
FYI: here is a log of what is happening ( I manually added comments on here for each line to let you know what the user has done to get that log in place of the full date)
app loads 12:14:49.488 IAP[4928:370136] Products available: (null)
click B then buy 12:14:52.053 IAP[4928:370136] Products available: IAP A
cancel iap 12:15:01.086 IAP[4928:370136] Transaction Failed
click B then buy 12:15:02.622 IAP[4928:370136] Products available: IAP B
cancel iap 12:15:04.256 IAP[4928:370136] Transaction Failed
click A then buy 12:15:02.622 IAP[4928:370136] Products available: IAP B
cancel iap 12:15:04.256 IAP[4928:370136] Transaction Failed
click A then buy 12:15:02.622 IAP[4928:370136] Products available: IAP A
cancel iap 12:15:04.256 IAP[4928:370136] Transaction Failed
That loop continues, always with the productRequest lagging behind by 1 with the chosen IAP. Please help!
It looks like you're just confusing yourself here:
NSArray *products = response.products;
if (products.count != 0) {
NSLog(#"Products available: %#", _product.localizedTitle);
_product = products[0];
You are getting the localized title from the product you stored earlier. This is why you are one behind. Instead, get the product and hence its title from the products that the response returned to you:
NSArray *products = response.products;
if (products.count != 0) {
SKProduct* product = products[0];
NSLog(#"Products available: %#", product.localizedTitle);
Related
I've successfully implemented one consumable product, however i have no clue to to implement multiple consumable products. Id like to addd more ProductIdentifiers like com.lalala.20batteries, com.lalala.30batteries
can anyone please give me some guides
here is my code for the single consumable product
#interface ViewController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation ViewController
#define ProductIdentifier #"com.lalala.10batteries"
- (IBAction)taps10batteries{
NSLog(#"User requests to get 10 batteries");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:ProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for(SKPaymentTransaction *transaction in queue.transactions){
if(transaction.transactionState == SKPaymentTransactionStateRestored){
//called when the user successfully restores a purchase
NSLog(#"Transaction state -> Restored");
[self get10Batteries];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
break;
case SKPaymentTransactionStatePurchased:
[self get10Batteries];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)get10Batteries{
NSUbiquitousKeyValueStore *cloudstore1 = [NSUbiquitousKeyValueStore defaultStore];
//load cloud integer
coins = [cloudstore1 doubleForKey:#"yo" ];
coins = [[NSUserDefaults standardUserDefaults]
integerForKey:#"com.lalala.10batteries"];
coins += 10;
[[NSUserDefaults standardUserDefaults] setInteger:coins
forKey:#"com.lalala.10batteries"];
[[NSUserDefaults standardUserDefaults] synchronize];
_coinLabel.text = [NSString stringWithFormat:#"%li", (long)coins];
//save icloud
[cloudstore1 setDouble:coins forKey:#"yo"];
[cloudstore1 synchronize];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSUbiquitousKeyValueStore *cloudstore1 = [NSUbiquitousKeyValueStore defaultStore];
_coinLabel.text = [cloudstore1 stringForKey:#"yo" ];
NSUserDefaults *coinsdefaults = [NSUserDefaults standardUserDefaults];
if([coinsdefaults objectForKey:#"com.lalala.10batteries"] != nil) {
coins = [[NSUserDefaults standardUserDefaults] integerForKey:#"com.lalala.10batteries"];
coins = [coinsdefaults integerForKey:#"com.lalala.10batteries"];
_coinLabel.text = [NSString stringWithFormat:#"%li", (long)coins];
}
}
Your code can easily support multiple products, just like it supports one...
First of all, declare your other product IDs just like you did with one:
#define ProductIdentifier1 #"com.lalala.10batteries"
#define ProductIdentifier2 #"com.lalala.20batteries"
Then, create a loadAllProducts method, which is exactly like the taps10batteries method, except you declare your SKProductsRequest with ALL of the product IDs, just like you did with one:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:ProductIdentifier1, ProductIdentifier2, nil]];
Unless you prefer loading each product individually, then you'll need to create a method for each product, i.e. taps20batteries and you can ignore the next part of my answer...
The callback method can return multiple products like it can return one. Handle the response as you see fit, just like you did with one:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
int count = [response.products count];
if(count > 0){
_products = [response.products sortedArrayUsingComparator:^(id a, id b) { //sort all products by price
NSDecimalNumber *first = [(SKProduct*)a price];
NSDecimalNumber *second = [(SKProduct*)b price];
return [second compare:first];
}];
for (SKProduct *product in _products) {
NSLog(#"Product Available: %#", product.productIdentifier);
//do something with the product..
//perhaps gather the products into an array and present it somehow to the user so s/he can select what to buy?
}
}
else {
NSLog(#"No products available");
}
}
If you want a full implementation that you can use, try this one
see the line where you create SKProductsRequest object:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:ProductIdentifier]];
NSSet is a bag with objects inside unsorted and only one kind of allowed inside the bag (no dupes).
All you have to do is change it like this:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:
#"com.blahblah.unlockall",
#"com.blahblah.sounds",
#"com.blahblah.alarms",
#"com.blahblah.wallpapers",
nil]];
then u should have a custom wrapper class (CustomIAPHelper) as in the tutorial.
in view controller let's say you have a tableView then the code follows like this:
- (void)reload {
_products = nil;
[self.tableView reloadData];
[CustomIAPHelper sharedInstance].delegate = self;
[[CustomIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
// Sort the products array (by price)
_products = [products sortedArrayUsingComparator:^(id a, id b) {
NSDecimalNumber *first = [(SKProduct*)a price];
NSDecimalNumber *second = [(SKProduct*)b price];
return [second compare:first];
}];
[self.tableView reloadData];
}
[refreshControl endRefreshing];
[activity stopAnimating];
[activity setHidden:YES];
}];
}
remember to setup and implement iAPHelper's protocol methods.
Please be patient. This is my first time setting up an in-app purchase and I will try and provide as much information as I can. Instead of grooming the internet for someone else's code I ended up purchasing a class from infinite skills specifically on how to add a non-renewing subscription in-app purchase to my app. Their class was outdated, and come to find out, the "follow along" project files did not download. So I did a lot of research and this is what I came up with:
I created an in-app purchase in itunes, the in-app identifier matches the identifier in my .m coding.
I created a new provisioning profile and enabled in-app purchases and the icloud to manage the backend for the purchase. When I returned to the app i enabled icloud and made sure in-app purchases was turned on in the project target.
I created a view controller, added buttons and for the subclass I used SKStoreProductViewController. That View Controller looks like this:
I imported the storekit and the SK delegates.
It crashes when I hit the tabbarbutton from my home view to bring me to the in-app view controller.
Finally the coding:
InAppViewController.h:
#import <StoreKit/StoreKit.h>
#interface InAppViewController : SKStoreProductViewController
#end
InAppViewController.m:
//
// InAppViewController.m
// Contractor Rich
//
// Created by Joshua Hart on 2/1/15.
// Copyright (c) 2015 Code By Hart. All rights reserved.
//
#import "InAppViewController.h"
#import <StoreKit/StoreKit.h>
#interface InAppViewController ()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
#property (weak, nonatomic) IBOutlet UIButton *btnBuyAccess;
#property (weak, nonatomic) IBOutlet UIButton *btnPremiumFeature;
#property (weak, nonatomic) IBOutlet UILabel *lblStatus;
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSUbiquitousKeyValueStore *keyStore;
#property (strong, nonatomic) NSDate *expirationDate;
- (IBAction)btnBuyAccessTouched: (id)sender;
-(void)getProductInfo;
#end
#implementation InAppViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_btnBuyAccess.userInteractionEnabled = NO;
_keyStore = [[NSUbiquitousKeyValueStore alloc] init];
_expirationDate = [_keyStore objectForKey:#"expirationDate"];
NSDate *today = [NSDate date];
if (_expirationDate == nil)
_expirationDate = today;
if (_expirationDate > today) {
_btnPremiumFeature.userInteractionEnabled = YES;
}
else {
_btnBuyAccess.userInteractionEnabled = YES;
[self getProductInfo];
}
}
-(void) getProductInfo{
if ([SKPaymentQueue canMakePayments])
{
NSMutableArray *productIdentifierList = [[NSMutableArray alloc] init];
[productIdentifierList addObject:[NSString stringWithFormat:#"com.joshua.contractorrich.inapp"]];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray:productIdentifierList]];
request.delegate = self;
[request start];
}
}
-(void) productsRequest: (SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
if (products.count != 0)
{
_product = products[0];
_btnBuyAccess.userInteractionEnabled = YES;
_lblStatus.text = #"Ready for Purchase!";
}else{
_lblStatus.text = #"Product was not Found!";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products)
{
NSLog(#"Product not found: %#", product);
}
}
- (IBAction)btnBuyAccessTouched: (id)sender {
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:
_btnPremiumFeature.userInteractionEnabled = YES;
_lblStatus.text = #"Purchase Completed!";
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed: NSLog(#"Transaction Failed!");
_lblStatus.text = #"Purchase Failed!";
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
default:
break;
}
}
}
-(void) setExpirationDate {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:3];
NSDate *expirationDate = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:[NSDate date] options:0];
[_keyStore setObject:expirationDate forKey:#"expirationDate"];
[_keyStore synchronize];
}
-(void) initializeStore {
[_keyStore setObject:nil forKey:#"expirationDate"];
[_keyStore synchronize];
}
- (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.
}
*/
#end
Please understand this is my first time trying this. What may seem stupid is still the learning phase for me. Thank you!
There is no runtime error in this story. All that's happened is that you've created an Exceptions breakpoint. Now you are pausing at it. Simply resume running. If this is troublesome, disable the Exceptions breakpoint.
I have been following this tutorial on implementing an in app purchase - How do you add an in-app purchase to an iOS application?
However I get errors on lines:
productsRequest.delegate = self;
"Assigning to 'id <SKProductsRequestDelegate> from incompatible type "ViewController *const___strong'
and
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
Sending "ViewController *const___strong' to parameter of incompatible type 'id <SKPaymentTransactionObserver>
This is the .h:
#import <UIKit/UIKit.h>
#import <iAd/iAd.h>
#import <StoreKit/StoreKit.h>
#interface ViewController : UIViewController
{
IBOutlet UIButton *removeAdsButton;
}
- (IBAction)purchase;
- (IBAction)restore;
- (IBAction)tapsRemoveAdsButton;
#end
BOOL areAdsRemoved;
And this is my .m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Removal of ads
areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:#"areAddsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase
if(areAdsRemoved){
[self.view setBackgroundColor:[UIColor blueColor]];
//if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#define kRemoveAdsProductIdentifier #"com.companyname.appname.removeads"
- (IBAction)tapsRemoveAdsButton {
NSLog(#"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
if(SKPaymentTransactionStateRestored){
NSLog(#"Transaction state -> Restored");
//called when the user successfully restores a purchase
[self doRemoveAds];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finnish
if(transaction.error.code != SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)doRemoveAds{
ADBannerView *banner;
[banner setAlpha:0];
areAdsRemoved = YES;
removeAdsButton.hidden = YES;
removeAdsButton.enabled = NO;
[[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:#"areAdsRemoved"];
//use NSUserDefaults so that you can load wether or not they bought it
[[NSUserDefaults standardUserDefaults] synchronize];
}
#end
Those are not errors but warnings, you should add protocol conformance to your class declaration.
#interface ViewController : UIViewController < SKPaymentTransactionObserver, SKProductsRequestDelegate >
Be sure to implement every required method of both protocols.
I followed this tutorial and it seems to be working fine for ios6 but when I try with ios7 it never calls :
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)
(Fixed the incompatible type error, thanks to Macro206)
(In-App purchases still aren't working on ios7 (but seems to work fine on ios6(Able to buy and the ad banner's alpha set to 0 (alpha is set when the BOOL is true, from elsewhere in my app))))
Here's what I have: (I removed the animation/graphics codes to make it shorter)
//
// MainMenu.m
// HungryFish
//
//
//
//#import "AppDelegate.h"
#import "MainMenu.h"
#import "cocos2d.h"
#import "HelloWorldLayer.h"
#import "SimpleAudioEngine.h"
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#import <StoreKit/StoreKit.h>
#implementation MainMenu
CCDirectorIOS *director_;
BOOL areAdsRemoved=nil;
+(id) scene
{
CCScene *scene = [CCScene node];
MainMenu *layer = [MainMenu node];
[scene addChild: layer];
return scene;
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
int ADSIZE;
-(id) init
{
if( (self=[super init] )) {
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)]) {
// iOS 7
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
} else {
// iOS 6
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:#"areAddsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase
if(areAdsRemoved){
NSLog(#"Ads removed");
// [self.view setBackgroundColor:[UIColor blueColor]];
//if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}
}
return self;
}
// IN APP PURCHASES
#define kRemoveAdsProductIdentifier #"FishyFishinAPPid"
- (void)tapsRemoveAds{
NSLog(#"User requests to remove ads");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:(id)self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction) restore{
//this is called when the user restores purchases, you should hook this up to a button
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
if(SKPaymentTransactionStateRestored){
NSLog(#"Transaction state -> Restored");
//called when the user successfully restores a purchase
[self doRemoveAds];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch (transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finnish
if(transaction.error.code != SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)doRemoveAds{
areAdsRemoved = YES;
[[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:#"areAdsRemoved"];
//use NSUserDefaults so that you can load wether or not they bought it
[[NSUserDefaults standardUserDefaults] synchronize];
}
// IN APP PURCHASES END
- (void) dealloc
{
[super dealloc];
}
#end
//
// MainMenu.h
// HungryFish
//
//
//
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import <StoreKit/StoreKit.h>
#interface MainMenu : CCLayer <SKProductsRequestDelegate>
{
}
extern BOOL areAdsRemoved;
- (IBAction)purchase;
- (IBAction)restore;
- (IBAction)tapsRemoveAdsButton;
+(id) scene;
#end
The warnings I get are :
(At line: #implementation MainMenu)
Method definition for 'tapsRemoveAdsButton' not found
Method definition for 'purchase' not found
I looked at similar questions but never really understood how to fix it, adding "(id)self" instead of just "self" got rid of the errors but it doesn't fix the problem, the code stops at "[productsRequest start];" and "- (void)productsRequest:" never gets fired.
I'm sure i'm doing basic mistakes =(
(Oh and in case it matters, I've been testing it in the simulator, works fine on ios6 but not on ios7)
Like Macro206 mentioned you have to add <SKProductsRequestDelegate>after your #interface AND #import <StoreKit/StoreKit.h>. Furthermore In-App-Purchase should be tested on a real device with a special test account.
Your code is terribly formatted and you are keeping obsolete lines. If you want people to review your code, you should make it easier for them and make it readable. Have a look at this link
I am getting my In-App Purchase Title (I can see it in NSLog), but the UILabel that shows the title gets called after viewDidLoad. Is there any way I can get the Title before viewDidLoad, so that the UILabel will show the Title?
Below is my code. Let me know if you need me to post anything more, thanks!
PurchaseViewController.h
#interface PurchaseViewController : UIViewController
<SKPaymentTransactionObserver, SKProductsRequestDelegate>
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSString *productID;
#property (strong, nonatomic) IBOutlet UILabel *productTitle;
#property (strong, nonatomic) IBOutlet UIButton *buyButton;
#property (strong, nonatomic) IBOutlet UITextView *productDescription;
- (IBAction)buyProduct:(id)sender;
- (void)getProductInfo:(UIViewController *)viewController;
#property (nonatomic) int buttonNumber;
#end
PurchaseViewController.m
#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);
}
}
- (IBAction)buyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma mark SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self unlockFeature];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
The usual architecture is to get the product info before showing the view containing the info. Then you pass that info to your view controller as you create the view controller and show its view.
Moreover, you shouldn't handle these things in any view controller code. Fetching product infos and similar stuff like that (Location, Session, app's models' factories etc.) is a typical use case of shared data for a (optionally stateful) Manager singleton (f.e. PurchaseManager in this case) which would be called on app launch/AppDelegate methods and work in the background, independently on your UI code/threads.
Example:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[PurchaseManager defaultManager] fetchProducts];
}