0

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
    }
    
    
}
Am1rFT
  • 227
  • 5
  • 18

1 Answers1

1

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!

Yusuf
  • 61
  • 3
  • Thanks for comprehensive and detailed response. I only had to change this part and it worked perfectly – Am1rFT Aug 15 '23 at 10:29
  • func purchase( _ product: SKProduct, applicationUsername: String) async -> PurchaseResult { return await withCheckedContinuation { continuation in let paymentRequest = SKMutablePayment() paymentRequest.productIdentifier = product.productIdentifier paymentRequest.applicationUsername = applicationUsername SKPaymentQueue.default().add(paymentRequest) self.purchaseCompletion = { result in continuation.resume(with: .success(result)) } SKPaymentQueue.default().add(paymentRequest) } } – Am1rFT Aug 15 '23 at 10:30