Purchases is a client for the RevenueCat subscription and purchase tracking system. It is an open source framework that provides a wrapper around StoreKit
and the RevenueCat backend to make implementing in-app subscriptions in Swift
or Objective-C
easy - receipt validation and status tracking included!
RevenueCat | |
---|---|
✅ | Server-side receipt validation |
➡️ | Webhooks - enhanced server-to-server communication with events for purchases, renewals, cancellations, and more |
🖥 | macOS support |
🎯 | Subscription status tracking - know whether a user is subscribed whether they're on iOS, Android or web |
📊 | Analytics - automatic calculation of metrics like conversion, mrr, and churn |
📝 | Online documentation up to date |
🔀 | Integrations - over a dozen integrations to easily send purchase data where you need it |
💯 | Well maintained - frequent releases |
📮 | Great support - Help Center |
🤩 | Awesome new features |
Purchases is available through CocoaPods and Carthage
pod "Purchases", "2.6.0"
And then run:
pod install
github "revenuecat/purchases-ios" "2.6.0"
And then run:
carthage update --no-use-binaries
Purchases requires XCode 10.2+ and minimum targets iOS 9.0+ and macOS 10.12+
For more detailed information, you can view our complete documentation at docs.revenuecat.com.
Log in to the RevenueCat dashboard and obtain a free API key for your application.
Don't forget to enable the In-App Purchase capability for your project under
Project Target -> Capabilities -> In-App Purchase
You should only configure Purchases once (usually on app launch) as soon as your app has a unique user id for your user. This can be when a user logs in if you have accounts or on launch if you can generate a random user identifier. The same instance is shared throughout your app by accessing the .shared
instance in the SDK.
Swift:
import Purchases
Purchases.debugLogsEnabled = true
Purchases.configure(withAPIKey: "my_api_key", appUserID: "my_app_user_id")
Obj-C:
#import "RCPurchases.h"
RCPurchases.debugLogsEnabled = YES;
[RCPurchases configureWithAPIKey:@"my_api_key" appUserID:@"my_app_user_id"];
Purchases will automatically fetch the latest active entitlements and get the product information from Apple or Google. This means when users launch your purchase screen, products will already be loaded.
Below is an example of fetching entitlements and launching an upsell screen.
Swift:
func displayUpsellScreen() {
Purchases.shared.entitlements { (entitlements, error) in
let vc = UpsellController()
vc.entitlements = entitlements
presentViewController(vc, animated: true, completion: nil)
}
}
Obj-C
[[RCPurchases sharedPurchases] entitlementsWithCompletionBlock:^(RCEntitlements *entitlements, NSError *error) {
UpsellViewController *vc = [[UpsellViewController alloc] init];
vc.entitlements = entitlements;
[self presentViewController:vc animated:YES completion:nil];
}];
When it comes time to make a purchase, Purchases has a simple method, makePurchase
. The code sample below shows the process of purchasing a product and confirming it unlocks the "my_entitlement_identifier" content.
Swift:
Purchases.shared.makePurchase(product, { (transaction, purchaserInfo, error, cancelled) in
if let purchaserInfo = purchaserInfo {
if purchaserInfo.entitlements["my_entitlement_identifier"].isActive == true {
// Unlock that great "pro" content
}
}
})
Obj-C:
[[RCPurchases sharedPurchases] makePurchase:product withCompletionBlock:^(SKPaymentTransaction *transaction, RCPurchaserInfo *purchaserInfo, NSError *error, BOOL cancelled) {
if (purchaserInfo.entitlements[@"my_entitlement_identifier"].isActive) {
// Unlock that great "pro" content.
}
}];
makePurchase
handles the underlying framework interaction and automatically validates purchases with Apple through our secure servers. This helps reduce in-app purchase fraud and decreases the complexity of your app. Receipt tokens are stored remotely and always kept up-to-date.
Purchases makes it easy to check what active subscriptions the current user has. This can be done two ways within the .purchaserInfo
method:
- Checking active Entitlements - this lets you see what entitlements (from RevenueCat dashboard) are active for the user.
- Checking the active subscriptions - this lets you see what product ids (from iTunes Connect or Play Store) are active for the user.
Swift:
Purchases.shared.purchaserInfo { (purchaserInfo, error) in
if let purchaserInfo = purchaserInfo {
if purchaserInfo.entitlements["my_entitlement_identifier"].isActive == true {
// Grant user "pro" access
}
}
}
Obj-C:
[[RCPurchases sharedPurchases] purchaserInfoWithCompletionBlock:^(RCPurchaserInfo * purchaserInfo, NSError * error) {
if (purchaserInfo.entitlements[@"my_entitlement_identifier"].isActive) {
// Grant user "pro" access
}
}];
Since the SDK updates and caches the latest PurchaserInfo when the app becomes active, the completion block in
.purchaserInfo
won't need to make a network request in most cases.
Restoring purchases is a mechanism by which your user can restore their in-app purchases, reactivating any content that had previously been purchased from the same store account (Apple or Google).
If two different App User IDs try to restore transactions from the same underlying store account (Apple or Google) RevenueCat will create an alias between the two App User IDs and count them as the same user going forward.
This is a common if your app does not have accounts and is relying on RevenueCat's random App User IDs.
Swift:
Purchases.shared.restoreTransactions { (purchaserInfo, error) in
//... check purchaserInfo to see if entitlement is now active
}
Obj-C:
[[RCPurchases sharedPurchases] restoreTransactionsWithCompletionBlock:^(RCPurchaserInfo * purchaserInfo, NSError * error) {
//... check purchaserInfo to see if entitlement is now active
}];
Restoring purchases for logged in users:
If you've provided your own App User ID, calling restoreTransactions could alias the logged in user to another generated App User ID that has made a purchase on the same device.
Allow Sharing App or Play Store Accounts
By default, RevenueCat will not let you reuse an App or Play Store account that already has an active subscription. If you set allowSharingAppStoreAccount = True the SDK will be permissive in accepting shared accounts, creating aliases as needed.
By default allowSharingAppStoreAccount is True for RevenueCat random App User IDs but must be enabled manually if you want to allow permissive sharing for your own App User IDs.
The Purchases SDK supports purchases initiated from the App Store (iOS 11+) through the optional delegate method purchases(purchases:shouldPurchasePromoProduct:makeDeferredPurchase:)
.
Swift:
func purchases(_ purchases: Purchases, shouldPurchasePromoProduct product: SKProduct, defermentBlock makeDeferredPurchase: @escaping RCDeferredPromotionalPurchaseBlock) {
// Save the deferment block and call it later...
let defermentBlock = makeDeferredPurchase
// ...or call it right away to proceed with the purchase
defermentBlock { (transaction, info, error, cancelled) in
if let purchaserInfo = info {
if purchaserInfo.entitlements.all["my_entitlement_identifier"]?.isActive == true {
// Unlock that great "pro" content
}
}
}
}
To test this in sandbox mode, open this URL on a physical device in Safari:
itms-services://?action=purchaseIntent&bundleId=<YOUR_BUNDLE_ID>&productIdentifier=<YOUR_SKPRODUCT_ID>
Since Purchases SDK works across different platforms, a user's subscription status may change from a variety of sources. You can respond to any changes in purchaser info by conforming to an optional delegate method, didReceivePurchaserInfo:
. This will fire whenever we receive a change in purchaser info and you should expect it to be called at launch and throughout the life of the app.
Depending on your app, it may be sufficient to ignore the delegate and simply handle changes to purchaser information the next time your app is launched.
Swift:
func purchases(_ purchases: Purchases, didReceiveUpdated purchaserInfo: PurchaserInfo) {
// handle any changes to purchaserInfo
}
Obj-C:
- (void)purchases:(nonnull RCPurchases *)purchases didReceiveUpdatedPurchaserInfo:(nonnull RCPurchaserInfo *)purchaserInfo {
// handle any changes to purchaserInfo
}
When an error has occurred, the completion callback will receive an NSError
object. For a complete list of errors, see our online error handling documentation: https://docs.revenuecat.com/docs/errors
When investigating or logging errors, review the userInfo
dictionary, paying attention to the following keys:
RCReadableErrorCodeKey
contains a cross-platform error name string that can be used for identifying the error.NSLocalizedDescriptionKey
contains a description of the error. This description is meant for the developer.NSUnderlyingErrorKey
contains the underlying error that caused the error in question, if an underlying error is present.
To handle specific errors, you can iterate through
Swift:
if let err = error as NSError? {
// log error details
print("Error: \(err.userInfo[ReadableErrorCodeKey])")
print("Message: \(err.localizedDescription)")
print("Underlying Error: \(err.userInfo[NSUnderlyingErrorKey])")
// handle specific errors
switch PurchasesErrorCode(_nsError: err).code {
case .networkError:
showAlert("Network error, check your connection and try again.")
case .purchaseNotAllowedError:
showAlert("Purchases not allowed on this device.")
case .purchaseInvalidError:
showAlert("Purchase invalid, check payment source.")
default:
break
}
}
Obj-C:
if (error) {
// log error details
NSLog(@"RCError: %@", [error.userInfo objectForKey:RCReadableErrorCodeKey]);
NSLog(@"Message: %@", error.localizedDescription);
NSLog(@"Underlying Error: %@", [error.userInfo objectForKey:NSUnderlyingErrorKey]);
switch ([error code]) {
case RCNetworkError:
showError(@"Network error, check your connection and try again.");
case RCPurchaseNotAllowedError:
showError(@"Purchases not allowed on this device.");
case RCPurchaseInvalidError:
showError(@"Purchase invalid, check payment source.");
default:
break;
}
}
You can enabled detailed debug logs by setting debugLogsEnabled = true
. You can set this before you configure Purchases.
Swift:
Purchases.debugLogsEnabled = true
Purchases.configure(withAPIKey: "my_api_key", appUserID: "my_app_user_id")
Obj-C:
RCPurchases.debugLogsEnabled = YES;
[RCPurchases configureWithAPIKey:@"my_api_key" appUserID:@"my_app_user_id"];
OS_ACTIVITY_MODE
On iOS, disabling
OS_ACTIVITY_MODE
in your XCode scheme will block debug logs from printing in the console. If you have debug logs enabled, but don't see any output, go toProduct -> Scheme -> Edit Scheme...
in Xcode and uncheck theOS_ACTIVITY_MODE
environment variable.
Example output:
[Purchases] - DEBUG: Debug logging enabled.
[Purchases] - DEBUG: SDK Version - 2.6.0
[Purchases] - DEBUG: Initial App User ID - (null)
[Purchases] - DEBUG: GET /v1/subscribers/<APP_USER_ID>
[Purchases] - DEBUG: GET /v1/subscribers/<APP_USER_ID>/products
[Purchases] - DEBUG: No cached entitlements, fetching
[Purchases] - DEBUG: Vending purchaserInfo from cache
[Purchases] - DEBUG: applicationDidBecomeActive
[Purchases] - DEBUG: GET /v1/subscribers/<APP_USER_ID>/products 200
An entitlement represents features or content that a user is "entitled" to. With Entitlements, you can set up your available in-app products remotely and control their availability without the need to update your app. For more information on configuring entitlements, look at our entitlements documetation.
We've added an example in this project showing a simple example using Purchases with the RevenueCat backend. Note that the pre-registered in app purchases in the demo apps are for illustration purposes only and may not be valid in App Store Connect. Set up your own purchases with RevenueCat when running the example.
- Head over to our online documentation for complete setup guides
- If you haven't already, make sure your products are configured correctly in the RevenueCat dashboard by checking out our guide on entitlements
- If you want to use your own user identifiers, read about setting app user ids
- If you're moving to RevenueCat from another system, see our guide on migrating your existing subscriptions
- Once you're ready to test your integration, you can follow our guides on testing purchases
You can use Github Issues to report any bugs and issues with Purchases. Here is some advice for users that want to report an issue:
- Make sure that you are using the latest version of Purchases. The issue that you are about to report may be already fixed in the latest master branch version: https://github.com/revenuecat/purchases-ios/tree/master.
- Providing reproducible steps for the issue will shorten the time it takes for it to be fixed - a Gist is always welcomed!
- Since some issues are Sandbox specific, specifying what environment you encountered the issue might help.
If you have questions or need help integrating Purchases please start by heading to our online documentation and checking out the guides and support resources we have there.
If there is something you'd like to see included or feel anything is missing you can add a feature to our public roadmap. If the feature already exists, or you see something else you'd like, upvote it.
Purchases SDK is free to use but some features require a paid plan. You can find more about that on our website on the pricing plan page.