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
}
}
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!