StoreKit Updates
Overview
StoreKit is Apple's framework for implementing in-app purchases, subscriptions, and App Store interactions. Recent updates have introduced significant enhancements to the core framework, new SwiftUI views for merchandising subscriptions, and improved tools for testing and development.
Core Framework Updates
AppTransaction Updates (iOS 18.4+)
AppTransaction now includes two new fields:
appTransactionID: A globally unique identifier for each Apple Account that downloads your app
- Unique for each family group member for apps supporting Family Sharing
- Back-deployed to iOS 15
originalPlatform: Indicates the platform on which the customer originally purchased the app
- Values include iOS, macOS, tvOS, or visionOS
- Helps support business model changes and entitle customers appropriately
// Example of accessing AppTransaction properties
Task {
do {
let appTransaction = try await AppTransaction.shared
// Access the new properties
let transactionID = appTransaction.appTransactionID
let platform = appTransaction.originalPlatform
// Use these values for business logic
if platform == .iOS {
// Handle iOS-specific logic
}
} catch {
print("Failed to get app transaction: \(error)")
}
}Transaction Updates (iOS 18.4+)
The Transaction type represents a successful In-App Purchase and has been enhanced with:
New API:
Transaction.currentEntitlements(for:)replacesTransaction.currentEntitlement(for:)- Returns an asynchronous sequence of transactions entitling the customer to a given product
- Supports multiple entitlements through different means
New Fields:
appTransactionID: Links transactions to the app downloadofferPeriod: Details the subscription period associated with a redeemed offeradvancedCommerceInfo: Applies only to apps using the Advanced Commerce API
// Example of using the new currentEntitlements API
Task {
for await verificationResult in Transaction.currentEntitlements(for: "your.product.id") {
switch verificationResult {
case .verified(let transaction):
// Handle verified transaction
let appTransactionID = transaction.appTransactionID
if let offerPeriod = transaction.offerPeriod {
// Handle offer period information
}
case .unverified(let transaction, let verificationError):
// Handle unverified transaction
print("Verification failed: \(verificationError)")
}
}
}RenewalInfo Updates (iOS 18.4+)
The RenewalInfo type for auto-renewable subscriptions has been enhanced with:
- Enhanced API:
SubscriptionStatuscan now query subscription statuses using a Transaction ID - Four New Fields: Providing more comprehensive insights into subscription details
- Expiration Reasons: Valuable for understanding customer behavior and tailoring strategies
- Example: If a subscription expires due to a price increase, you can offer win-back promotions
// Example of accessing subscription status with a transaction ID
Task {
do {
let status = try await Product.SubscriptionInfo.Status(transactionID: "transaction_id_here")
// Access renewal info
if let renewalInfo = status.renewalInfo {
// Check expiration reason if applicable
if let expirationReason = renewalInfo.expirationReason {
switch expirationReason {
case .priceIncrease:
// Offer a win-back promotion
case .billingError:
// Prompt to update payment method
default:
// Handle other expiration reasons
}
}
}
} catch {
print("Failed to get subscription status: \(error)")
}
}StoreKit Views
SubscriptionOfferView
A new SwiftUI view for merchandising auto-renewable subscriptions:
// Basic usage with product ID
SubscriptionOfferView(productID: "your.subscription.id")
.prefersPromotionalIcon(true)
// Using a loaded product
SubscriptionOfferView(product: loadedSubscriptionProduct)
// With custom icon
SubscriptionOfferView(productID: "your.subscription.id") {
Image("custom_icon")
.resizable()
.frame(width: 40, height: 40)
}
// With placeholder icon while loading
SubscriptionOfferView(productID: "your.subscription.id") {
Image("custom_icon")
.resizable()
.frame(width: 40, height: 40)
} placeholderIcon: {
Image(systemName: "hourglass")
.resizable()
.frame(width: 40, height: 40)
}Configuring SubscriptionOfferView
Add detail action to direct customers to subscription store:
SubscriptionOfferView(productID: "your.subscription.id")
.subscriptionOfferViewDetailAction {
// Action when detail link is tapped
isShowingSubscriptionStore = true
}Displaying Different Plans Based on Customer Status
Configure which subscription plan to display using the visibleRelationship parameter:
// Using a subscription group ID
SubscriptionOfferView(groupID: "your.group.id", visibleRelationship: .upgrade)
// Available relationships:
// - .upgrade: Shows a plan one level higher than current
// - .downgrade: Shows a plan one level lower than current
// - .crossgrade: Shows equivalent tier plans with best value
// - .current: Shows customer's current plan
// - .all: Shows all plans in the groupTracking Subscription Status
Use the subscriptionStatusTask modifier to determine customer status:
@main
struct MyApp: App {
@State private var customerStatus: SubscriptionStatus = .unknown
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.customerSubscriptionStatus, customerStatus)
.subscriptionStatusTask(for: "your.group.id") { statuses in
// Translate StoreKit statuses to your app's model
if statuses.contains(where: { $0.state == .subscribed }) {
customerStatus = .subscribed
} else if statuses.contains(where: { $0.state == .expired }) {
customerStatus = .expired
} else {
customerStatus = .notSubscribed
}
}
}
}
}In-App Purchase Request Signing
StoreKit now requires JSON Web Signatures (JWS) for certain Purchase Option and View Modifier APIs:
- Setting customer eligibility for introductory offers
- Signing promotional offers
The App Store Server Library simplifies the JWS signing process:
- Retrieve your In-App Purchase signing key from App Store Connect
- Use the key with the App Store Server Library to create signed requests
// Example of using the App Store Server Library for signing
import AppStoreServerLibrary
// Create a signed JWS for a promotional offer
func createSignedOfferJWS(productID: String, offerID: String) async throws -> String {
let signingKey = try SigningKey(
privateKeyFilePath: "path/to/key.p8",
keyID: "YOUR_KEY_ID",
issuerID: "YOUR_ISSUER_ID"
)
let library = try AppStoreServerLibrary(
signingKey: signingKey,
environment: .production
)
return try library.createOfferSignature(
productIdentifier: productID,
subscriptionOfferID: offerID,
applicationUsername: nil,
nonce: UUID().uuidString,
keyIdentifier: "YOUR_KEY_ID",
timestamp: Int(Date().timeIntervalSince1970)
)
}Testing and Development
StoreKit Testing in Xcode
Create a local StoreKit configuration file to test In-App Purchases without App Store Connect setup:
- Select File > New > File From Template
- Search for "storekit" and select "StoreKit Configuration File"
- Name the file (e.g.,
LocalConfiguration.storekit) - Define products in the configuration file
Transaction Manager
Use the Transaction Manager window in Xcode to:
- Create and inspect transactions
- Modify transaction properties
- Test different purchase scenarios
Testing Subscription Offers
- Set up subscription offers in your local configuration file
- Implement the necessary JWS signing for offers
- Test different offer scenarios using the Transaction Manager
Advanced Commerce API
The Advanced Commerce API enables easier support for:
- In-App Purchases for large content catalogs
- Creator experiences
- Subscriptions with optional add-ons
This API is accessible through the new advancedCommerceInfo field in the Transaction model.