swiftstorekitrevenuecat

Implementing applicationUsername(AppTokenId) in superwall


In Superwall I need to assign the applicationUsername(AppTokenId), to be able to validate the IAPs on my server. As I checked the docs I can create a PurchaseController but there is not place to set the applicationUsername.

    class SubscriptionController : PurchaseController
{
    var orderPresenter = OrderPresenter()
    var selectedProductId = ""
    
    
    
    func purchase(product: SKProduct) async -> SuperwallKit.PurchaseResult {
         var productID = product.productIdentifier
        var data = [String:Any]()
        data["productId"] = productID
        selectedProductId = productID
        
        ApiGenerator.request(targetApi: OrderService.Init(data: data) ,
                             responseModel: SuccessModel_str.self,
                             success: { [self] (response) in
            if response.response.statusCode == 200 {
                return
                if SKPaymentQueue.canMakePayments() {
                    let paymentRequest = SKMutablePayment()
                    paymentRequest.productIdentifier = selectedProductId
                    //TODO: added userID here
                    paymentRequest.applicationUsername = response.body?.id
                    SKPaymentQueue.default().add(paymentRequest)
                } else {
                        // Show error to the user.
                }
            }else {
                
                var errorMessage = response.message
                
            }
        }) { (error) in
            print(error)
        }
        
        return .cancelled
    }
    
    func restorePurchases() async -> SuperwallKit.RestorationResult {
        return .restored
    }
    
    
}

Solution

  • You're right in saying that you need to use a PurchaseController if you want to handle validation yourself and provide an applicationUsername.

    When using a PurchaseController with Superwall, you wouldn't pass the applicationUsername to Superwall. You're responsible for that while handling all the purchasing logic and setting the subscription status within Superwall.

    In this instance, you'll need to create a class that handles the purchasing logic via StoreKit and set the applicationUsername on the SKPayment before adding it to the SKPaymentQueue.

    Here is an example of how you might do this. Note: this isn't a full implementation but gives you a starting point for how to approach this:

    import StoreKit
    import SuperwallKit
    
    final class StoreKitService: NSObject, ObservableObject, SKPaymentTransactionObserver {
      static let shared = StoreKitService()
      private var purchaseCompletion: ((PurchaseResult) -> Void)?
    
      override init() {
        super.init()
        SKPaymentQueue.default().add(self)
      }
    
      func purchase(
        _ product: SKProduct,
        applicationUsername: String
      ) async -> PurchaseResult {
        return await withCheckedContinuation { continuation in
          let payment = SKPayment(product: product)
         
          // Set your application username on the SKPayment
          payment.applicationUsername = applicationUsername
    
          self.purchaseCompletion = { result in
            continuation.resume(with: .success(result))
          }
          SKPaymentQueue.default().add(payment)
        }
      }
    
      func paymentQueue(
        _ queue: SKPaymentQueue,
        updatedTransactions transactions: [SKPaymentTransaction]
      ) {
        for transaction in transactions {
          switch transaction.transactionState {
          case .purchased:
            // TODO: Do verification here
            Superwall.shared.subscriptionStatus = .active
            SKPaymentQueue.default().finishTransaction(transaction)
            purchaseCompletion?(.purchased)
            purchaseCompletion = nil
            // TODO: Handle other cases here
          }
        }
      }
    
      // TODO: Implement restoration here
    }
    
    class SubscriptionController: PurchaseController {
      enum ApiError: Error {
        case invalidResponse(message: String)
      }
    
      func purchase(product: SKProduct) async -> PurchaseResult {
        do {
          let userId = try await getUserId()
          return await StoreKitService.shared.purchase(product, applicationUsername: userId)
        } catch {
          return .failed(error)
        }
      }
    
      private func getUserId() async throws -> String {
        return try await withCheckedThrowingContinuation { continuation in
          ApiGenerator.request(
            targetApi: OrderService.Init(data: data) ,
            responseModel: SuccessModel_str.self,
            success: { response in
              if response.response.statusCode == 200,
                let userId = response.body?.id {
                continuation.resume(returning: userId)
              } else {
                continuation.resume(throwing: ApiError.invalidResponse(message: response.message))
              }
            }
          ) { error in
            continuation.resume(throwing: error)
          }
        }
      }
    
      func restorePurchases() async -> RestorationResult {
        // TODO: Implement restoration logic
        return .restored
      }
    }
    

    Here, I've created a StoreKitService class which you'd use as the basis for handling the purchasing and restoring logic with StoreKit.

    This contains a purchase(_:applicationUsername:) function which is called from the purchase(product:) delegate method of the PurchaseController. This is responsible for creating the SKPayment, setting the applicationUsername, and adding it to the payment queue. When the product is purchased, you'd verify the transaction with your server and then finish the transaction before returning the PurchaseResult.

    Make sure to read the Superwall docs around using a PurchaseController.

    Hope this answers your question!