StoreKit Configuration File Demo
This is a demo Swift app to show how a StoreKit Configuration File can be used to with RevenueCat and StoreKit to test in-app purchases and subscriptions without needing to use App Store Connect.
StoreKit Config File
This is already configured in this project but these were the steps take to create and use the StoreKit Configuration File.
Step 1: Create file
Creating a "StoreKit Configuration File" will prompt you with a checkbox for "Sync this file with an app in App Store Connect". This is new in Xcode 14 but was not enabled for this project. This project is meant to run without needing to do anything on App Store Connect.
Syncing with App Store Connect is a great feature if/once you already have products configured and you don't want to manage your live products (in App Store Connect) and test products (in a StoreKit Configuration File) separately.
Step 2: Add products
The StoreKit Configuration File allows you to create any number of:
- Consumable in-app purchases
- Non-consumable in-app purchases
- Non-renewing subscriptions
- Auto-renewable subscriptions
This project only added an lifetime non-consumable and two auto-renewable subscriptions.
Step 3: Enable in scheme
The Xcode scheme needs to be configured to load products from the StoreKit Configuration File. This can be done by selecting your file in the Run > StoreKit Configuration
.
Step 4: Running and debugging
Now the app is ready to load products and make test purchases using the StoreKit Configuration File and Xcode.
Debug > StoreKit > Manage Transactions
Demo
The demo app is contains a separate view for each implementation:
Both work with the StoreKit Configuration File
RevenueCat
struct RevenueCatView: View {
@State private var offering: Offering?
var body: some View {
VStack(spacing: 10) {
Text("With RevenueCat")
.padding()
if let offering = self.offering {
ForEach(offering.availablePackages) { package in
Button {
Task {
try await Purchases.shared.purchase(package: package)
}
} label: {
Text(package.storeProduct.localizedTitle)
}
}
}
}
.padding()
.task {
await self.loadSKProducts()
}
}
private func loadSKProducts() async {
// This should go in App but here because demo
Purchases.configure(withAPIKey: "your_api_key")
do {
self.offering = try await Purchases.shared.offerings().current
} catch {
print(error)
}
}
}
StoreKit 2
struct StoreKitView: View {
@State private var products: [Product] = []
var body: some View {
VStack(spacing: 10) {
Text("With StoreKit 2")
.padding()
ForEach(self.products) { product in
Button {
Task {
try await product.purchase()
}
} label: {
Text(product.displayName)
}
}
}
.padding()
.task {
await self.loadSKProducts()
}
}
private func loadSKProducts() async {
let productIds = ["com.revenuecat.pro.monthly",
"com.revenuecat.pro.yearly",
"com.revenuecat.pro.lifetime"]
do {
self.products = try await Product.products(for: productIds)
} catch {
print(error)
}
}
}