> This page is for For Developers.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://whop.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://whop.ferndocs.com/_mcp/server.

# User Management

> Handle login, logout, and membership checking

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:

```swift
struct ContentView: View {
    @Environment(Checkout.self) var checkout

    var body: some View {
        if checkout.isSubscribed {
            PremiumContentView()
        } else {
            PaywallView()
        }
    }
}
```

#### Property Reference: isSubscribed

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

```swift
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()
            }
        }
    }
}
```

#### Method Reference: hasAccess(to:)

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

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

#### Property Reference: CheckoutMembership

\| Property | Type | Description | |----------|------|-------------| | `id` |
`String` | Membership ID | | `productId` | `String` | Associated product ID |
\| `status` | `Status` | `.active`, `.cancelled`, `.expired`, etc. | |
`isActive` | `Bool` | Whether the membership grants access | | `expiresAt` |
`Date?` | When the membership expires | | `createdAt` | `Date` | When 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:

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

#### Device memberships are claimed

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

#### User memberships are loaded

Existing memberships from other devices are synced to this device.

#### Views update automatically

Since Checkout is `@Observable`, any views using `isSubscribed` or
`memberships` refresh automatically.

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

#### Method Reference: logIn()

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

```swift
func handleLogout() {
    checkout.logOut()
    // User is now logged out
    // isSubscribed checks device-level memberships only
}
```

> **Tip**
>
> 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.

#### Method Reference: logOut()

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

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

| Scenario               | Behavior                                           |
| ---------------------- | -------------------------------------------------- |
| Purchase without login | Membership tied to device ID                       |
| Login after purchase   | Membership claimed by user account                 |
| Same user, new device  | Memberships sync after login                       |
| Logout                 | Device memberships remain; user memberships hidden |

## Device ID

The SDK automatically manages a unique device identifier stored in the iOS Keychain:

```swift
// Access the device ID if needed
let deviceId = checkout.deviceId
```

## Checking Current User

Access the currently logged-in user ID:

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

#### What if a user purchases on two devices before logging in?

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.

#### What if the user already has an active subscription when logging in?

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.

#### Can I migrate existing users to WhopCheckout?

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.

#### What happens if the user ID changes?

Avoid changing user IDs. If you must, the old user retains their memberships
and the new user ID starts fresh.

#### How do I handle family sharing or multiple accounts per device?

Call `logOut()` when switching accounts, then `logIn(appUserId:)` with the new user. Each user's memberships load independently.

## Quick Reference

| Method/Property     | Purpose                                |
| ------------------- | -------------------------------------- |
| `isSubscribed`      | Check access to any configured product |
| `hasAccess(to:)`    | Check access to specific product       |
| `memberships`       | Array of active memberships            |
| `logIn(appUserId:)` | Associate user with device purchases   |
| `logOut()`          | Clear user session                     |
| `appUserId`         | Current logged-in user ID (or `nil`)   |
| `deviceId`          | Unique device identifier               |

## Next Steps

#### [Setup](/developer/guides/ios/checkout/setup)

Review SDK configuration

#### [Purchasing](/developer/guides/ios/checkout/purchasing)

Implement the checkout flow