Skip to content

Transaction persistence

lvillani edited this page Nov 8, 2014 · 12 revisions

RMStore delegates transaction persistence and provides two optional reference implementations for storing transactions in the Keychain or in NSUserDefaults. You can implement your transaction, use the reference implementations provided by the library or, in the case of non-consumables and auto-renewable subscriptions, get the transactions directly from the receipt.

#Reference implementations

RMStore provides transaction persistence via RMStoreKeychainPersistence (using the Keychain) and RMStoreUserDefaultsPersistence (using NSUserDefaults).

Neither is intended as a fail-proof solution and, if security is a real concern, you might want to avoid using an open source implementation for transaction persistence. That said, the reference persistors should provide a good starting point for your own implementation.

##RMStoreKeychainPersistence

RMStoreKeychainPersistence is the reference implementation for persisting transactions in the Keychain. Given that the Keychain is not intended for storing complex structures, RMStoreKeychainPersistence only persists product identifiers and their stock.

Installation

####Using CocoaPods

pod 'RMStore/KeychainPersistence'

####Manually

Add the following files from RMStore/Optional to your project:

You will also need to link against Security.framework.

###Working with non-consumables

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

RMStoreKeychainPersistence *persistence = [RMStore defaultStore].transactionPersistor;
BOOL purchased = [persistence isPurchasedProductOfIdentifier:@"fabulousIdol"];

###Working with consumables

Consumables can be purchased more than once and typically will be consumed at most once per purchase. Here is how you would normally operate with a consumable:

RMStoreKeychainPersistence *persistence = [RMStore defaultStore].transactionPersistor;
NSInteger purchaseCount = [persistence countProductOfdentifier:@"banana"];
if (purchaseCount > 0)
{
    BOOL success = [persistence consumeProductOfIdentifier:@"banana"];
}

##RMStoreUserDefaultsPersistence

RMStoreUserDefaultsPersistence is the reference implementation for persisting transactions in NSUserDefaults. It stores more information about transactions than RMStoreKeychainPersistence with the trade-off that user defaults are much less secure.

Installation

####Using CocoaPods

pod 'RMStore/NSUserDefaultsPersistence'

####Manually

Add the following files from RMStore/Optional to your project:

###Obfuscation

By default RMStoreUserDefaultsPersistence stores transactions in NSUserDefaults as objects using NSCoding, as a form of weak obfuscation. For better security, you might want to provide your custom obfuscation. You can do this by subclassing RMStoreUserDefaultsPersistence and overriding the methods defined in the Obfuscation category.

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

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

You will be obfuscating RMStoreTransaction instances, an analogue of SKPaymentTransaction which supports NSCopying, unlike the original. This should make your work a little simpler.

###Working with non-consumables

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

RMStoreUserDefaultsPersistence *persistence = [RMStore defaultStore].transactionPersistor;
BOOL purchased = [persistence isPurchasedProductOfIdentifier:@"fabulousIdol"];

###Working with consumables

Consumables can be purchased more than once and typically will be consumed at most once per purchase. Here is how you would normally operate with consumables:

RMStoreUserDefaultsPersistence *persistence = [RMStore defaultStore].transactionPersistor;
NSInteger purchaseCount = [persistence countProductOfdentifier:@"banana"];
if (purchaseCount > 0)
{
    BOOL success = [persistence consumeProductOfIdentifier:@"banana"];
}

#Custom persistence

RMStore delegates transaction persistence via the trasactionPersistor property, enabling you to provide your own implementation using the RMStoreTransactionPersistor protocol:

- (void)persistTransaction:(SKPaymentTransaction*)transaction;

Query methods are not part of the protocol. It's up to you to decide how to implement them, as well as how much information of the transaction will be persisted.

#Setting the persistor

No matter if you use a custom or reference persistor, you will need to set it at startup. For example:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _persistor = [[RMStoreKeychainPersistence alloc] init];
    [RMStore defaultStore].transactionPersistor = _persistor;
    // Your code
    return YES;
}

Bear in mind that transactionPersistor is a weak property. In the above example the app delegate is responsible of retaining the persistor.

#Using the receipt

Starting with iOS 7, transactions of non-consumables and auto-renewable subscriptions are always included in the app receipt. This is not the case for consumables and non-renewing subscriptions, whose transactions are included only once.

For apps offering non-consumables and auto-renewable subscriptions, querying the receipt might be enough to satisfy their transaction persistence needs. RMStore also provides optional classes to parse (RMAppReceipt) and verify (RMStoreAppReceiptVerificator) the app receipt. For more information, see Receipt verification.