Implementing in-app purchases on iOS games based on Cocos2d-X – Part 2 - Plunge Interactive

Implementing in-app purchases on iOS games based on Cocos2d-X – Part 2

In the previous post we learned the basics of how to implement the request of available products to purchase. In this final part we are going to learn how to finally purchase the products using IAP.
 

If you remember, we implemented a productsDownloaded callback method to send the available IAP to our Cocos2d-x c++ code, after few modifications, it will be so easy to make a purchase, something like this:

void Cover::productsDownloaded(const std::vector& products)
{
    IAPWrapper* IAP = new IAPWrapper::IAPWrapper();

    IAP->buyProductIdentifier(products[0].identification);
}

 
After calling those few lines of code, the IAP SDK will automatically show a small menu screen asking the user if he wants to proceed with the purchase or if he prefers to cancel it. In order to do so, we first need to update our callback class. Then, remember that wherever you want to use this callback, you need to import this header, and override the virtual productsDownloaded method. You might also want to add some more methods here, like productPurchased, or purchasedFailed, purchasedCanceled, etc. This is the code for IAPCallback.h:

#ifndef xxx_IAPCallback_h
#define xxx_IAPCallback_h

#include "cocos2d.h"

using namespace cocos2d;

namespace iOSBridge 
{
    namespace Callbacks
    {
        struct IAPItem
        {
            std::string identification;
            std::string name;
            std::string localizedTitle;
            std::string localizedDescription;
            float price;
        };

        class IAPCallback
        {
        public:
            virtual void productsDownloaded(const std::vector& products) = 0;
        };
    }
}

#endif

Then we need to define an Objective-C++ class that can be easily included on any of our Cocos2d-x scenes. This will be the IAPWrapper class. This would be the code for IAPWrapper.h:

#import "IAPWrapper.h"

bool IAPWrapper::canMakePayments()
{
    return [[IAPHelperFight_objc sharedHelper] canMakePayments];
}

void IAPWrapper::requestProducts(iOSBridge::Callbacks::IAPCallback* callback)
{
    [[IAPHelperFight_objc sharedHelper] requestProductsWithCallback: callback];
}

void IAPWrapper::buyProductIdentifier(const std::string& productID)
{
    NSString *nsID = [NSString stringWithCString:productID.c_str() 
                                                encoding:[NSString defaultCStringEncoding]];

    [[IAPHelperFight_objc sharedHelper] buyProductIdentifier:nsID];
}

And this would be the code for IAPWrapper.mm:

#ifndef xxx_IAPWrapper_h
#define xxx_IAPWrapper_h

#import "IAPHelperFight_objc.h"
#import "IAPCallback.h"

class IAPWrapper
{
public:
    // Some users might have IAP disabled
    bool canMakePayments();

    void requestProducts(iOSBridge::Callbacks::IAPCallback* callback);

    void buyProductIdentifier(const std::string& productID);

};

#endif

Then we need to refresh the “pure” Objective-C class that gives us the real access to the IAP SDK. This is the IAPHelper_objc class. This is the IAPHelper_objc.h declaration:

#import <Foundation/Foundation.h>
#import "StoreKit/StoreKit.h"
#include "IAPCallback.h"

#define kProductsLoadedNotification         @"ProductsLoaded"
#define kProductPurchasedNotification       @"ProductPurchased"
#define kProductPurchaseFailedNotification  @"ProductPurchaseFailed"

@interface IAPHelper_objc : NSObject  {
    NSSet * _productIdentifiers;    
    NSArray * _products;
    NSMutableSet * _purchasedProducts;
    SKProductsRequest * _request;
    iOSBridge::Callbacks::IAPCallback* latestIAPCallback;
}

@property (retain) NSSet *productIdentifiers;
@property (retain) NSArray * products;
@property (retain) NSMutableSet *purchasedProducts;
@property (retain) SKProductsRequest *request;

- (void)buyProductIdentifier:(NSString *)productIdentifier;
- (void)requestProducts;
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (bool)canMakePayments;
- (void)requestProductsWithCallback:(iOSBridge::Callbacks::IAPCallback*)callback;
- (void)buyProductIdentifier:(NSString *)productIdentifier;

@end

And this is the implementation for IAPHelper_objc.m:

#import "IAPHelper_objc.h"

@implementation IAPHelper_objc

@synthesize productIdentifiers = _productIdentifiers;
@synthesize products = _products;
@synthesize purchasedProducts = _purchasedProducts;
@synthesize request = _request;

- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers{
    latestIAPCallback = nil;

    if ((self = [super init])) {
        _productIdentifiers = [productIdentifiers retain];

        // Check for previously purchased products
        NSMutableSet * purchasedProducts = [NSMutableSet set];
        for (NSString * productIdentifier in _productIdentifiers) {
            BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
            if (productPurchased) {
                [purchasedProducts addObject:productIdentifier];
                NSLog(@"Previously purchased: %@", productIdentifier);
            }
            NSLog(@"Not purchased: %@", productIdentifier);
        }
        self.purchasedProducts = purchasedProducts;

    }
    return self;
}

- (bool)canMakePayments
{
    return [SKPaymentQueue canMakePayments];
}

- (void)requestProductsWithCallback:(iOSBridge::Callbacks::IAPCallback*)callback
{
    latestIAPCallback = callback;
    [self requestProducts];
}

- (void)requestProducts {
    self.request = [[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];
    _request.delegate = self;
    [_request start];

}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {

    NSLog(@"Received products results...");   
    self.products = response.products;
    self.request = nil;    

    //[[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:_products];  

    std::vector items;

    // populate UI
    for(int i=0;iproductsDownloaded(items);
}

- (void)productsLoaded:(NSNotification *)notification {
    [NSObject cancelPreviousPerformRequestsWithTarget:self]; 
}

- (void)recordTransaction:(SKPaymentTransaction *)transaction {    
    // Optional: Record the transaction on the server side...    
}

- (void)provideContent:(NSString *)productIdentifier {

    NSLog(@"Toggling flag for: %@", productIdentifier);
    [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:productIdentifier];
    [[NSUserDefaults standardUserDefaults] synchronize];
    [_purchasedProducts addObject:productIdentifier];

    //[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:productIdentifier];

}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {

    NSLog(@"completeTransaction...");

    [self recordTransaction: transaction];
    [self provideContent: transaction.payment.productIdentifier];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

- (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];

}

- (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)buyProductIdentifier:(NSString *)productIdentifier {

    NSLog(@"Buying %@...", productIdentifier);

    for(int i=0;i

Then we need to add few lines of code to the AppController.mm file, to the didFinishLaunchingWithOptions method (remember to import the IAPHelperFight_objc.h class also in the AppController.mm file):

// In-app purchases: the helper will be notified when the product purchase transactions come in
[[SKPaymentQueue defaultQueue] addTransactionObserver:[IAPHelperFight_objc sharedHelper]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];

Yes! That’s it, we are now ready to buy products into our games using In-App Purchases!
 
Please feel free to follow me on twitter @jboschaiguade and also our company @plungeint in order to discuss about game development!

This site uses cookies to store information on your computer. Some cookies on this site are essential, and the site won't work as expected without them. Read more about them

ACCEPT