> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.qonversion.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Glassfy

Glassfy has [announced](https://glassfy.io/blog/important-update-glassfy-services-ending-december-2024/) that they will be ceasing operations in December 2024, and we're here to support the users during the transition. Enjoy[ 6 months of the Qonversion](https://qonversion.io/glassfy-alternative) completely for free, allowing you plenty of time to get comfortable with all that Qonversion has to offer.

<img src="https://mintcdn.com/qonversion/zdjn3kzNygqtFGgE/images/docs/a6feee1-6_months.png?fit=max&auto=format&n=zdjn3kzNygqtFGgE&q=85&s=725b906938cf9931c5a16b835bb8386e" alt="" width="2400" height="512" data-path="images/docs/a6feee1-6_months.png" />

To ensure a smooth transition, we have prepared a migration guide to help you move seamlessly from Glassfy to Qonversion.

First and foremost, [register](https://go.qonversion.io/zyUM) at Qonversion.

The registration will take you through an onboarding where you'll have to fill out information about your project, insert links - all to help you add your project to Qonversion. We highly recommend following this flow and not skipping any settings during registration. Anyway, we'll also cover the scenario if you don't fill these out.

## Store Configuration

<br />

### Connecting App Store

Here's what you had in Glassfy:

<img src="https://mintcdn.com/qonversion/-M3mkakSa01J8jKU/images/docs/5339994-glassfy_1.png?fit=max&auto=format&n=-M3mkakSa01J8jKU&q=85&s=f83732d7240d5b26367ae2e9a96306b0" alt="" width="1010" height="790" data-path="images/docs/5339994-glassfy_1.png" />

To configure App store in Qonversion, you will need the following fields:

1. **App-Specific Shared Secret**: Simply copy this value from the Apple Shared Secret field in the App Store Configuration section in Glassfy. Check the screenshot above. Alternatively, [here's](/docs/ios-app-store-info-fields#2-generate-the-app-specific-shared-secret) a more detailed guide on how to obtain this.
2. **App Store ID**: You'll find your App Store ID in the URL of your App Store Connect account. For example, if your URL is something like [https://apps.apple.com/us/app/example-app/id123456789](https://apps.apple.com/us/app/example-app/id123456789), then 123456789 is your app’s Apple ID.

### Connecting Google Play Console

Here's what you saw at Glassfy:

<img src="https://mintcdn.com/qonversion/bWelyT5Klw3olUpZ/images/docs/24c0236-image.png?fit=max&auto=format&n=bWelyT5Klw3olUpZ&q=85&s=b83e8f519c7554b0f6562d640b3abcea" alt="" width="2552" height="286" data-path="images/docs/24c0236-image.png" />

To set up your Google Play account in Qonversion, you will need the following:

1. **Android Package Name**: This is usually the applicationID in your app-level build.gradle file. You can either copy it from there or simply copy the **Android Package Name** field in your settings from Glassfy.
2. **Service Account Credentials JSON**: Copy the value from the **Google JSON Key** in the screenshot above or simply attach the same file you used in Glassfy. [Here's more](/docs/android-store-setup) on how you can get Google Play Service Account Key.

> 🚧 Can't find the fields?
>
> If you skipped filling in these details during the onboarding, navigate to the **Settings** -> **Stores**  on your Qonversion dashboard and fill out the empty fields.

## Offerings, Products, and Entitlements

Once all the data is filled in and the stores are set up, it's time to configure Offerings, Products, and Entitlements.

Here's how we refer to the same things differently:

| Glassfy     | Qonversion   |
| :---------- | :----------- |
| Permissions | Entitlements |
| SKUs        | Products     |
| Offerings   | Offerings    |

### Entitlements (Permissions)

Let's start with Entitlements. Similar to how you set up Permissions in Glassfy, you need to set up Entitlements in Qonversion.

Here’s how it was done in Glassfy:

<img src="https://mintcdn.com/qonversion/VTmQeWlObtMNi7PY/images/docs/820fe97-image.png?fit=max&auto=format&n=VTmQeWlObtMNi7PY&q=85&s=f1bb4ed11c1f4d58db613a6d890ac3b6" alt="" width="2274" height="1010" data-path="images/docs/820fe97-image.png" />

Here’s how to do it in Qonversion:

Navigate to the Entitlements and click the **Create Entitlement** button.

<img src="https://mintcdn.com/qonversion/i4lAeIKmC47lf8K-/images/docs/173cbc2-Screenshot_2024-02-06_at_16.54.09.png?fit=max&auto=format&n=i4lAeIKmC47lf8K-&q=85&s=ba27838f401ca54c4322970e5fcea577" alt="" width="2758" height="1700" data-path="images/docs/173cbc2-Screenshot_2024-02-06_at_16.54.09.png" />

### Products (SKUs)

Next, you need to create Products similar to  how you created SKUs in Glassfy:

<img src="https://mintcdn.com/qonversion/zdjn3kzNygqtFGgE/images/docs/a4dfce8-image.png?fit=max&auto=format&n=zdjn3kzNygqtFGgE&q=85&s=22e79169641855f74ed857067de6571f" alt="" width="1054" height="1110" data-path="images/docs/a4dfce8-image.png" />

To create Products in Qonversion, go to **Entitlements --> Products**  and click **Create Product**.

<img src="https://mintcdn.com/qonversion/cVFu44J4hjAFIf6D/images/docs/2298eb2-Screenshot_2024-02-06_at_16.57.17.png?fit=max&auto=format&n=cVFu44J4hjAFIf6D&q=85&s=ba7ef0d5bd67651ebe96a232dd366c6b" alt="" width="2768" height="1706" data-path="images/docs/2298eb2-Screenshot_2024-02-06_at_16.57.17.png" />

2. Fill in the product details.

<img src="https://mintcdn.com/qonversion/eOsiYIQAgYr1cnnF/images/docs/78c5028-Screenshot_2024-02-06_at_16.58.23.png?fit=max&auto=format&n=eOsiYIQAgYr1cnnF&q=85&s=ba735a4df62b38ddfc3fababe6bf0509" alt="" width="2772" height="1702" data-path="images/docs/78c5028-Screenshot_2024-02-06_at_16.58.23.png" />

* **Qonversion Product ID** – create your unique product identifier in Qonversion that corresponds to a unique product on the Apple App Store and Google Play Store. Qonversion SDK will use it to make purchases.

* **AppStore Product ID** – product identifier on Apple App Store. Learn more on how to create [subscriptions on iOS](https://qonversion.io/blog/configure-iap-app-store-connect/).

* **Google Play Product ID** – product identifier on Google Play Console. Learn more on how to set up [Android in-app products ](/docs/android-in-app-products).

* **Google Play Base Plan ID** - identifier of the base plan for Google Play Product. This is used for subscription products. If you're creating an in-app, leave this field empty for in-apps. In Qonversion, you will set up different products for the different base plans of the same product.

* **Associated Entitlements** - choose the entitlements that should be granted once this product is purchased.

<br />

### Offerings

Here's what your Offering setup looked like at Glassfy:

<img src="https://mintcdn.com/qonversion/ChZZw2lNh4YCkg0e/images/docs/dbb0ee9-image.png?fit=max&auto=format&n=ChZZw2lNh4YCkg0e&q=85&s=86564ea37946e529026be00a0553d7d1" alt="" width="2316" height="882" data-path="images/docs/dbb0ee9-image.png" />

In Qonversion, simply navigate to the  **Remote Config -> Offerings** from your dashboard and add the offerings similarly as you did it in Glassfy.

Now, move to installing your SDK and getting your hands into the code a bit.

## Install the SDK

Check out the links below for the SDK you need and follow the steps provided:

→ [iOS SDK](/docs/ios-sdk-setup)\
→ [Android SDK](/docs/android-sdk)
→ [Flutter SDK](/docs/flutter-sdk)
→ [React Native SDK](/docs/react-native-sdk)
→ [Unity SDK](/docs/unity-sdk)
→ [Cordova Plugin](/docs/cordova)
→ [Capacitor Plugin](/docs/capacitor)
→ [Web](/docs/web-sdk)
→ [Kids Mode Qonversion SDKs](/docs/kids-mode-sdk)

Read more about [Installing the SDKs](/docs/install-sdk).

<br />

When you're done with the setup, it's time to get into the code!

## Launch SDK

Firstly, you need to initialize the SDK. Here's how you did it in Glassfy:

```swift theme={null}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  Glassfy.initialize(apiKey: "YOUR_API_KEY", watcherMode: false)

}
```

```objectivec theme={null}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  [Glassfy initializeWithAPIKey:@"YOU_API_KEY" watcherMode:NO];

}
```

```java theme={null}
public class App extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    Glassfy.initialize(this, "YOUR_API_KEY", false, null);
  }
}
```

```kotlin theme={null}
class App : Application() {
  override fun onCreate() {
    super.onCreate()

    Glassfy.initialize(this, "YOUR_API_KEY", false, null)
  }
}
```

```javascript Flutter theme={null}
try {

  await Glassfy.initialize('YOU_API_KEY',watcherMode: false);

} catch (e) {
  // error
  [...]
}
```

```javascript React Native theme={null}
try {

  await Glassfy.initialize('YOU_API_KEY', false);

} catch (e) {
  // initialization error
  [...]
}
```

```typescript Capacitor theme={null}
try {
  await Glassfy.initialize({ apiKey: 'YOU_API_KEY', watcherMode: false });
} catch (e) {
  // initialization error
}
```

Here's how you'll do it for Qonversion.

```swift theme={null}
import Qonversion

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    let config = Qonversion.Configuration(projectKey: "projectKey", launchMode: .subscriptionManagement)
    Qonversion.initWithConfig(config)
  	return true
}
```

```objectivec theme={null}
#import "Qonversion.h"
  
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    QONConfiguration *configuration = [[QONConfiguration alloc] initWithProjectKey:@"projectKey" launchMode:QONLaunchModeSubscriptionManagement];
    [Qonversion initWithConfig:configuration];
    
    return YES;
}
```

```java theme={null}
import com.qonversion.android.sdk.Qonversion;
import com.qonversion.android.sdk.QonversionConfig;
import com.qonversion.android.sdk.dto.QLaunchMode;

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        final QonversionConfig qonversionConfig = new QonversionConfig.Builder(
                this,
                "projectKey",
                QLaunchMode.SubscriptionManagement
        ).build();
        Qonversion.initialize(qonversionConfig);
    }
}
```

```kotlin theme={null}
import com.qonversion.android.sdk.Qonversion
import com.qonversion.android.sdk.QonversionConfig
import com.qonversion.android.sdk.dto.QLaunchMode

public class App : Application {
    override fun onCreate() {
        super.onCreate()
        val qonversionConfig = QonversionConfig.Builder(
            this,
            "projectKey",
            QLaunchMode.SubscriptionManagement
        ).build()
        Qonversion.initialize(qonversionConfig)
    }
}
```

```javascript Flutter theme={null}
import 'package:qonversion_flutter/qonversion_flutter.dart';

final config = new QonversionConfigBuilder(
  'projectKey',
  QLaunchMode.subscriptionManagement
).build();
Qonversion.initialize(config);
```

```javascript React Native theme={null}
import Qonversion, {
  QonversionConfigBuilder,
  LaunchMode,
} from 'react-native-qonversion';

const config = new QonversionConfigBuilder(
  'projectKey',
  LaunchMode.SUBSCRIPTION_MANAGEMENT
).build();
Qonversion.initialize(config);
```

```csharp Unity theme={null}
using QonversionUnity;

private void Start()
{
    QonversionConfig config = new QonversionConfigBuilder(
            "projectKey",
            LaunchMode.SubscriptionManagement
        ).Build();
    Qonversion.Initialize(config);
}
```

```typescript Cordova theme={null}
const config = new Qonversion.ConfigBuilder(
  'projectKey',
  Qonversion.LaunchMode.SUBSCRIPTION_MANAGEMENT,
).build();
Qonversion.initialize(config);
```

```typescript Capacitor theme={null}
import Qonversion, {
  QonversionConfigBuilder,
  LaunchMode,
} from '@qonversion/capacitor-plugin';

const config = new QonversionConfigBuilder(
  'projectKey',
  LaunchMode.SUBSCRIPTION_MANAGEMENT
).build();
Qonversion.initialize(config);
```

<br />

## Displaying products

Glassfy:

```swift theme={null}
Glassfy.offerings { (offerings, err) in
    if let offering = offerings?["premium"] {
        // display your offering's skus
        for sku in offering.skus {
            // sku.extravars
            // sku.product.localizedTitle
            // sku.product.localizedDescription
            // sku.product.price
        }
    }
}
```

```objectivec theme={null}
[Glassfy offeringsWithCompletion:^(GYOfferings *offers, NSError *err) {
    GYOffering *offering = offers[@"premium"];
    if (offering) {
        // display your offering's skus
        for (GYSku *sku in offering.skus) {
            // sku.extravars
            // sku.product.localizedTitle
            // sku.product.localizedDescription
            // sku.product.price
        }
    }
}];
```

```kotlin theme={null}
Glassfy.offerings() { offers, err ->
    offers?.all
        ?.firstOrNull { it.offeringId == "premium" }
        ?.also {
            // display your offering's skus
            for (sku in it.skus) {
                // sku.extravars
                // sku.product.title
                // sku.product.description
                // sku.product.price
            }
        }
}
```

```java theme={null}
Glassfy.offerings(new OfferingsCallback() {
    @Override
    public void onResult(@Nullable Offerings offers, @Nullable GlassfyError err) {
        Offering offering = null;
        if (offers != null) {
            for (Offering o : offers.getAll()) {
                if (o.getOfferingId().equals("premium")) {
                    offering = o;
                }
            }
        }
        if (offering != null) {
            // display your offering's skus
            for (Sku sku : offering.getSkus()) {
                // sku.getExtravars();
                // sku.getProduct().getTitle();
                // sku.getProduct().getDescription();
                // sku.getProduct().getPrice();
            }
        }
    }
});
```

```typescript Flutter theme={null}
try {
    var offerings = await Glassfy.offerings();
    var offering = offerings.all
        ?.singleWhere((offering) => offering.offeringId == 'premium');

    offering?.skus?.forEach((sku) {
        // sku.product.description
        // sku.product.price
    });
} catch (e) {
  // initialization error
  [...]
}
```

```typescript React Native theme={null}
try {
    let offering = await Glassfy.offerings().all.find((o) => o.offeringId === 'premium');

    offering?.skus.forEach((sku) => {
      // sku.extravars
      // sku.product.description;
      // sku.product.price
    });
} catch (e) {
  [...]
}
```

```typescript Capacitor theme={null}
try {
    let offering = offerings.all.find((o) => o.identifier === 'premium_offering');

    offering?.skus.forEach((sku) => {
      // sku.extravars
      // sku.product.description;
      // sku.product.price
    });
} catch (e) {
  [...]
}
```

<br />

Qonversion:

```swift theme={null}
Qonversion.shared().offerings { (offerings, error) in
  if let products = offerings?.main?.products {
    // Display products for sale
  }
}
```

```objectivec theme={null}
[[Qonversion sharedInstance] offerings:^(QONOfferings * _Nullable offerings, NSError * _Nullable error) {
  if (offerings.main.products.count > 0) {
    // Display products for sale
  }
}];
```

```java theme={null}
Qonversion.getSharedInstance().offerings(new QonversionOfferingsCallback() {
    @Override
    public void onSuccess(@NotNull QOfferings offerings) {
        if (offerings.getMain() != null && !offerings.getMain().getProducts().isEmpty()) {
            // Display products for sale
        }
    }
    @Override
    public void onError(@NotNull QonversionError error) {
        // handle error here
    }
});
```

```kotlin theme={null}
Qonversion.shared.offerings(object: QonversionOfferingsCallback {
    override fun onSuccess(offerings: QOfferings) {
        val mainOffering = offerings.main
        if (mainOffering != null && mainOffering.products.isNotEmpty()) {
            // Display products for sale
        }
    }
    override fun onError(error: QonversionError) {
        // handle error here
    }
})
```

```javascript Flutter theme={null}
try {
  final QOfferings offerings = await Qonversion.getSharedInstance().offerings();
  final List<QProduct> products = offerings.main.products;
  if (products.isNotEmpty) {
    // Display your products
  }
} catch (e) {
  print(e);
}
```

```javascript React Native theme={null}
try {
  const offerings = await Qonversion.getSharedInstance().offerings();
  if (offerings.main != null && offerings.main.products.length > 0) {
    // Display products for sale
  }
} catch (e) {
  // handle error here
}
```

```csharp Unity theme={null}
Qonversion.GetSharedInstance().Offerings((offerings, error) =>
{
   if (error == null)
   {
            Offering mainOffering = offerings.Main;
            if(mainOffering != null)
            {
                List<Product> products = mainOffering.Products;
                if (products.Any())
                {
                    // Display your products
                }
            }
   }
   else
   {
       // Handle the error  
       Debug.Log("Error" + error.ToString());
   }
});
```

```typescript Cordova theme={null}
try {
  const offerings = await Qonversion.getSharedInstance().offerings();
  if (offerings.main != null && offerings.main.products.length > 0) {
    // Display products for sale
  }
} catch (e) {
  // handle error here
}
```

```typescript Capacitor theme={null}
try {
  const offerings = await Qonversion.getSharedInstance().offerings();
  if (offerings.main != null && offerings.main.products.length > 0) {
    // Display products for sale
  }
} catch (e) {
  // handle error here
}
```

<br />

## Making purchases

Glassfy:

```swift theme={null}
Glassfy.purchase(sku: premiumSku) { (transaction, e) in
    // update app status accordingly
    if let p = transaction?.permissions["premium"] {
        if p.isValid {
            // unlock aFeature
        } else {
            // lock aFeature
        }
    }
}
```

```objectivec theme={null}
[Glassfy purchaseSku:premiumSku completion:^(GYTransaction *transaction, NSError *err) {
    GYPermissions *permissions = transaction.permissions;
    if (permissions) {
        GYPermission *p = permissions[@"premium"];
        if (p.isValid) {
            // unlock aFeature
        }
        else {
            // lock aFeature
        }
    }
}];
```

```java theme={null}
Glassfy.purchase(activity, sku, new PurchaseCallback() {
    @Override
    public void onResult(@Nullable Transaction t, @Nullable GlassfyError err) {
        // update app status accordingly
        Permission permission = null;
        if (t != null) {
            for (Permission p : t.getPermissions().getAll()) {
                if (p.getPermissionId().equals("premium")) {
                    permission = p;
                }
            }
        }
        if (permission != null) {
            if (permission.isValid()) {
              // unlock aFeature
            } else {
              // lock aFeature
            }
        }
    }
});
```

```kotlin theme={null}
Glassfy.purchase(activity, sku) { transaction, err ->
    // update app status accordingly
    transaction?.permissions
        ?.all
        ?.firstOrNull { it.permissionId == "premium" }
        ?.also {
            if (it.isValid) {
                // unlock aFeature
            } else {
                // lock aFeature
            }
        }
}
```

```typescript Flutter theme={null}
try {
    var transaction = await Glassfy.purchaseSku(sku);

    var p = transaction.permissions?.all?.singleWhere((permission) => permission.permissionId == 'premium');
    if (p?.isValid==true) {
        // unlock aFeature
    }
    else {
        // lock aFeature
    }
} catch (e) {
  // initialization error
  [...]
}
```

```typescript React Native theme={null}
try {
    const transaction = await Glassfy.purchaseSku(premiumSku );
    const permission = transaction.permissions.all.find((p) => p.permissionId === "premium");
    if (permission && permission.isValid) {
        // unlock aFeature
    }
} catch (e) {
  // initialization error
  [...]
}
```

```typescript Capacitor theme={null}
try {
    const transaction = await Glassfy.purchaseSku({ sku: premiumSku });
    const permission = transaction.permissions.all.find((p) => p.permissionId === "premium");
    if (permission && permission.isValid) {
        // unlock aFeature
    }

} catch (e) {
  // initialization error
  [...]
}
```

<br />

Qonversion:

```swift theme={null}
Qonversion.shared().purchase(product) { (result) in
  if result.isSuccessful {
    if let premium: Qonversion.Entitlement = result.entitlements["premium"], premium.isActive {
      // Grant user access to premium features
    }
  } else if result.isCanceledByUser {
    // Handle canceled purchase
  } else if result.isPending {
    // Handle pending purchase
  } else {
    // Handle errors
  }
}
```

```objectivec theme={null}
[[Qonversion sharedInstance] purchaseWithResult:product completion:^(QONPurchaseResult * _Nonnull result) {
  if (result.isSuccessful) {
    QONEntitlement *premiumEntitlement = result.entitlements[@"premium"];
    if (premiumEntitlement && premiumEntitlement.isActive) {
      // Grant user access to premium features
    }
  } else if (result.isCanceledByUser) {
    // Handle canceled purchase
  } else if (result.isPending) {
    // Handle pending purchase
  } else {
    // Handle errors
  }
}];
```

```java theme={null}
Qonversion.getSharedInstance().purchase(this, product, new QonversionPurchaseCallback() {
    @Override
    public void onResult(@NonNull QPurchaseResult result) {
        if (result.isSuccessful()) {
            QEntitlement premium = result.getEntitlements().get("premium");
            if (premium != null && premium.isActive()) {
                // Grant user access to premium features
            }
        } else if (result.isCanceledByUser()) {
            // Handle canceled purchase
        } else if (result.isPending()) {
            // Handle pending purchase
        } else {
            // Handle errors
        }
    }
});
```

```kotlin theme={null}
Qonversion.shared.purchase(this, product, object : QonversionPurchaseCallback {
    override fun onResult(result: QPurchaseResult) {
        when {
            result.isSuccessful -> {
                val premium = result.entitlements["premium"]
                if (premium != null && premium.isActive) {
                    // Grant user access to premium features
                }
            }
            result.isCanceledByUser -> {
                // Handle canceled purchase
            }
            result.isPending -> {
                // Handle pending purchase
            }
            else -> {
                // Handle errors
            }
        }
    }
})
```

```javascript Flutter theme={null}
final result = await Qonversion.getSharedInstance().purchaseWithResult(product);

if (result.isSuccess) {
  final premium = result.entitlements?['premium'];
  if (premium != null && premium.isActive) {
    // Grant user access to premium features
  }
} else if (result.isCanceled) {
  // Handle canceled purchase
} else if (result.isPending) {
  // Handle pending purchase
} else {
  // Handle errors
}
```

```javascript React Native theme={null}
const result: PurchaseResult = await Qonversion.getSharedInstance().purchaseWithResult(product);

if (result.isSuccess) {
  const premium = result.entitlements?.get('premium');
  if (premium && premium.isActive) {
    // Grant user access to premium features
  }
} else if (result.isCanceled) {
  // Handle canceled purchase
} else if (result.isPending) {
  // Handle pending purchase
} else {
  // Handle errors
}
```

```csharp Unity theme={null}
Qonversion.GetSharedInstance().Purchase(product, (result) =>
{
    if (result.IsSuccess)
    {
        if (result.Entitlements != null && 
            result.Entitlements.TryGetValue("premium", out var premium) && 
            premium.IsActive)
        {
            // Grant user access to premium features
        }
    }
    else if (result.IsCanceled)
    {
        // Handle canceled purchase
    }
    else if (result.IsPending)
    {
        // Handle pending purchase
    }
    else
    {
        // Handle errors
    }
});
```

```typescript Cordova theme={null}
const result = await Qonversion.getSharedInstance().purchase(product);

if (result.isSuccess) {
  const premium = result.entitlements?.get('premium');
  if (premium && premium.isActive) {
    // Grant user access to premium features
  }
} else if (result.isCanceled) {
  // Handle canceled purchase
} else if (result.isPending) {
  // Handle pending purchase
} else {
  // Handle errors
}
```

```typescript Capacitor theme={null}
const result = await Qonversion.getSharedInstance().purchase(product);

if (result.isSuccess) {
  const premium = result.entitlements?.get('premium');
  if (premium && premium.isActive) {
    // Grant user access to premium features
  }
} else if (result.isCanceled) {
  // Handle canceled purchase
} else if (result.isPending) {
  // Handle pending purchase
} else {
  // Handle errors
}
```

The "`product`" here is the Qonversion Product you created earlier.

<br />

## Subscription status

To check the subscription status in Qonversion, you will use Entitlements. In Glassfy, you used Permissions.

Glassfy:

```swift theme={null}
Glassfy.permissions { permissions, err in
    guard let permissions = permissions else { return }
    for p in permissions.all {
        switch (p.permissionId) {
        case "aPermission":
            if (p.isValid) {
                // unlock aFeature
            }
            break;
        default:
            print("Permission not handled");
            break;
        }
    }
}
```

```objectivec theme={null}
[Glassfy permissionsWithCompletion:^(GYPermissions *permission, NSError *err) {
    NSArray<GYPermission*> *permissions = permission.all;
    if (permissions) {
        for (GYPermission *p in permissions) {
            switch (p.permissionId) {
            case @"premium":
                if (p.isValid) {
                    // unlock aFeature
                }
                
                break;
            default:
                NSLog(@"Permission not handled");
                break;
            }
        }
    }
}];
```

```kotlin theme={null}
Glassfy.permissions { permission, err ->
    // update app status accordingly
    permission?.all?.forEach {
        when (it.permissionId) {
            "premium" ->
                if (it.isValid) {
                    // unlock aFeature
                } 
            else -> println("Permission not handled");
        }
    }
}
```

```java theme={null}
Glassfy.permissions(new PermissionsCallback() {
    @Override
    public void onResult(@Nullable Permissions permission, @Nullable GlassfyError error) {
        // update app status accondingly
        if (permission != null) {
            for (Permission p: permission.getAll()) {
                switch (p.getPermissionId()) {
                case "premium":
                    if (p.isValid()) {
                        // unlock aFeature
                    } 
                    break;
                default:
                    Log.d(TAG, "Permission not handled");
                }
            }
        }
    }
});
```

```typescript Flutter theme={null}
try {
var permission = await Glassfy.permissions();
      permission.all?.forEach((p)=> {
        if (p.permissionId == "premium" && p.isValid==true) {
          // unlock aFeature
        }
      });
} catch (e) {
  // initialization error
  [...]
}
```

```typescript React Native theme={null}
try {
    const permissions = await Glassfy.permissions();
    permissions.forEach((p)=>{
        switch (p.permissionId) {
            case "premium":
                if (permission.isValid) {
                    // unlock 
                }
                break;
        
            default:
                break;
        }
    });

} catch (e) {
  // initialization error
  [...]
}
```

```typescript Capacitor theme={null}
try {
    const permissions = Glassfy.permissions();
    const permission = transaction.permissions.all.find((p) => p.permissionId === "premium");
    if (permission && permission.isValid) {
        // unlock aFeature
    }

} catch (e) {
  // initialization error
  [...]
}
```

Qonversion:

```swift theme={null}
Qonversion.shared().checkEntitlements { (entitlements, error) in
  if let error = error {
    // handle error
    return
  }
  
  if let premium: Qonversion.Entitlement = entitlements["premium"], premium.isActive {
    // unlock feature
  }
}
```

```objectivec theme={null}
[[Qonversion sharedInstance] checkEntitlements:^(NSDictionary<NSString *, QONEntitlement *> * _Nonnull entitlements,
                                NSError * _Nullable error) {
    QONEntitlement *premiumEntitlement = entitlements[@"premium"];
    if (premiumEntitlement && premiumEntitlement.isActive) {
      // unlock feature
    }
}];
```

```java theme={null}
Qonversion.getSharedInstance().checkEntitlements(new QonversionEntitlementsCallback() {
    @Override
    public void onSuccess(@NotNull Map<String, QEntitlement> entitlements) {
        final QEntitlement premiumEntitlement = entitlements.get("premium");

        if (premiumEntitlement != null && premiumEntitlement.isActive()) {
            // unlock feature
        }
    }

    @Override
    public void onError(@NotNull QonversionError error) {
        // handle error here
    }
});
```

```kotlin theme={null}
Qonversion.shared.checkEntitlements(object: QonversionEntitlementsCallback {
    override fun onSuccess(entitlements: Map<String, QEntitlement>) {
        val premiumEntitlement = entitlements["premium"]
        if (premiumEntitlement != null && premiumEntitlement.isActive) {
            // unlock feature
        }
    }

    override fun onError(error: QonversionError) {
        // handle error here
    }
})
```

```javascript Flutter theme={null}
try {
  final Map<String, QEntitlement> entitlements = await Qonversion.getSharedInstance().checkEntitlements();
  final premium = entitlements['premium'];
  if (premium != null && premium.isActive) {
    // unlock feature
  }
} catch (e) {
  print(e);
}
```

```javascript React Native theme={null}
try {
  const entitlements = await Qonversion.getSharedInstance().checkEntitlements();

  const premiumEntitlement = entitlements.get('premium');
  if (premiumEntitlement != null) {
    // unlock feature
  }
} catch (e) {
  // handle error here
}
```

```csharp Unity theme={null}
Qonversion.GetSharedInstance().CheckEntitlements((entitlements, error) =>
{
    if (error == null)
    {
        if (entitlements.TryGetValue("premium", out Entitlement premium) && premium.IsActive)
        {
            // unlock feature
        }
    }
    else
    {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
    }
});
```

```typescript Cordova theme={null}
try {
    const entitlements = await Qonversion.getSharedInstance().checkEntitlements();

    const premiumEntitlement = entitlements.get('premium');
    if (premiumEntitlement != null) {
        // unlock feature
    }
} catch (e) {
    // handle error here
}
```

```typescript Capacitor theme={null}
try {
  const entitlements = await Qonversion.getSharedInstance().checkEntitlements();

  const premiumEntitlement = entitlements.get('premium');
  if (premiumEntitlement != null) {
    // unlock feature
  }
} catch (e) {
  // handle error here
}
```

## Authorization system

> 📘 Cross-device and cross-platform access
>
> Set up this section only if you need cross-device and cross-platform access and your project includes an authorization system.

Qonversion lets you identify your signed-in users and unlock premium access across multiple devices. Use the `identify()` method to link a user to your signed-in subscriber. Call this method every time you want to use User Identity. For example, when a user logs in.\
User Identity provides a convenient way of managing premium access of your existing subscribers, including the following cases:

* A user reinstalls your app for any reason. Using the same User ID allows you to provide premium access linked to previously purchased products.
* A user logs in on several devices. You can provide premium access based on a subscription purchased on one of his devices.
* A user logs in on iOS and Android versions of your app. You can provide premium access based on a subscription purchased on one of the platforms.

### Logging in

When a user logs into his account, call `identify()`.

```swift theme={null}
Qonversion.shared().identify("your_custom_user_id")

// or the following option, if you want to get notified about the result.

Qonversion.shared().identify("your_custom_user_id") { user, error in
  // use user if necessary
}
```

```objectivec theme={null}
[[Qonversion sharedInstance] identify:@"your_custom_user_id"];

// or the following option, if you want to get notified about the result.

[[Qonversion sharedInstance] identify:@"your_custom_user_id" completion:^(QONUser * _Nullable user, NSError * _Nullable error) {
  // use user if necessary
}];
```

```java theme={null}
Qonversion.getSharedInstance().identify("your_custom_user_id");

// or the following option, if you want to get notified about the result.

Qonversion.getSharedInstance().identify("your_custom_user_id", new QonversionUserCallback() {
    @Override
    public void onSuccess(@NonNull QUser user) {
        // use user if necessary
    }

    @Override
    public void onError(@NonNull QonversionError error) {
        // handle error here
    }
});
```

```kotlin theme={null}
Qonversion.shared.identify("your_custom_user_id")

// or the following option, if you want to get notified about the result.

Qonversion.shared.identify("your_custom_user_id", object : QonversionUserCallback {
    override fun onSuccess(user: QUser) {
        // use user if necessary
    }

    override fun onError(error: QonversionError) {
        // handle error here
    }
})
```

```javascript Flutter theme={null}
try {
  final userInfo = await Qonversion.getSharedInstance().identify("your_custom_user_id");
  // use userInfo if necessary
} catch (e) {
  // handle error here
}
```

```javascript React Native theme={null}
try {
  const userInfo = await Qonversion.getSharedInstance().identify('your_custom_user_id');
  // use userInfo if necessary
} catch (e) {
  // handle error here
}
```

```csharp Unity theme={null}
Qonversion.GetSharedInstance().Identify("your_custom_user_id")

// or the following option, if you want to get notified about the result.

Qonversion.GetSharedInstance().Identify("your_custom_user_id", (userInfo, error) =>
{
    if (error == null)
    {
        // use userInfo if necessary
    }
    else
    {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
    }
});
```

```typescript Cordova theme={null}
try {
  const userInfo = await Qonversion.getSharedInstance().identify('your_custom_user_id');
  // use userInfo if necessary
} catch (e) {
  // handle error here
}
```

```typescript Capacitor theme={null}
try {
  const userInfo = await Qonversion.getSharedInstance().identify('your_custom_user_id');
  // use userInfo if necessary
} catch (e) {
  // handle error here
}
```

```curl theme={null}
curl --location --request POST 'https://api.qonversion.io/v3/identities/new_identities_user' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data-raw '{
    "user_id": "QON_38a2d811afd54a433587620f8696266e"
}'

{
    "id": "new_identities_user",
    "user_id": "QON_38a2d811afd54a433587620f8696266e"
}
```

> 🚧 Use the unique user ID stored in your database
>
> Always use unique user ID values. Otherwise, a user can get matched to another user's entitlements status.

To check entitlements for identified users,  you can call the `checkEntitlements` function, which we discussed above.

### Logging out

You need to call the `logout()` method to handle entitlements for an unauthorized user.\
Call this method when a user logs out within your app:

```swift theme={null}
Qonversion.shared().logout()
```

```objectivec theme={null}
[[Qonversion sharedInstance] logout];
```

```java theme={null}
Qonversion.getSharedInstance().logout();
```

```kotlin theme={null}
Qonversion.shared.logout()
```

```javascript Flutter theme={null}
Qonversion.getSharedInstance().logout();
```

```javascript React Native theme={null}
Qonversion.getSharedInstance().logout();
```

```csharp Unity theme={null}
Qonversion.GetSharedInstance().Logout();
```

```typescript Cordova theme={null}
Qonversion.getSharedInstance().logout();
```

```typescript Capacitor theme={null}
Qonversion.getSharedInstance().logout();
```

When a user logs back into his account, don't forget to use `identify()` method again.

## User-base migration

Those steps have to be done to keep the system working smoothly for your current user base. The necessity of steps depends on the complexity of your system. The simplest system is represented by a single platform infrastructure (Apple, Google, or Stripe). The complex one includes a few platforms with authorization system, your backend infrastructure, and WebHooks handler.

### Client-side migration

Follow these steps to sync user status and retrieve the latest data from Google and Apple on your current device. Call the `syncHistoricalData()` method right after Qonversion SDK initialization to synchronize all the device-related data and ensure that none of the entitlements has been missed.

> ⚠️ Qonversion Android SDK 9.+ limitation
>
> For Qonversion Android SDK 9.+, the method `syncHistoricalData()` has been removed because of a limitation in Google Play Billing Library 8, which prevents the retrieval of historical purchases

### User-database migration

Once you have the app version with Qonversion SDK up and running, it's time to proceed to the server-side migration.

1. Prepare files with [AppStore with Base64 encoded receipt data](/docs/migrating-subscriptions#app-store-data-migration-file) or [Google Play with purchase tokens](/docs/migrating-subscriptions#google-play-data-migration-file).\
   If you do not have receipt data or purchase tokens on your side but are already running your subscription management with a third-party vendor, you should ask the vendor’s support team for the required data file.
2. Then simply **share the files with us** using our support chat and provide CSV files.

## Release

Once you're done with these steps go ahead and release your app. For complex systems with cross-platform access and authorization setups, make sure you're in touch with us and our team has verified your user database migration.
