CashSDKCommerce is in the cashsdk-ios Swift package. Use 2.6.0 or later.
Add https://github.com/CashSDK/cashsdk-ios.git in Xcode and select the
CashSDKCommerce product. It runs on macOS 12 or later and needs Swift 5.9 or
later.
macOS with Stripe
Use your own Stripe account in a directly distributed native Mac app with Swift customer sessions, checkout, billing management and entitlements.
For a directly distributed Mac app, select macOS · Stripe in CashSDK and use
CashSDKCommerce. This is a native Swift actor using URLSession, with no StoreKit
purchase flow or embedded provider credentials. For Mac App Store distribution,
use the StoreKit SDK.
Connect your Stripe account#
In Marketplace, connect your own Stripe API key and webhook signing secret. Import your Stripe prices and map them to entitlements, then put the products in an offering. Payments use your Stripe account. App Store Connect is not required for this platform.
In Your App → Web and Stripe, tick Allow browser sessions and allow the exact HTTPS origin of your hosted checkout return page. Configure Sandbox and Production separately. Loopback HTTP is permitted only for sandbox development.
Your authenticated backend issues a customer session with cashsdk-node:
const session = await cashsdk.createWebSession({
customerId: authenticatedUser.id,
origin: "https://your-app.example",
scopes: [
"customer:read", "entitlements:read", "subscriptions:read", "offerings:read",
"checkout:create", "checkout:read", "billing:manage",
],
});
The backend determines the customer. Return session with Cache-Control: no-store.
Use your existing app sign-in to authenticate this endpoint. Keep the CashSDK server
key and Stripe credentials on servers. cashsdk is a CashSDKServer holding a
restricted key with Create customer web sessions. Minting is rate limited, so
the app reuses a session until it expires.
Create the native client#
import CashSDKCommerce
let client = try CashSDKCommerceClient(
publishableKey: configuration.publishableKey,
environment: .sandbox,
customerId: authenticatedUser.id,
origin: "https://your-app.example",
getSessionToken: { try await backend.cashSDKSession() }
)
let access = try await client.getEntitlements()
let hasPro = access.isActive("pro")
let offerings = try await client.getOfferings()
backend.cashSDKSession() returns CommerceSessionToken, decoded from the backend
response. The SDK shares concurrent token requests and renews expired or rejected
sessions. Timeouts and cancellation bound authentication. Use one client per signed-in
customer. Call logout() and create a new instance when the account changes.
A stored entitlement snapshot checks expiry on every isActive call. Refresh access
when your app becomes active, and protect server resources with backend entitlement
checks too. Stripe and StoreKit entitlements can belong to the same customer ID.
Open Checkout in the system browser#
import AppKit
let checkout = try await client.checkout(
CommerceCheckoutInput(
packageId: selectedPackage.id,
successURL: configuration.returnURL,
cancelURL: configuration.returnURL
),
idempotencyKey: savedAttempt.key
)
if let url = try checkout.redirectURL() {
await MainActor.run { _ = NSWorkspace.shared.open(url) }
}
Persist the attempt key before calling checkout, and persist its ID after the response. Retry an uncertain request with the same key. A network error does not establish that no payment happened. The client never automatically retries a network-failed write.
Both return URLs must use the configured origin. Use a hosted return page or a
universal link you own. Refresh when the app regains focus; do not grant access from
URL parameters. waitForCheckout returns the last status when its wait expires,
and waitForEntitlement("pro") returns an access snapshot that you must check.
Checkout sells your Stripe subscriptions and one-time products, applies free trials
from your intro offers, and takes cards. A replayed idempotency key returns the
original checkout. When a checkout is refused, act on CommerceError.code:
already_subscribed opens the billing portal, already_owned shows the product as
owned, already_purchased waits for access with waitForEntitlement, and
checkout_in_progress retries shortly. A Stripe Checkout your own server creates
also works, through signed webhooks. Both are in
Getting paid on the web.
redirectURL() returns Stripe's page on Stripe's own host, or on your Stripe account's custom
domain under Stripe's paths. Custom domains need CashSDK 2.6.0 or later.
Manage billing and sign out#
let portal = try await client.openBillingPortal(returnURL: configuration.returnURL)
let url = try portal.redirectURL()
await MainActor.run { _ = NSWorkspace.shared.open(url) }
// At account sign-out:
try await client.logout()
Enable the Stripe customer portal for your account and environment. The portal is
owner-checked by the API and Stripe connector. It can manage the payment methods,
cancellation and plan changes you enable in Stripe. Your backend should call
revokeWebSessions(user.id) to end this customer's sessions on all devices.
Run the example#
From the Swift package directory:
swift run --package-path Examples/StripeMac
The SwiftUI app accepts your publishable key, API URL, authenticated backend endpoint, merchant session token and hosted return URL. It opens Stripe in the system browser, persists checkout attempts across relaunch, refreshes access when focused, and offers billing management and sign-out. For an App Sandbox target, enable outgoing network connections. The example is not signed or notarized; your own app needs both before you distribute it.
Go live#
Before you ship, follow Go live with Stripe: the Production Stripe
connection, live prices in your offering, your production return origin, a live
server key, environment: .production in the client, and a first live purchase and
refund.
References: Apple URLSession and Stripe Checkout.