JTDRECIPE

Just The Damn Recipe

Sign up

iOS App Store Subscription Integration Guide

To enable native App Store subscriptions for your application, you need to modify your iOS project (Xcode) source code.

1. Capabilities & StoreKit

Ensure your App ID has In-App Purchase capability enabled in Apple Developer Portal. In Xcode, add the In-App Purchase capability to your target.

2. Create the Purchase Manager (Swift)

Create a helper class to handle StoreKit 2 interactions and bridge to the WebView.

import StoreKit
import WebKit

class PurchaseManager: NSObject, SKPaymentTransactionObserver {
    static let shared = PurchaseManager()
    weak var webView: WKWebView?
    
    // Map web product IDs to App Store Product IDs
    let productMap: [String: String] = [
        "monthly": "com.jtdrecipe.pro.monthly",
        "6month": "com.jtdrecipe.pro.6month",
        "yearly": "com.jtdrecipe.pro.yearly"
    ]
    
    override init() {
        super.init()
        SKPaymentQueue.default().add(self)
    }
    
    func purchase(webProductId: String) {
        guard let appStoreId = productMap[webProductId] else { return }
        
        if SKPaymentQueue.canMakePayments() {
            let request = SKMutablePayment()
            request.productIdentifier = appStoreId
            SKPaymentQueue.default().add(request)
        }
    }
    
    // Handle Transactions
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased:
                verifyReceipt(transaction: transaction)
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                SKPaymentQueue.default().finishTransaction(transaction)
                // Notify web of failure if needed
            case .restored:
                verifyReceipt(transaction: transaction)
                SKPaymentQueue.default().finishTransaction(transaction)
            default: break
            }
        }
    }
    
    func verifyReceipt(transaction: SKPaymentTransaction) {
        guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
              let receiptData = try? Data(contentsOf: appStoreReceiptURL) else { return }
        
        let receiptString = receiptData.base64EncodedString(options: [])
        
        // Send to your backend function
        verifyOnBackend(receipt: receiptString)
    }
    
    func verifyOnBackend(receipt: String) {
        // Construct JSON payload
        let json: [String: Any] = ["receiptData": receipt]
        guard let jsonData = try? JSONSerialization.data(withJSONObject: json) else { return }
        
        // Your backend endpoint
        let url = URL(string: "https://YOUR-APP.base44.app/functions/verifyAppStorePurchase")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = jsonData
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        // Note: You need to handle auth token injection if your function requires "user" context from Base44
        // Or you can pass userId in body and handle it with service role in backend (less secure but easier for wrappers)
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data, error == nil else { return }
            
            // On success, notify WebView
            DispatchQueue.main.async {
                self.webView?.evaluateJavaScript("if(window.onPurchaseSuccess) window.onPurchaseSuccess();", completionHandler: nil)
            }
        }
        task.resume()
    }
}

3. Inject JavaScript Bridge

In your ViewController where you setup the WKWebView, add the script message handler.

import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let contentController = WKUserContentController()
        // Register the handler name 'purchaseSubscription'
        contentController.add(self, name: "purchaseSubscription")
        
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        
        webView = WKWebView(frame: view.bounds, configuration: config)
        PurchaseManager.shared.webView = webView
        // ... load your URL ...
    }
    
    // Handle calls from JavaScript
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "purchaseSubscription",
           let body = message.body as? [String: Any],
           let productId = body["productId"] as? String {
            
            // Trigger purchase
            PurchaseManager.shared.purchase(webProductId: productId)
        }
    }
}

4. Backend Configuration

Don't forget to set the APPLE_SHARED_SECRET in your Base44 App Secrets. You can get this from App Store Connect under the "Users and Access" or App-specific Shared Secret section.

Advertisement