Progressive Web Applications on iOS typically have restricted access to Apple’s exclusive APIs compared to native apps. These limitations extend to user reachability, purchases, and authentication features. Even after the introduction of the Web Push API to Safari’s PWA, there continues to be a limitation in access for Store PWAs. This restriction also applies to In-App Purchases (which is the opposite for Android and Windows)
For those new to PWABuilder, let me give a quick primer. The PWABuilder service can package Progressive Web Apps for various platforms, including the Apple App Store. It achieves this by automatically generating a Swift project-wrapper, allowing PWA developers to target the App Store without having to touch native iOS code. However, this approach has certain limitations. Notably, it does not natively support the integration of In-App Purchases.
This gap in functionality comes with extra coding and configurations, which can be quite a challenge especially for those of you who, like me, are PWA developers with limited Swift experience. But with challenges come opportunities, and this is where it gets interesting. Let’s see how we can enable in-app purchases in our PWA.
After active iterations with ChatGPT and reading related to Store Kit articles [Mastering Store Kit, In-App Purchase with StoreKit2, Shotty Birds Repository, Documentation], we eventually made headway.
We defined and set up the correct webkit message handlers and functions in Swift with each one assigned a specific task - be it kicking off a purchase, retrieving info on products, or requesting transactions.
// Fetch products list by id's from WebView
func fetchProducts(productIDs: [String] = StoreKitAPI.IntentsProducts) async {
do {
self.products = try await Product.products(for: productIDs)
// Convert each product representation (Data) to JSON String
let productJSONStrings: [String] = self.products.compactMap { product in
guard let jsonString = String(data: product.jsonRepresentation, encoding: .utf8) else {
return nil
}
return jsonString
}
self.productsJson = "[\(productJSONStrings.joined(separator: ","))]"
returnProductsResult(jsonString: self.productsJson)
} catch {
self.products = []
// handle error
}
}
// push results to webview
func returnProductsResult(jsonString: String){
DispatchQueue.main.async(execute: {
PWAShell.webView.evaluateJavaScript("this.dispatchEvent(new CustomEvent('iap-products-result', { detail: '\(jsonString)' }))")
})
}
Following the Swift implementation, the next step was to configure the front end of the demo PWA to handle these new events. We set up JavaScript event listeners to respond to messages from the Swift class, thus creating a smooth relay between Swift and JavaScript.
productsRequest() {
if (this.iOSIAPCapability)
window.webkit.messageHandlers['iap-products-request'].postMessage([
'demo_product_id',
'demo_product2_id',
'demo_subscription',
'demo_subscription_auto'
]);
}
window.addEventListener('iap-products-result', (event: CustomEvent) => {
if (event && event.detail) {
this.logMessage(event.detail, true);
this.products = JSON.parse(event.detail);
}
});
The output until now is promising. We’ve managed to make a prototype that starts and manages In-App purchase processes within Store PWAs on Apple devices. However, we’ve only just started.
For you to follow along:
- MacOS with a fresh Xcode is required for this project (sadly)
- Use this repository with “iap” branch as your base. It’s pre-configured as a demo of IAP.
- Follow readme instructions about the CocoaPods installation, but skip the firebase setup section
- Also check this instruction about the building and publishing xcode project.
- Check the documentation about StoreKit testing, and update StoreKit test file for your needs
- Native logic is in IAP.swift file
- The source code for web demo component of you can find here
- Feel free to create issue here or reach us in Discord for any questions
Your feedback is greatly valued. With this feature’s success, it will bring us substantial leap forward, not only enhancing the Store PWA experience but also advancing the PWA community as a whole.