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

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

iOS allows us to sell free apps or paid apps, but there is also another business model available for the developer: the In App Purchases (IAP). IAP are usually used to sell virtual goods, or to unlock premium features or additional content across apps and games. In this tutorial we will learn how to implement them when you are using Cocos2d-x.

Defining the virtual products

The very first step is to define your App ID in your Provisioning Portal, then you need to define the virtual products for that App ID in iTunes Connect. I will not enter into more details on this section since there are many tutorials about this on the Internet, and it’s not something specific to Cocos2d-x. The better place to find information about how to define the virtual products with the App Store is the iOS Developer Library itself, here is the direct link to the correct place.

The Sandbox Environment

When you are developing IAP for your app you don’t want to spend real money, you will use what is called a “Sandbox Environment”, it uses the same infrastructure of the App Store, but it does not process the payments. It returns transactions as if payments were processed successfully.

In order to purchase the items during the development without spending all your money, you can’t use your Apple ID, you need to create a test user. This can be done on iTunes Connect, into the “Manage users” section. Go there and create a test account. You will see that you can create test users for each region, but usually just one user will be enough.

Testing in the Sandbox

You need to logout from your iTunes account on the test device, you can do this on your iOS device -> settings -> store -> Apple ID -> Sing Out and then DON’T SING IN AGAIN WITH THE NEW TEST ACCOUNT. You need then to launch the app under development and make the purchases, the store kit will automatically prompt you to authenticate, then you can login using the test account to aprove the payment. The financial transaction will never take place, but the transaction will be complete as if a payment was made.

You might also want to make sure the IAP are not restricted in your device. You can check that on Settings->General->Restrictions. I actually suggest you to ENABLE RESTRICTIONS, and then make sure that “Allow Purchases” is ON.

Implementing IAP on a Cocos2d-x based iOS game

So I’ll assume that you have readed the previous section, and are owner of a valid Apple iOS Developer account, have created your App ID, and registered your IAP items into Itunes Connect. Time to move on the code then.

The first issue I need to warn you about is something common on cocos2d-x and you are probably aware of it: Cocos2d-x is C++ and the iOS IAP native SDK is based on Objective-C. Don’t worry because I’ll implement some methods that will be very useful to you, and as you know Objective-C can be easily accessed by C++ (or by Objective-C++ to be more precise that can also be accessed by C++, but I prefer to keep the terminology simple).

The objective of the tutorial is to retrieve the IAP related information from the server, and also to enable the purchases for your users that will allow you to get rich as we all the iOS developers are.

So let’s get started. Remember that we need to call Objective-C from our C++ code, and then call back the C++ code from Objective-C. This process has been explained in a previous article in this blog.  I first declare a callback C++ class that will allow me to send all the information from Objective-C to our C++ code. Put the following class inside the “Classes” folder in your XCODE project:

#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 I create an Objective-C class called IAPCallback.h, I create it into the iOS folder into XCODE, where all the platform specific code should be, this is the definition of the class:

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

#define kProductsLoadedNotification @"ProductsLoaded"

@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)requestProducts;
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (bool)canMakePayments;
- (void)requestProductsWithCallback:(iOSBridge::Callbacks::IAPCallback*)callback;

@end

And this would be the implementation of the class:

#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)dealloc
{
	[_productIdentifiers release];
	_productIdentifiers = nil;
	[_products release];
	_products = nil;
	[_purchasedProducts release];
	_purchasedProducts = nil;
	[_request release];
	_request = nil;
	[super dealloc];
}

@end

Notice that I have a method called “canMakePayments”. It is very important to have that method, because if the user has disabled the in-app purchases we should respond to that and disabling him the access to the game shop for instance. Then you just need to call the “requestProductsWithCallback” method, and you are done…

It’s very easy when you know the small details :-)

Credits:
Part of the objective-c code on this article was borrowed from Ryan’s website.

 

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