-
Notifications
You must be signed in to change notification settings - Fork 450
Transaction persistence
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.
####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.
####Using CocoaPods
pod 'RMStore/NSUserDefaultsPersistence'
####Manually
Add the following files from RMStore/Optional to your project:
- RMStoreTransaction.h
- RMStoreTransaction.m
- RMStoreUserDefaultsPersistence.h
- RMStoreUserDefaultsPersistence.m
###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.