A lightweight library for handling in-app purchases on iOS with a single API for StoreKit 1 and StoreKit 2.
- StoreKit 1 support for iOS 13-14
- StoreKit 2 support for iOS 15+
- Product fetching
- Purchase & Restore purchases
- Refresh and reading receipt
- iOS 13.0+
- Swift 5.9+
- Xcode 15+
Add InAppMaster to your Package.swift:
dependencies: [
.package(url: "https://github.com/moslienko/InAppMaster.git", from: "1.0.0")
]Then add the product to your target dependencies:
.target(
name: "YourApp",
dependencies: [
.product(name: "InAppMaster", package: "InAppMaster")
]
)You can also add the package directly in Xcode via File -> Add Package Dependencies....
If needed, you can integrate the library manually by adding the Sources/InAppMaster folder to your project.
InAppService is the main entry point of the library. It automatically chooses the underlying implementation:
InAppLegacyServiceon iOS 13-14InAppModernServiceon iOS 15+
Recommended flow:
- Configure product identifiers once at app launch.
- Fetch products from the App Store.
- Start a purchase for a selected product.
- Restore purchases when the user requests it.
- Refresh and read the receipt when you need backend validation.
Configure the service before fetching products or starting a purchase:
import InAppMaster
let inAppService = InAppService.shared
inAppService.configure(productIdentifiers: [
"com.example.app.monthly",
"com.example.app.yearly",
"com.example.app.lifetime"
])A good place for this call is app startup, for example in App, AppDelegate, or your dependency container.
Use fetchProducts to request products from the App Store. The result contains [any InAppProductProtocol], so the same code works across StoreKit 1 and StoreKit 2.
import InAppMaster
InAppService.shared.fetchProducts { result in
switch result {
case .success(let products):
for product in products {
print(product.id)
print(product.title)
print(product.descr)
print(product.priceString)
}
case .failure(let error):
print("Fetch failed:", error)
}
}Important: call fetchProducts before purchaseProduct(id:).
import InAppMaster
InAppService.shared.purchaseProduct(id: "com.example.app.monthly") { result in
switch result {
case .success(let productID):
print("Purchased:", productID)
// Unlock content or trigger backend validation here.
case .failure(let error):
print("Purchase failed:", error)
}
}import InAppMaster
InAppService.shared.restoreProducts { result in
switch result {
case .success(let restoredIDs):
print("Restored:", restoredIDs)
// Re-enable premium access based on restored identifiers.
case .failure(let error):
print("Restore failed:", error)
}
}Recommended approach:
- iOS 13-14: use
InAppRefreshReceiptService+getReceiptData() - iOS 15+: prefer
getCurrentActiveSubscriptionJWS()for StoreKit 2 based backend flows - iOS 15+:
getReceiptData()still works if you need the classic App Store receipt
If your backend validates the App Store receipt, refresh it first and then read receipt data from the bundle.
import InAppMaster
Task {
do {
try await InAppRefreshReceiptService.shared.refreshReceipt()
switch InAppService.shared.getReceiptData() {
case .success(let receiptData):
let base64Receipt = receiptData.base64EncodedString()
case .failure(let error):
print("Receipt read failed:", error)
}
} catch {
print("Receipt refresh failed:", error)
}
}Use this helper if your backend validates StoreKit 2 transaction JWS instead of the classic app receipt:
import InAppMaster
if #available(iOS 15.0, *) {
Task { @MainActor in
let jws = await InAppService.shared.modernService.getCurrentActiveSubscriptionJWS()
}
}StoreKit 2 can deliver verified transactions outside the direct purchase call:
import InAppMaster
if #available(iOS 15.0, *) {
Task { @MainActor in
InAppService.shared.modernService.transactionDidUpdate = { productID in
print("Transaction updated:", productID)
// Update local access state or trigger backend revalidation.
}
}
}The library may return InAppStoreKitError values such as:
productsListEmptyproductsNotFetchedunknownProductIdpendinguserCancelleduserCantMakePaymentsreceiptNotFoundNeedRefreshfailedRestoreunknown
InAppMaster
Copyright (c) 2026 Pavel Moslienko 8676976+moslienko@users.noreply.github.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
