An open API service indexing awesome lists of open source software.

https://github.com/jaywcjlove/storekithelper

A lightweight StoreKit2 wrapper designed specifically for SwiftUI, making it easier to implement in-app purchases.
https://github.com/jaywcjlove/storekithelper

apple iap in-app-purchase ios ios-swift jaywcjlove macos payment purchase receipt storekit storekit2 swift swift-app swift-application swiftui swiftui-app swiftui-application

Last synced: about 2 months ago
JSON representation

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

Awesome Lists containing this project

README

          


Using my apps is also a way to support me:


Scap: Screenshot & Markup Edit
Screen Test
Deskmark
Keyzer
Vidwall Hub
VidCrop
Vidwall
Mousio Hint
Mousio
Musicer
Audioer
FileSentinel
FocusCursor
Videoer
KeyClicker
DayBar
Iconed
Menuist
Quick RSS
Quick RSS
Web Serve
Copybook Generator
DevTutor for SwiftUI
RegexMate
Time Passage
Iconize Folder
Textsound Saver
Create Custom Symbols
DevHub
Resume Revise
Palette Genius
Symbol Scribe


StoreKit Helper
===

[δΈ­ζ–‡](./README.zh.md)

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

![StoreKit Helper](https://github.com/user-attachments/assets/d0d27552-9d2d-4a09-8d8d-b96b3b3648a9)

## Documentation

Please refer to the detailed `StoreKitHelper` [documentation](https://github.com/jaywcjlove/devtutor) in [DevTutor](https://github.com/jaywcjlove/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
- βœ… **Type Safe**: Protocol-based product definitions with compile-time safety
- πŸ§ͺ **Testable**: Fully testable architecture with comprehensive test case coverage [ExampleTests.swift](https://github.com/jaywcjlove/StoreKitHelper/blob/main/Example/ExampleTests/ExampleTests.swift)/[StoreKitHelperTests.swift](https://github.com/jaywcjlove/StoreKitHelper/blob/main/Tests/StoreKitHelperTests/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.

```swift
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".

```swift
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:

```swift
@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:

```swift
await store.purchase(product)
```

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

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

Compatible legacy usage:

```swift
@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
}
}
```

## 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.

```swift
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.

```swift
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.

```swift
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.

```swift
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

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

### PurchaseStatus

```swift
enum PurchaseStatus {
case loading
case purchased
case notPurchased
}
```

### StoreContext Properties

- `products: [Product]` - Available products from the App Store
- `purchasedProductIDs: Set` - 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
- `errorMessage: String?` - Current error message, if any

### StoreContext Methods

- `purchase(_ product: Product, options: Set = [])` - Purchase a specific product, optionally forwarding StoreKit purchase options
- `restorePurchases()` - Restore previous purchases
- `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.