I am working in Spritekit and I am trying to enable SKPayments in my game to give the user 5 lives once they make their purchase. But everytime I run the app and try to make a purchase I keep getting an error that my SKProduct FiveLives is nil. The code and the line I keep getting the error on is below. Can somebody please help me?
self.product1ID = @"com.retrogames.5Lives";
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[self getProduct1ID:self];
SKPayment *payment = [SKPayment paymentWithProduct:_FiveLives];
ERROR LINE [[SKPaymentQueue defaultQueue] addPayment:payment];
}
The Rest of my class is here
StoreViewController.h
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
@interface StoreViewController : UIViewController <SKPaymentTransactionObserver, SKProductsRequestDelegate>{
SKProductsRequest *productsRequest;
}
@property (strong, nonatomic) SKProduct *FiveLives;
@property (strong, nonatomic) NSString *product1ID;
- (IBAction)_5Lives:(id)sender;
-(void)getProduct1ID:(UIViewController *)viewcontroller;
@end
StoreViewController.M
#import "StoreViewController.h"
@interface StoreViewController ()
@end
@implementation StoreViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)getProduct1ID:(UIViewController *)viewcontroller{
if ([SKPaymentQueue canMakePayments]) {
SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:[NSSet setWithObject:self.product1ID]];
request.delegate = self;
[request start];
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Sorry purchase was unsuccessful" message:@"Please enable in app purchases in your settings" delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alert show];
}
}
#pragma mark _
#pragma mark SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *Products = response.products;
if (Products.count != 0) {
_FiveLives = Products[0];
}
Products = response.invalidProductIdentifiers;
for (SKProduct *FiveLives in Products) {
NSLog(@"Product Not Found : %@", FiveLives);
}
}
- (IBAction)_5Lives:(id)sender {
self.product1ID = @"com.retrogames.5Lives";
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[self getProduct1ID:self];
SKPayment *payment = [SKPayment paymentWithProduct:_FiveLives];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction)RestorePurchases:(id)sender{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];
}
-(void)add5Lives{
NSLog(@"BUY5LIVES");
GameLives = GameLives + 5;
[[NSUserDefaults standardUserDefaults] setInteger:GameLives forKey:@"Key"];
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:[self add5Lives];{
NSLog(@"TRANSACTION SUCCESSFUL");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:NSLog(@"TRANSACTION FAILED");{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Payment was unsuccessful" message:@"Payment was unable to be processed." delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alert show];
}
break;
default:
break;
}
}
}
NEW CODE AFTER I USED THE TUTORIAL
PURCHASELIVES
PurchaseLives.m
+ (PurchaseLives *)sharedInstance {
static dispatch_once_t once;
static PurchaseLives * sharedInstance;
dispatch_once(&once, ^{
NSSet * productIdentifiers = [NSSet setWithObjects:
@"com.retrogames.5Lives",
nil];
sharedInstance = [[self alloc] initWithProductIdentifiers:productIdentifiers];
});
return sharedInstance;
}
@end
IAPHelper
IAPHelper.m
NSString *const IAPHelperProductPurchasedNotification = @"IAPHelperProductPurchasedNotification";
@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);
} 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...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(@"Found product: %@ %@ %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
FiveLives = skProducts[0];
NSLog(@"Products = %@",skProducts);
}
_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 %@...", FiveLives.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:FiveLives];
[[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;
}
}
}
// 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];
}
- (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];
}
@end
BUYLIVES
#import "BuyLivesViewController.h"
#import "PurchaseLives.h"
#import "IAP Helper.h"
@interface BuyLivesViewController (){
NSArray *_products;
}
@end
@implementation BuyLivesViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productPurchased:) name:IAPHelperProductPurchasedNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (IBAction)Buy5Lives:(id)sender {
[[PurchaseLives sharedInstance] buyProduct:FiveLives];
}
- (void)productPurchased:(NSNotification *)notification {
NSString * productIdentifier = notification.object;
[_products enumerateObjectsUsingBlock:^(SKProduct * product, NSUInteger idx, BOOL *stop) {
if ([product.productIdentifier isEqualToString:productIdentifier]) {
GameLives = GameLives + 5;
}
}];
}
@end
The problem is here:
getProduct1ID
issues a request to receive all products from the store. However this request is NON-blocking. meaning that by the time the request completes you are already callingpaymentWithProduct
. And in this time_FiveLives
was yet to be set with the valid product and it will benil
.What I'll recommend you to do is call
getProduct1ID
at the START of your application (I do it in the app delegate methodapplication:didFinishLaunchingWithOptions:
) and store the product as you do in a member variable you can use later.Then when the user clicks the purchase button just use the product instance to make the purchase.
I'll also recommend you to look here and see how the flow of IAP is commited. Great tutorial which I followed myself and it works perfectly.