Migrate from SwiftyStoreKit
Migrate your app from SwiftyStoreKit to Qonversion
Qonversion provides server-side receipt validation, real-time app monitoring, subscription analytics, integrations with the leading mobile platforms, and more. You can easily migrate from SwiftyStoreKit to Qonversion. Qonversion is probably the best SwiftyStoreKit alternative.
Why migrate from SwiftyStoreKit?
Qonversion makes it easy to get the list of available in-app products, make and restore purchases, check user entitlements, and validate user receipts. Server-side receipt validation guarantees the entitlements' accuracy.
Unlike with SwiftyStoreKit, you don't need to worry about storing the information on user subscriptions. Qonversion handles entitlement logic and provides simple methods to get accurate user entitlements with just a few lines of code.
Qonversion provides a cross-platform subscription infrastructure for iOS, Android, and web apps, so it's really easy to handle end-user access across mobile and desktop apps.
You can learn more about Qonversion products & entitlements here.
SwiftyStoreKit Migration Steps
1. App start
SwiftyStoreKit
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// see notes below for the meaning of Atomic / Non-Atomic
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
if purchase.needsFinishTransaction {
// Deliver content from server, then:
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
// Unlock content
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
return true
}
Qonversion
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let config = Configuration(projectKey: "projectKey", launchMode: .subscriptionManagement)
Qonversion.initWithConfig(config)
return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
QONConfiguration *configuration = [[QONConfiguration alloc] initWithProjectKey:@"projectKey" launchMode:QONLaunchModeSubscriptionManagement];
[Qonversion initWithConfig:configuration];
return YES;
}
Migration Step:
Remove the SwiftyStoreKit completeTransactions()
method, and replace it with the Qonversion SDK launch()
method.
2. Load products
SwiftyStoreKit
SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
print("Product: \(product.localizedDescription), price: \(priceString)")
}
else if let invalidProductId = result.invalidProductIDs.first {
print("Invalid product identifier: \(invalidProductId)")
}
else {
print("Error: \(result.error)")
}
}
Qonversion
Qonversion.shared().products { productsList, error in
let product = productsList["main"]
if product?.type == .trial {
}
}
[[Qonversion sharedInstance] products:^(NSDictionary<NSString *,QONProduct *> * _Nonnull productsList, NSError * _Nullable error) {
if (error) {
// Handle error
}
QONProduct *product = productsList[@"main"];
if (product && product.type == QONProductTypeTrial) {
}
}];
Migration Step:
Products are configured in the Qonversion dashboard for Qonversion SDK and mapped to SKProducts. Configure the products in Qonversion, then replace retrieveProductsInfo()
in SwiftyStoreKit with products()
in Qonversion SDK.
2. Making a purchase
SwiftyStoreKit
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: true) { result in
switch result {
case .success(let purchase):
print("Purchase Success: \(purchase.productId)")
case .error(let error):
switch error.code {
case .unknown: print("Unknown error. Please contact support")
case .clientInvalid: print("Not allowed to make the payment")
case .paymentCancelled: break
case .paymentInvalid: print("The purchase identifier was invalid")
case .paymentNotAllowed: print("The device is not allowed to make the payment")
case .storeProductNotAvailable: print("The product is not available in the current storefront")
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
default: print((error as NSError).localizedDescription)
}
}
}
Qonversion
Qonversion.shared().purchase("main") { (entitlements, error, isCancelled) in
if let error = error as NSError? {
let skError = SKError(_nsError: error)
switch skError.code {
case .unknown: print("unknown error")
case .clientInvalid: print("clientInvalid")
case .paymentCancelled: print("paymentCancelled")
case .paymentInvalid: print("paymentInvalid")
case .paymentNotAllowed: print("paymentNotAllowed")
case .storeProductNotAvailable: print("storeProductNotAvailable")
case .cloudServicePermissionDenied: print("cloudServicePermissionDenied")
case .cloudServiceNetworkConnectionFailed: print("cloudServiceNetworkConnectionFailed")
case .cloudServiceRevoked: print("cloudServiceRevoked")
default: print((error as NSError).localizedDescription)
}
}
if let premium: Qonversion.Entitlement = entitlements["premium"], premium.isActive {
// Flow for success state
}
}
[[Qonversion sharedInstance] purchase:@"main" result:^(NSDictionary<NSString *, QONEntitlement *> * _Nonnull entitlements,
NSError * _Nullable error,
BOOL cancelled) {
if (error) {
switch (error.code) {
case SKErrorUnknown:
NSLog(@"SKErrorUnknown");
break;
case SKErrorClientInvalid:
NSLog(@"SKErrorClientInvalid");
case SKErrorPaymentCancelled:
NSLog(@"SKErrorPaymentCancelled");
case SKErrorPaymentInvalid:
NSLog(@"SKErrorPaymentInvalid");
case SKErrorPaymentNotAllowed:
NSLog(@"SKErrorPaymentNotAllowed");
case SKErrorStoreProductNotAvailable:
NSLog(@"SKErrorStoreProductNotAvailable");
case SKErrorCloudServicePermissionDenied:
NSLog(@"SKErrorCloudServicePermissionDenied");
case SKErrorCloudServiceNetworkConnectionFailed:
NSLog(@"SKErrorCloudServiceNetworkConnectionFailed");
case SKErrorCloudServiceRevoked:
NSLog(@"SKErrorCloudServiceRevoked");
default:
NSLog(@"%@", error.localizedDescription);
break;
}
}
QONEntitlement *premiumEntitlement = entitlements[@"premium"];
if (premiumEntitlement && premiumEntitlement.isActive) {
// Flow for success state
}
}];
Migration Step:
Purchases are initiated from a product ID or a SKProduct in SwiftyStoreKit. In Qonversion purchases should be initiated from Qonversion Product id. Replace the SwiftyStoreKit purchaseProduct()
method with purchase()
. Pass the Qonversion product id that was loaded previously.
You don't need to parse through SKError to check if the user has cancelled the payment. The property cancelled
is provided in the callback for that case.
Confirm that the subscription has been purchased by checking if the permissions
object contains active entitlement for the premium content you configured in the Qonversion dashboard.
3. Restore previous purchases
SwiftyStoreKit
SwiftyStoreKit.restorePurchases(atomically: true) { results in
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}
Qonversion
Qonversion.shared().restore { [weak self] (entitlements, error) in
if let error = error {
// Handle error
}
if let entitlement: Qonversion.Entitlement = entitlements["plus"], entitlement.isActive {
// Restored and entitlement is active
}
}
[[Qonversion sharedInstance] restore:^(NSDictionary<NSString *, QONEntitlement *> * _Nonnull result, NSError * _Nullable error) {
if (error) {
// Handle error
}
QONEntitlement *entitlement = result[@"active"];
if (entitlement && entitlement.isActive) {
// Restored and entitlement is active
}
}];
Migration step:
Just replace the SwiftyStoreKit restorePurchases
with Qonversion restore
method.
3. Handling purchases started on the App Store (iOS 11 or later)
Unlike SwiftyStoreKit, Qonversion SDK supports App Store promoted in-app purchases including delayed promoted purchases or making a purchase after specific checks. For example, if you don't want to show the purchase screen to the user before your app's onboarding, you can delay the purchase and call it when needed.
Let's have a look at the example below:
SwiftyStoreKit
SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
// return true if the content can be delivered by your app
// return false otherwise
}
Qonversion
By default App Store promoted purchases will be enabled, and purchasing flow will run on the app start. If you need additional logic set QNPromoPurchasesDelegate:
Qonversion.shared().setPromoPurchasesDelegate(self)
[[Qonversion sharedInstance] setPromoPurchasesDelegate:self];
Then implement the delegate function:
func shouldPurchasePromoProduct(withIdentifier productID: String, executionBlock: @escaping Qonversion.PromoPurchaseCompletionHandler) {
// check AppStore productID value in case you want to enable promoted purchase only for specific products
let completion: Qonversion.PurchaseCompletionHandler = {result, error, flag in
// handle purchased product or error
}
// call this block if you want to allow promoted purchase or just store the block and call when needed
// do nothing and do not call the block if you don't want to allow the purchase
executionBlock(completion)
}
Migration step:
App Store promotional offers support is available by default. If you need additional logic, then use setPromoPurchasesDelegate()
and the function shouldPurchasePromoProduct()
4. Receipt verification and user entitlements
SwiftyStoreKit
Receipt verification is done locally in SwiftyStoreKit. This might lead to inaccurate subscription status data and is not recommended by Apple.
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = "com.musevisions.SwiftyStoreKit.Subscription"
// Verify the purchase of a Subscription
let purchaseResult = SwiftyStoreKit.verifySubscription(
ofType: .autoRenewable, // or .nonRenewing (see below)
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let expiryDate, let items):
print("\(productId) is valid until \(expiryDate)\n\(items)\n")
case .expired(let expiryDate, let items):
print("\(productId) is expired since \(expiryDate)\n\(items)\n")
case .notPurchased:
print("The user has never purchased \(productId)")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}
Qonversion
Qonversion provides server-side receipt validation out-of-the-box. You can check the user subscription status as follows:
Qonversion.shared().checkEntitlements { (entitlements, error) in
if let error = error {
// handle error
return
}
if let premium: Qonversion.Entitlement = entitlements["premium"], premium.isActive {
switch premium.renewState {
case .willRenew, .nonRenewable:
// .willRenew is the state of an auto-renewable subscription
// .nonRenewable is the state of consumable/non-consumable IAPs that could unlock lifetime access
break
case .billingIssue:
// Grace period: entitlement is active, but there was some billing issue.
// Prompt the user to update the payment method.
break
case .cancelled:
// The user has turned off auto-renewal for the subscription, but the subscription has not expired yet.
// Prompt the user to resubscribe with a special offer.
break
default: break
}
}
}
[[Qonversion sharedInstance] checkEntitlements:^(NSDictionary<NSString *, QONEntitlement *> * _Nonnull entitlements,
NSError * _Nullable error) {
QONEntitlement *premiumEntitlement = entitlements[@"premium"];
if (premiumEntitlement && premiumEntitlement.isActive) {
switch (premiumEntitlement.renewState) {
case QONEntitlementRenewStateWillRenew:
case QONEntitlementRenewStateNonRenewable:
// QONEntitlementRenewStateWillRenew is state for auto-renewable purchases
// QONEntitlementRenewStateNonRenewable is state for in-app purchases that unlock the entitlement lifetime
break;
case QONEntitlementRenewStateBillingIssue:
// Grace period: entitlement is active, but there was some billing issue.
// Prompt the user to update the payment method.
break;
case QONEntitlementRenewStateCancelled:
// The user canceled the subscription, but the subscription has not expired yet.
// Prompt the user to resubscribe with some special offer.
break;
default:
break;
}
}
}];
Active Subscriptions Migration
There are several ways to transmit the data on subscribers that have already purchased a subscription using SwiftyStoreKit to Qonversion.
1. First option
Provide your users with a restore purchase option. For example, you can direct them to the screen where restore purchase is available. Qonversion will get all the required data including the historical data for analytics once the subscription is restored.
2. Second option
Trigger restore purchase automatically for users, who subscribed using SwiftyStoreKit. This will get all the required data.
Please note, triggering restore will prompt a user to sign in to their Apple ID if they are not signed in already. So you need to trigger restore only once.
Here is the code sample showing how to check and save in UserDefaults that a purchase has been restored for a user:
let keyForMigrated = "isMigratedFromSwiftyStoreKit"
let isMigrated = UserDefaults.standard.bool(forKey: keyForMigrated)
if isMigrated == false {
Qonversion.restore { (_, _) in }
UserDefaults.standard.setValue(true, forKey: keyForMigrated)
}
NSString *keyForRestoredFlag = @"isRestored";
BOOL isRestored = [[NSUserDefaults standardUserDefaults] boolForKey:keyForRestoredFlag];
if (isRestored == false) {
[Qonversion restore:^(NSDictionary<NSString *,QONEntitlement *> * _Nonnull result, NSError * _Nullable error) {}];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:keyForRestoredFlag];
}
3. Third option
If you have user receipts stored on your backend, you can contact us and we will import them into Qonversion.
Updated about 1 year ago
You can check the sample iOS app with Qonversion implementation or watch the video tutorial: