Skip to content

0.1.0

Choose a tag to compare

@Kosikowski Kosikowski released this 19 Sep 00:15
· 11 commits to main since this release

One-time purchases and trials over StoreKit 2, for macOS 26 and iOS 26, with a simulated store for testing them.

Early. Non-consumables and trials only. No subscriptions, no consumables. The API may still move before 1.0.

.package(url: "https://github.com/Kosikowski/swift-storekit.git", from: "0.1.0")

What is in it

Product Link it into
PurchaseCore the app All the logic. Foundation and Observation only: no StoreKit, no SwiftUI.
PurchaseStoreKit the app AppStoreFront: the App Store behind Core's protocols.
PurchaseUI the app .purchaseStore(_:), PurchaseButton, RestorePurchasesButton. No paywall.
PurchaseTestKit, PurchaseDebugUI the app A simulated store, scenarios, a debug panel. DEBUG only, whole: in a release build both modules are empty.
PurchaseTestSupport test targets only ManualClock, waitUntil, a .storekit validator.
PurchaseDirectDistribution a build sold outside the App Store EverythingOwnedStoreFront.

What it gets right, so that your app does not have to

Each of these was found in shipping code or measured against real StoreKit, and has a test:

  • Ownership and prices are read in a task nothing can cancel. Real StoreKit answers a cancelled task with nothing owned and an empty product list — not errors — and SwiftUI cancels .task whenever a view goes away.
  • A purchase unlocks at once, though StoreKit may list it a second after purchase() returns.
  • "Not answered yet" is a state (unknown is not none), and ownership never waits for prices.
  • A purchase that does not verify — or a result StoreKit adds later — is a failure that gets said, never a cancellation.
  • A trial is a free non-consumable dated by the App Store: the same trial on every device, and it ends by itself.
  • An app using the package links in Release. (With Xcode 27, a package's public function returning some View that ends in .task does not; see decision D25.)

Testing

swift test runs everything that decides anything, offline. make check adds the layer check, a release test run, the iOS build, builds of the Demo, and swift package release-check, which proves the simulated store is absent from a release build — of the package, and of a built app. The real adapter runs against real StoreKit from a hosted test bundle, on the Mac and in an iOS simulator, nightly. What StoreKit was measured to do, per OS and per Xcode, is in docs/05-testing.md, docs/10-decisions.md and spike/README.md.

Start with getting started; if you have hand-written StoreKit 2 code already, migrating an existing app.