Built on StoreKit 2. Native macOS needs macOS 14 or later. Mac Catalyst builds use the package's iOS platform, so they follow your iOS deployment target (iOS 15 or later).
macOS / Catalyst
Sell in-app purchases and subscriptions on the Mac App Store, in native macOS and Mac Catalyst apps, with Swift and StoreKit 2.
The macOS SDK is the iOS SDK. The same cashsdk-ios Swift package, the same
import CashSDK and the same async/await API run in native macOS apps and in
Mac Catalyst builds of your iPad app. Purchases go through the Mac App Store
and are verified by CashSDK on the server.
For apps distributed directly with Stripe, use macOS with Stripe.
Pick your Mac setup#
How you set up CashSDK depends on how your Mac app is listed in App Store Connect.
| Your Mac app | Bundle ID | App Store Connect record | CashSDK setup |
|---|---|---|---|
| Universal purchase | Same as the iOS app | Same as the iOS app | Tick macOS as a platform. Nothing else changes. |
| Separate native macOS app | Its own | Its own, with its own Apple ID | Add the Mac bundle ID and Mac Apple App ID, sync its products, point its notifications at CashSDK. |
| Mac Catalyst, Xcode default | maccatalyst.<iOS bundle ID> | Its own | Same as a separate native macOS app. |
| Mac Catalyst, "Use iOS Bundle Identifier" | Same as the iOS app | Same as the iOS app | Same as universal purchase. |
Universal purchase#
The Mac build shares the iOS bundle ID and the iOS app's App Store Connect record. Customers who buy on one device have the purchase on the other.
In the CashSDK dashboard, tick macOS as a platform when you create the app, or later in the Platforms step. That is all. Your products stay App Store products, the notification URL you already set covers the Mac build, and entitlements work the same way on both.
Separate Mac App Store record#
The Mac app has its own bundle ID and its own App Store Connect record, with its own numeric Apple ID. CashSDK keeps one app for both: the iOS record and the Mac record share access through entitlements.
When you create the app in the dashboard (or in the Platforms step), tick
macOS and enter the Mac bundle ID: the Mac app's own bundle ID, or
maccatalyst.<iOS bundle ID> for a Mac Catalyst build. You can change it later
in the app's Settings.
The Mac Apple App ID is the numeric Apple ID of the Mac app's App Store Connect record (App Information in App Store Connect). CashSDK finds it on its own when your App Store Connect API key can see that record. If it does not, enter it in the app's Settings.
Sandbox purchases from the Mac app verify without it. Production purchases do not, and while a Mac bundle ID is set the app cannot switch to Production until the Mac Apple App ID is filled in.
App Store Connect API keys and In-App Purchase keys belong to your team, not to one app. The key you already uploaded covers the Mac record too. Do not upload a second one.
Each CashSDK app has one App Store Server Notifications URL. Copy it from the Store Connect page in the dashboard. In App Store Connect, open the Mac app, then App Information → App Store Server Notifications. Paste the URL into both the Production Server URL and the Sandbox Server URL, and choose Version 2.
Create the Mac app's in-app purchases and subscriptions in its own App Store
Connect record. On the Store Connect page, the Mac App Store card syncs
that catalog. The products arrive under the store Mac App Store
(mac-app-store in the API).
App Store Connect product IDs are unique across your developer account, so the Mac products have their own product IDs. Map each one to the same entitlements as the matching iOS product, so access carries across devices.
When a separate Mac record is set up, the setup checklist adds three Mac checks: the Mac Apple App ID is set, the catalog is synced from the Mac record, and notifications are arriving from the Mac app. Universal purchase apps do not get these checks, because there is nothing extra to set up.
Mac Catalyst#
By default Xcode gives the Mac Catalyst build the bundle ID
maccatalyst.<iOS bundle ID>. That is a separate App Store Connect record, so
follow Separate Mac App Store record and enter
maccatalyst.<iOS bundle ID> as the Mac bundle ID.
If you tick Use iOS Bundle Identifier in the Mac Catalyst target in Xcode, the build shares the iOS bundle ID and record. That is a universal purchase: tick macOS and you are done.
Install#
The macOS SDK is the same Swift package as iOS. In Xcode, choose File → Add Package Dependencies… and enter the repository URL:
https://github.com/cashsdk/cashsdk-ios
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/cashsdk/cashsdk-ios.git", from: "2.6.0")
],
targets: [
.target(name: "YourMacApp", dependencies: [
.product(name: "CashSDK", package: "cashsdk-ios")
])
]
Enable the capability. In your macOS target's Signing & Capabilities, add the In-App Purchase capability. A Mac Catalyst build inherits it from your iOS target.
Configure#
Initialize CashSDK once, as early as possible: in your SwiftUI App init or in
applicationDidFinishLaunching. Use your publishable key. It is the same key
your iOS app ships.
import SwiftUI
import CashSDK
@main
struct MyApp: App {
init() {
CashSDK.configure(publishableKey: "csk_pk_…")
}
var body: some Scene {
WindowGroup { RootView() }
}
}
Only ship publishable keys (csk_pk_…) in your app. Secret keys
(csk_sk_…) belong on your backend.
Paywalls on the Mac#
From 2.3.0, register(placement:) presents the paywall you designed in the dashboard on
the Mac too, with the same placements, campaigns and events as on iPhone:
- Native macOS (AppKit or SwiftUI): a paywall set to open as a sheet attaches to your key
window. Otherwise, or when no window is open, it opens in its own window. Closing it,
buying or restoring ends it the same way as on iOS, and a gated
featureclosure runs once the customer has access. - Mac Catalyst: the paywall presents the same way it does on iPad. See Paywalls on the iOS page.
CashSDK.shared.register(placement: "upgrade") {
// Runs once the customer has access
openProFeature()
}
With 2.2.0 or earlier, a native Mac build shows no paywall: update the package to 2.3.0.
Your own purchase screen#
To sell from a screen you build yourself, load the StoreKit products with products(for:)
and pass the one the customer picks to purchase(_:):
import SwiftUI
import StoreKit
import CashSDK
struct PaywallScreen: View {
@State private var products: [Product] = []
var body: some View {
VStack {
ForEach(products, id: \.id) { product in
Button {
Task { _ = try? await CashSDK.shared.purchase(product) }
} label: {
HStack { Text(product.displayName); Spacer(); Text(product.displayPrice) }
}
}
Button("Restore purchases") {
Task { _ = try? await CashSDK.shared.restore() }
}
}
.task {
products = (try? await CashSDK.shared.products(for: ["app.example.pro.monthly",
"app.example.pro.yearly"])) ?? []
}
}
}
getPresentationResult(placement:) tells you whether a placement would show a paywall, so
you can show your own screen at the same moments.
Purchase#
Pass a product identifier to purchase. CashSDK runs the StoreKit 2 flow,
verifies the transaction on the server, and returns the customer's updated
entitlements.
switch try await CashSDK.shared.purchase("app.example.pro.yearly") {
case .success(let entitlements):
if entitlements.isActive("pro") { unlockPro() }
case .pending:
showPendingNotice()
case .userCancelled, .localStoreKit:
break
}
.localStoreKit means an Xcode StoreKit configuration file completed the
purchase. It never reaches CashSDK and grants no server-side access.
Check entitlements#
entitlements is a synchronous snapshot that works offline, so reading it never
waits on the network.
if CashSDK.shared.entitlements.isActive("pro") {
// Show premium content
} else {
// Show your paywall
}
Restore purchases#
Mac customers often move between Macs, so always show a Restore purchases control. It recovers access on a new Mac or after a reinstall.
try await CashSDK.shared.restore()
if CashSDK.shared.entitlements.isActive("pro") {
unlockPro()
}
Identify the customer#
Link the SDK to your own user ID so entitlements follow the customer across Macs,
iPhones and iPads. Call identify after the user signs in.
CashSDK.shared.identify(userId: "8841", userToken: tokenFromYourBackend)
Entitlements are keyed to the customer. A subscription bought in your iPhone app is active in your Mac app once the customer signs in there with the same user ID, and the other way round. With a separate Mac record, this works when the Mac products are mapped to the same entitlements as the iOS ones.
Coupons#
Coupons (App Store offer codes) work on the Mac for universal purchase apps, because the products are App Store products. Coupons for products in a separate Mac App Store record are not supported yet. See Coupons.
Coding agents#
The Magic Prompt and the setup snippets support macOS. With the platform macos,
they give Swift and StoreKit 2 code that uses your Mac bundle ID.
Troubleshooting#
Purchase refused with 403 bundle_id_mismatch
The purchase came from a bundle ID that is neither the app's Bundle ID nor
its Mac bundle ID. The error message names the bundle IDs CashSDK expected.
Most often this is a Mac Catalyst build with the default
maccatalyst.<iOS bundle ID>, or a native Mac app with its own bundle ID, and
no Mac bundle ID set in the app's Settings. Enter it there and try again.
Production purchase refused with 400 apple_app_id_required
A Production purchase from the separate Mac record needs the Mac Apple App ID. The error message says so. Enter the Mac app's numeric Apple ID in the app's Settings. Sandbox purchases do not need it.
No notifications from the Mac app
The Mac record has its own App Store Server Notifications settings. Paste the same CashSDK URL into its Production Server URL and Sandbox Server URL, with Version 2. Until the Mac bundle ID is set in the app's Settings, CashSDK answers the Mac record's notifications with a 403. Apple shows that as a failed delivery and retries. The Mac App Store card on the Store Connect page shows whether notifications are arriving from the Mac app.
A Mac purchase does not unlock access
Check that the Mac products synced under Mac App Store and that each one is mapped to the same entitlement as the matching iOS product.
From 2.3.0 the SDK reports its platform as macos in events and coupon calls on native
macOS and Mac Catalyst, so you can filter Mac customers in the dashboard. Earlier versions
report ios on every Apple platform.