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.

704

Swifty StoreKit 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.


What’s Next

You can check the sample iOS app with Qonversion implementation or watch the video tutorial: