Purchases and Subscription Status

By default, Superwall handles basic subscription-related logic for you:

  1. Purchasing: When the user initiates a checkout on a paywall.
  2. Restoring: When the user restores previously purchased products.
  3. Subscription Status: When the user's subscription status changes to active or expired (by checking the local receipt).

However, if you want more control, you can pass in a PurchaseController when configuring the SDK via configure(apiKey:purchaseController:options:) and manually set Superwall.shared.subscriptionStatus to take over this responsibility.

Step 1: Creating a PurchaseController

A PurchaseController handles purchasing and restoring via protocol methods that you implement. You pass in your purchase controller when configuring the SDK:

// MyPurchaseController.swift

import SuperwallKit
import StoreKit

final class MyPurchaseController: PurchaseController {
  static let shared = MyPurchaseController()
  
  // 1
  func purchase(product: SKProduct) async -> PurchaseResult {
   
    // TODO
    // ----
    // Purchase via StoreKit, RevenueCat, Qonversion or however 
    // you like and return a valid PurchaseResult
    
    return .purchased // .cancelled,  .pending, .failed(Error)
  }
  
  // 2
  func restorePurchases() async -> Bool {
    
    // TODO
    // ----
    // restore purchases and return true if successful.
    
    return true // false
  }
}
@import SuperwallKit;
@import StoreKit;

// MyPurchaseController

@interface MyPurchaseController: NSObject <SWKPurchaseController>
  + (instancetype)sharedInstance;
@end

@implementation MyPurchaseController

+ (instancetype)sharedInstance
{
  static MyPurchaseController *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [MyPurchaseController new];
  });
  return sharedInstance;
}

// 1
- (void)purchaseWithProduct:(SKProduct * _Nonnull)product completion:(void (^ _Nonnull)(enum SWKPurchaseResult, NSError * _Nullable))completion {
  
  // TODO
  // ----
  // Purchase via StoreKit, RevenueCat, Qonversion or however 
  // you like and return a valid SWKPurchaseResult
  
  completion(SWKPurchaseResultPurchased, nil);
}

// 2
- (void)restorePurchasesWithCompletion:(void (^ _Nonnull)(BOOL))completion {
    
  // TODO
  // ----
  // restore purchases and return YES if successful.

  completion(YES);
}

@end

Hereโ€™s what each method is responsible for:

  1. Purchasing a given product. In here, enter your code that you use to purchase a product. Then, return the result of the purchase as a PurchaseResult. This is an enum that contains the following cases, all of which must be handled:
    1. .cancelled: The purchase was cancelled.
    2. .purchased: The product was purchased.
    3. .pending: The purchase is pending/deferred and requires action from the developer.
    4. .failed(Error): The purchase failed for a reason other than the user cancelling or the payment pending.
  2. Restoring purchases. Here, you restore purchases and return a boolean indicating whether the restoration was successful or not.

Step 2: Configuring the SDK With Your PurchaseController

Pass your purchase controller to the configure(apiKey:purchaseController:options:) method:

// AppDelegate.swift

import UIKit
import SuperwallKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    Superwall.configure(
      apiKey: "MYAPIKEY", 
      purchaseController: MyPurchaseController.shared // <- Handle purchases on your own
    )
   
    return true
  }
}
// AppDelegate.m

@import SuperwallKit;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Override point for customization after application launch.
  [Superwall configureWithApiKey:@"MYAPIKEY" purchaseController:[MyPurchaseController sharedInstance] options:nil completion:nil];
  return YES;
}

Step 3: Keeping subscriptionStatus Up-To-Date

You must set Superwall.shared.subscriptionStatus every time the user's subscription status changes, otherwise the SDK won't know who to show a paywall to. This is an enum that has three possible cases:

  1. .unknown: This is the default value. In this state, paywalls will not show and their presentation will be automatically delayed until subscriptionStatus changes to a different value.

  2. .active: Indicates that the user has an active subscription. Paywalls will not show in this state unless you remotely set the paywall to ignore subscription status.

  3. .inactive: Indicates that the user doesn't have an active subscription. Paywalls can show in this state.

Here's how you might do this:

import SuperwallKit

// On app launch, when you are waiting to determine a user's subscription status.
Superwall.shared.subscriptionStatus = .unknown

// When a subscription is purchased, restored, validated, expired, etc...
myService.subscriptionStatusDidChange {
    if user.hasActiveSubscription {
    Superwall.shared.subscriptionStatus = .active 
  } else {
    Superwall.shared.subscriptionStatus = .inactive
  }
}
@import SuperwallKit;

// when you are waiting to determine a user's subscription status
[Superwall sharedInstance].subscriptionStatus = SWKSubscriptionStatusUnknown;

// when a subscription is purchased, restored, validated, expired, etc...
[myService setSubscriptionStatusDidChange:^{
  if (user.hasActiveSubscription) {
    [Superwall sharedInstance].subscriptionStatus = SWKSubscriptionStatusActive;
  } else {
    [Superwall sharedInstance].subscriptionStatus = SWKSubscriptionStatusInactive;
  }
}];

๐Ÿ“˜

subscriptionStatus is cached between app launches