Skip to main content
The WhopCheckout SDK tracks memberships at both the device level and user level. This allows purchases before login while syncing across devices once users authenticate.

Checking Membership Status

Check Any Subscription

Use isSubscribed to check if the user has access to any of your configured products:
struct ContentView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        if checkout.isSubscribed {
            PremiumContentView()
        } else {
            PaywallView()
        }
    }
}
var isSubscribed: Bool { get }
Returns: true if the user has at least one active membership or StoreKit entitlement.

Check Specific Product

Use hasAccess(to:) when you have multiple products with different access levels:
struct FeatureView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        VStack {
            // Basic features - check basic product
            if checkout.hasAccess(to: "prod_basic") {
                BasicFeaturesView()
            }

            // Pro features - check pro product
            if checkout.hasAccess(to: "prod_pro") {
                ProFeaturesView()
            }
        }
    }
}
func hasAccess(to productId: String) -> Bool
Parameters:
  • productId: The product ID to check (starts with prod_)
Returns: true if the user has an active membership for that specific product.

Accessing Memberships Directly

For more granular control, access the memberships array:
struct MembershipsView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        List(checkout.memberships) { membership in
            VStack(alignment: .leading) {
                Text(membership.productId)
                    .font(.headline)
                Text("Status: \(membership.status.rawValue)")
                    .foregroundStyle(.secondary)
                if let expiresAt = membership.expiresAt {
                    Text("Expires: \(expiresAt.formatted())")
                        .font(.caption)
                }
            }
        }
    }
}
PropertyTypeDescription
idStringMembership ID
productIdStringAssociated product ID
statusStatus.active, .cancelled, .expired, etc.
isActiveBoolWhether the membership grants access
expiresAtDate?When the membership expires
createdAtDateWhen the membership was created

User Login

When a user logs in to your app, call logIn() to associate their account with any device-level purchases:
func handleLogin(userId: String) async {
    do {
        try await checkout.logIn(appUserId: userId)
        // Memberships now synced to this user
    } catch {
        print("Login failed: \(error)")
    }
}

What Happens on Login

1

Device memberships are claimed

Any purchases made before login are associated with the user’s account.
2

User memberships are loaded

Existing memberships from other devices are synced to this device.
3

Views update automatically

Since Checkout is @Observable, any views using isSubscribed or memberships refresh automatically.
struct LoginView: View {
    @Environment(Checkout.self) var checkout
    @State private var isLoggingIn = false

    var body: some View {
        Button("Log In") {
            Task {
                isLoggingIn = true
                defer { isLoggingIn = false }

                // Your app's login flow
                let userId = try await performYourLogin()

                // Tell WhopCheckout about the logged-in user
                try await checkout.logIn(appUserId: userId)
            }
        }
        .disabled(isLoggingIn)
    }
}
func logIn(appUserId: String) async throws
Parameters:
  • appUserId: Your app’s unique identifier for this user. Should not change for the same user.
Note: User IDs should be stable. Don’t use session tokens or values that change on each login.

User Logout

When users log out, call logOut() to clear user-specific data:
func handleLogout() {
    checkout.logOut()
    // User is now logged out
    // isSubscribed checks device-level memberships only
}
After logout, isSubscribed still returns true if there are device-level memberships that haven’t been claimed by any user. This allows “guest” purchases to persist.
func logOut()
Clears the current user session. Device-level memberships remain accessible.

Guest Purchases

Users can purchase without logging in. The SDK tracks these purchases by device:
struct GuestPurchaseView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        VStack {
            if checkout.isSubscribed {
                Text("You have premium access!")

                if checkout.appUserId == nil {
                    // Prompt to create account
                    Text("Create an account to sync across devices")
                    Button("Sign Up") {
                        // Your signup flow
                    }
                }
            } else {
                Button("Purchase") {
                    Task {
                        try? await checkout.purchase("plan_xxxxxxxxxxxxxx")
                    }
                }
            }
        }
    }
}

How Guest Purchases Work

ScenarioBehavior
Purchase without loginMembership tied to device ID
Login after purchaseMembership claimed by user account
Same user, new deviceMemberships sync after login
LogoutDevice memberships remain; user memberships hidden

Device ID

The SDK automatically manages a unique device identifier stored in the iOS Keychain:
// Access the device ID if needed
let deviceId = checkout.deviceId

Checking Current User

Access the currently logged-in user ID:
struct ProfileView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        if let userId = checkout.appUserId {
            Text("Logged in as: \(userId)")
            Button("Log Out") {
                checkout.logOut()
            }
        } else {
            Text("Not logged in")
            Button("Log In") {
                // Your login flow
            }
        }
    }
}

FAQ

When they log in on either device, both device-level memberships are claimed by their account. The next time they log in on the other device, all memberships sync.
When a user logs in with logIn(appUserId:), all device memberships are added to their account. If the user already has existing memberships from another device or account, they keep access to all of them.Conflict resolution:
  • Same product, different plans: The user retains access from both. When one expires, the other continues.
  • Same product, same plan: Both memberships remain active. Whop does not cancel duplicates automatically—this is intentional so users don’t lose access they paid for.
If you need to handle duplicate subscriptions, you can check checkout.memberships and guide users to manage their subscriptions via the billing portal.
Yes. When users log in with logIn(appUserId:), use the same user ID you have in your existing system. Their memberships (if purchased through Whop) will sync automatically.
Avoid changing user IDs. If you must, the old user retains their memberships and the new user ID starts fresh.
Call logOut() when switching accounts, then logIn(appUserId:) with the new user. Each user’s memberships load independently.

Quick Reference

Method/PropertyPurpose
isSubscribedCheck access to any configured product
hasAccess(to:)Check access to specific product
membershipsArray of active memberships
logIn(appUserId:)Associate user with device purchases
logOut()Clear user session
appUserIdCurrent logged-in user ID (or nil)
deviceIdUnique device identifier

Next Steps