RMStore

A lightweight iOS library for In-App Purchases

@hpique

What's the problem with StoreKit?

StoreKit uses delegates

A single object is notified of the result of each request.


- (void)requestProducts
{
	NSSet *products = [NSSet setWithArray:@[@"fabulousIdol", @"rootBeer", @"rubberChicken"]];
    _request = [[SKProductsRequest alloc] initWithProductIdentifiers:
            [NSSet setWithObject:products]];
    _request.delegate = self;
    [_request start];
}

- (void)productsRequest:(SKProductsRequest *)request 
     didReceiveResponse:(SKProductsResponse *)response
{
    NSLog(@"Products loaded", @"");
    _request = nil;
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
    NSLog(@"Something went wrong", @"");
    _request = nil;
}
						

Yet In-App Purchase logic has many stakeholders

RMStore adds blocks and notifications to StoreKit

Making your code more contextual and reducing coupling.


NSSet *products = [NSSet setWithArray:@[@"fabulousIdol", @"rootBeer", @"rubberChicken"]];
[[RMStore defaultStore] requestProducts:products
success:^(NSArray *products, NSArray *invalidProductIdentifiers) {
    NSLog(@"Products loaded", @"");
} failure:^(NSError *error) {
    NSLog(@"Something went wrong", @"");
}];
						

Features

  • StoreKit with blocks and notifications
  • Receipt verification (optional)
  • Purchase management

RMStore meta-features

  • One class
  • No external dependencies
  • Well documented
  • Demo project
  • Unit tests with 100% code coverage
  • CocoaPods support
  • Continuos integration

StoreKit with blocks

RMStore adds blocks to all asynchronous StoreKit operations.

Products request


NSSet *products = [NSSet setWithArray:@[@"fabulousIdol", @"rootBeer", @"rubberChicken"]];
[[RMStore defaultStore] requestProducts:products
success:^(NSArray *products, NSArray *invalidProductIdentifiers) {
    NSLog(@"Products loaded", @"");
} failure:^(NSError *error) {
    NSLog(@"Something went wrong", @"");
}];
					

Add payment


[[RMStore defaultStore] addPayment:@"waxLips" 
success:^(SKPaymentTransaction *transaction) {
    NSLog(@"Product purchased", @"");
} failure:^(SKPaymentTransaction *transaction, NSError *error) {
    NSLog(@"Something went wrong", @"");
}];
					

Restore transactions


[[RMStore defaultStore] restoreTransactionsOnSuccess:^{
    NSLog(@"Transactions restored", @"");
} failure:^(NSError *error) {
    NSLog(@"Something went wrong", @"");
}];
					

Notifications

RMStore sends notifications of StoreKit related events and extends NSNotification to provide relevant information.

Adding and removing the observer

To receive notifications, implement the desired methods of the RMStoreObserver protocol and add the observer to RMStore.


[[RMStore defaultStore] addStoreObserver:self];
// ...
[[RMStore defaultStore] removeStoreObserver:self];
					

Products request notifications


- (void)storeProductsRequestFailed:(NSNotification*)notification
{
    NSError *error = notification.storeError;
}

- (void)storeProductsRequestFinished:(NSNotification*)notification 
{
    NSArray *products = notification.products;
    NSArray *invalidProductIdentifiers = notification.invalidProductIdentififers;
}
					

Payment transaction notifications

Sent after a payment has been requested or for each restored transaction.


- (void)storePaymentTransactionFailed:(NSNotification*)notification
{
    NSError *error = notification.storeError;
    NSString *productIdentifier = notification.productIdentifier;
    SKPaymentTransaction *transaction = notification.transaction;
}

- (void)storePaymentTransactionFinished:(NSNotification*)notification
{
    NSString *productIdentifier = notification.productIdentifier;
    SKPaymentTransaction *transaction = notification.transaction;
}
					

Restore transactions notifications


- (void)storeRestoreTransactionsFailed:(NSNotification*)notification;
{
    NSError *error = notification.storeError;
}

- (void)storeRestoreTransactionsFinished:(NSNotification*)notification { }
					

Receipt verification

RMStore doesn't perform receipt verification by default.

You can provide your own custom verification...

...or use the app-side verification provided by the library.

App-side verification

RMStore provides optional app-side receipt verification via RMStoreLocalReceiptVerificator.


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _receiptVerificator = [[RMStoreLocalReceiptVerificator alloc] init]; // Keep a reference to the verificator as the below property is weak
    [RMStore defaultStore].receiptVerificator = _receiptVerificator;
    // Your code
    return YES;
}
					

Custom verification

Implement the RMStoreReceiptVerificator protocol:


- (void)verifyReceiptOfTransaction:(SKPaymentTransaction*)transaction
                           success:(void (^)())successBlock
                           failure:(void (^)(NSError *error))failureBlock;
					

You will also need to set the receiptVerificator delegate at startup, as indicated before.

Purchase management

RMStore stores transactions in NSUserDefaults with weak obfuscation. It also offers various methods to query and manage purchases.

Working with non-consumables

Non-consumables can only be purchased once. To know if a non-consumable has been purchased:


BOOL purchased = [[RMStore defaultStore] isPurchasedForIdentifier:@"fabulousIdol"];
					

Working with consumables

Consumables can be purchased more than once and tipically will be consumed at most once per purchase. Normally you would:


NSInteger purchaseCount = [[RMStore defaultStore] countPurchasesForIdentifier:@"banana"];
if (purchaseCount > 0)
{
    BOOL success = [[RMStore defaultStore] consumeProductForIdentifier:@"banana"];
}					

Obfuscation

By default RMStore stores transactions in NSUserDefaults as objects using NSCoding, as a form of weak obfuscation.

Custom obfuscation

Implement the RMStoreTransactionObfuscator protocol and set the transactionObfuscator delegate at startup.


- (NSData*)dataWithTransaction:(RMStoreTransaction*)transaction;

- (RMStoreTransaction*)transactionWithData:(NSData*)data;
					

You will be obfuscating RMStoreTransaction instances, an analogue of SKPaymentTransaction which supports NSCopying, unlike the original.

Current status

RMStore is in version 0.3.

It works but its interface might change.

Roadmap to version 1.0

iOS 7 StoreKit API changes

Ongoing work in branch ios7.

  • Support applicationUsername in addPayment and restoreTransactions
  • Refresh receipt with blocks and notications
  • Verify the app receipt

Make purchase management optional

And allow you to choose how to store purchases (e.g., keychain).

Plus

  • OS X target and demo project
  • Convenience methods for subscriptions
  • Support content downloads

Questions?