← 返回首页
GitHub - jaywcjlove/StoreKitHelper: A lightweight StoreKit2 wrapper designed specifically for SwiftUI, making it easier to implement in-app purchases. · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

jaywcjlove/StoreKitHelper

Go to file
Code

Repository files navigation

More items
Using my apps is also a way to support me:

StoreKit Helper

中文

A lightweight StoreKit2 wrapper designed specifically for SwiftUI, making in-app purchases implementation simpler and more intuitive.

Documentation

Please refer to the detailed StoreKitHelper documentation in DevTutor, which includes multiple quick start examples, custom payment interface examples, and API references, providing comprehensive examples and guidance.

Features

  • 🚀 SwiftUI Native: Designed specifically for SwiftUI with @ObservableObject and @EnvironmentObject support
  • 💡 Simple API: Clean and intuitive interface for managing in-app purchases
  • 🔄 Automatic Updates: Real-time transaction monitoring and status updates
  • 🎟️ Offer Code Redemption: Built-in support for Apple's system offer code redemption sheet
  • Type Safe: Protocol-based product definitions with compile-time safety
  • 🧪 Testable: Fully testable architecture with comprehensive test case coverage ExampleTests.swift/StoreKitHelperTests.swift

Usage

Create and inject a StoreContext instance at your SwiftUI app's entry point, which is responsible for loading the product list and tracking purchase status.

import StoreKitHelper enum AppProduct: String, InAppProduct { case lifetime = "focuscursor.lifetime" case monthly = "focuscursor.monthly" var id: String { rawValue } } @main struct DevTutorApp: App { @StateObject var store = StoreContext(products: AppProduct.allCases) var body: some Scene { WindowGroup { ContentView().environmentObject(store) } } }

StoreContext exposes a tri-state purchase status. At app launch, purchaseStatus starts as .loading until Transaction.currentEntitlements finishes its first sync. During this stage, hasPurchased and hasNotPurchased both return false, so startup code will not accidentally treat the user as "not purchased".

switch store.purchaseStatus { case .loading: // Purchase state is still being resolved case .purchased: // ✅ User has active purchases case .notPurchased: // 🧾 User has no active purchases }

Recommended usage:

@EnvironmentObject var store: StoreContext var body: some View { switch store.purchaseStatus { case .loading: ProgressView("Checking purchases...") case .purchased: // ✅ User has purchased - show full functionality case .notPurchased: // 🧾 User hasn't purchased - show limited content or purchase prompt } }

The default purchase call remains unchanged:

await store.purchase(product)

If you need StoreKit purchase options, you can now pass them through directly:

await store.purchase(product, options: [ .appAccountToken(appAccountToken) ])

Compatible legacy usage:

@EnvironmentObject var store: StoreContext var body: some View { if store.hasResolvedPurchaseStatus == false { ProgressView("Checking purchases...") } else if store.hasNotPurchased == true { // 🧾 User hasn't purchased - show limited content or purchase prompt } else if store.hasPurchased == true { // ✅ User has purchased - show full functionality } }

Offer Code Redemption

StoreKitHelperView and StoreKitHelperSelectionView include a "Redeem Offer Code" entry on supported platforms. The entry is shown on iOS 16+, macOS 15+, and visionOS 1+. It is hidden on unsupported platforms, so apps with a macOS 14 minimum deployment target continue to build and run without showing the redemption UI.

You can also present Apple's system offer code redemption sheet manually:

@EnvironmentObject var store: StoreContext Button("Redeem Offer Code") { store.presentOfferCodeRedemption() }

After redemption completes, StoreContext refreshes current entitlements and updates purchasedProductIDs / purchaseStatus.

Localization

If you need to read localization strings from StoreKitHelper outside the package, use StoreKitHelperL18n.localized(...).

This API resolves strings from the package resource bundle and avoids symbol conflicts with custom String extensions in the host app.

import StoreKitHelper let locale = Locale(identifier: Locale.preferredLanguages.first ?? "en") let title = StoreKitHelperL18n.localized( key: "purchase_failed", locale: locale )

StoreKitHelperView

Use StoreKitHelperView to directly display in-app purchase popup views and configure various parameters through a chainable API.

struct PurchaseContent: View { @EnvironmentObject var store: StoreContext var body: some View { let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en") StoreKitHelperView() .environment(\.locale, .init(identifier: locale.identifier)) .environment(\.pricingContent, { AnyView(PricingContent()) }) .environment(\.popupDismissHandle, { // Triggered when the popup is dismissed // (e.g., user clicks the close button) store.isShowingPurchasePopup = false }) .environment(\.termsOfServiceHandle, { // Action triggered when the [Terms of Service] button is clicked }) .environment(\.privacyPolicyHandle, { // Action triggered when the [Privacy Policy] button is clicked }) .frame(maxWidth: 300) .frame(minWidth: 260) } }

Click to open the paid product list interface.

struct ContentView: View { @EnvironmentObject var store: StoreContext var body: some View { if store.hasNotPurchased == true, store.isLoading == false { PurchasePopupButton() .sheet(isPresented: $store.isShowingPurchasePopup) { PurchaseContent() } } } }

StoreKitHelperSelectionView

Similar to StoreKitHelperView, but for selecting purchase items to make payments.

struct PurchaseContent: View { @EnvironmentObject var store: StoreContext var body: some View { let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en") StoreKitHelperSelectionView() .environment(\.locale, .init(identifier: locale.identifier)) .environment(\.pricingContent, { AnyView(PricingContent()) }) .environment(\.popupDismissHandle, { // Triggered when the popup is dismissed // (e.g., user clicks the close button) store.isShowingPurchasePopup = false }) .environment(\.termsOfServiceHandle, { // Action triggered when the [Terms of Service] button is clicked }) .environment(\.privacyPolicyHandle, { // Action triggered when the [Privacy Policy] button is clicked }) .frame(maxWidth: 300) .frame(minWidth: 260) } }

API Reference

InAppProduct Protocol

protocol InAppProduct: CaseIterable { var id: String { get } }

PurchaseStatus

enum PurchaseStatus { case loading case purchased case notPurchased }

StoreContext Properties

  • products: [Product] - Available products from the App Store
  • purchasedProductIDs: Set<String> - Set of purchased product identifiers
  • purchaseStatus: PurchaseStatus - Current purchase state: .loading, .purchased, or .notPurchased
  • hasResolvedPurchaseStatus: Bool - Whether the initial purchase state sync has completed
  • hasNotPurchased: Bool - Whether the user hasn't purchased any products after purchase state resolution
  • hasPurchased: Bool - Whether the user has purchased any products after purchase state resolution
  • isLoading: Bool - Whether products are currently loading
  • isShowingOfferCodeRedemption: Bool - Whether the system offer code redemption sheet is being presented
  • errorMessage: String? - Current error message, if any

StoreContext Methods

  • purchase(_ product: Product, options: Set<Product.PurchaseOption> = []) - Purchase a specific product, optionally forwarding StoreKit purchase options
  • restorePurchases() - Restore previous purchases
  • presentOfferCodeRedemption() - Present Apple's system offer code redemption sheet on supported platforms
  • refreshPurchasedProducts() - Refresh current entitlements and purchase state
  • isPurchased(_ productID: ProductID) -> Bool - Check if a product is purchased by ID
  • isPurchased(_ product: InAppProduct) -> Bool - Check if a product is purchased
  • product(for productID: ProductID) -> Product? - Get product by ID
  • product(for product: InAppProduct) -> Product? - Get product by InAppProduct

License

Licensed under the MIT License.

About

A lightweight StoreKit2 wrapper designed specifically for SwiftUI, making it easier to implement in-app purchases.

Topics

Resources

License

Stars

262 stars

Watchers

2 watching

Forks

Sponsor this project

 
Sponsor

Packages

 
 
 

Contributors

Languages

Footer

© 2026 GitHub, Inc.