> ## 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.

# Displaying Products

> Manage promoted in-app purchases in your app without releasing a new app version

Qonversion SDK provides flexible cross-platform in-app purchase management for your app. You don't need to save App Stores product IDs and respective prices on the client side. To manage in-app products promoted in your app from the Qonversion dashboard, you need to [configure Products, Entitlements and Offerings](subscription-management-mode#1-configure-products--entitlements) in Qonversion.

Once you have everything configured, Qonversion provides two ways of displaying products:

* with [Qonversion Offerings](offerings) (recommended)
* with Qonversion Products directly

We strongly recommend using Qonversion Offerings. This allows you to:

* Change products offered to your users without app release
* Run A/B tests

<Check>
  ### Local cache

  [Qonversion SDK caches the data on products, offerings, and entitlements](offline-sdk-mode). This data is available when the internet connection is lost or there are server-side delays. Every time your application launches, SDK requests actual SkProduct/SkuDetails from the App Store or Google Play to get the most up-to-date product data.
</Check>

## Displaying Products

Use the `offerings` method to get the wrapper object `Offerings`:

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

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

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

  ```dart 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);
  }
  ```

  ```typescript 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
  }
  ```
</CodeGroup>

The products are listed in the same order as they were added to [the Qonversion Offerings settings](https://dash.qonversion.io/app/offering).

Additionally, in the [the same settings](https://dash.qonversion.io/app/offering), you can mark an offering as a main. That allows you to avoid keeping the Offering ID in your code. Nonetheless, you can still get an offering by ID (see below).

<Frame caption="Qonversion Offerings">
  <img src="https://mintcdn.com/qonversion/eOsiYIQAgYr1cnnF/images/docs/8c48593-Screenshot_2023-11-01_at_11.17.01.png?fit=max&auto=format&n=eOsiYIQAgYr1cnnF&q=85&s=06b2fdfbe3c02371c8ffa52802aca523" width="2278" height="936" data-path="images/docs/8c48593-Screenshot_2023-11-01_at_11.17.01.png" />
</Frame>

`Qonversion.Product` contains the following data:

| Property                     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `qonversionID`               | Qonversion product ID. For example, **main**                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `storeID`                    | App Stores Product ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `basePlanID`                 | *Android only*. Identifier of the base plan for subscription product.                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `type`                       | Product type. Values: **trial** – subscription with a trial period; **intro** – subscription with an intro period; **directSubscription/subscription** – auto-renewable or prepaid (Android) subscription without a trial or intro; **oneTime/inapp** – non-recurring product.                                                                                                                                                                                                             |
| `duration` (deprecated)      | *iOS only*. Duration of a product. Values: **unknown** – non-renewable purchases; **weekly**; **monthly**; **3Months**; **6Months**; **annual**. Note: there is no 2-month duration option since Google Play does not have it. **Deprecated: use `subscriptionPeriod` instead.**                                                                                                                                                                                                           |
| `subscriptionPeriod`         | Subscription period for the product. Nil if the product is not a subscription. `SubscriptionPeriod` object contains two fields: `SubscriptionPeriodUnit`that may be `day/week/month/year`and Integer`unitCount`. For example, `SubscriptionPeriodUnit = .month` and `unitCount = 3` indicate that the subscription duration is 3 months. On Android and Cross-platform SDKs, there is also an `iso` field representing duration in ISO 8601 format, e.g. "P3M" for the above example.      |
| `trialDuration` (deprecated) | *iOS only*. Duration of an introductory offer. Values: **notAvailable** – trial not available; **unknown** – no trial info; **threeDays**; **week**; **twoWeeks**; **month**; **twoMonths**; **threeMonths**; **sixMonths**; **year**; **other** – duration not in enum range, check `skuDetails` or `skProduct` directly. **Deprecated: use `trialPeriod` instead.**                                                                                                                      |
| `trialPeriod`                | Trial period for the product. Nil if the product is not a subscription or trial not available. `SubscriptionPeriod` object contains two fields: `SubscriptionPeriodUnit`that may be `day/week/month/year`and Integer`unitCount`. For example, `SubscriptionPeriodUnit = .day` and `unitCount = 7` indicate that the trial duration is 7 days. On Android and Cross-platform SDKs, there is also an `iso` field representing duration in ISO 8601 format, e.g. "P3M" for the above example. |
| `skProduct`                  | *iOS only*. Contains `SKProduct` object received from StoreKit.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `skuDetail`                  | *Android only*. Contains skuDetails object received from Google Billing Client. **Deprecated: use `storeDetails` instead.**                                                                                                                                                                                                                                                                                                                                                                |
| `storeDetails`               | *Android only*. Describes the store details of the product, containing all the information from Google Play including the offers for purchasing the base plan of this product (specified by `basePlanID`) in case of a subscription. The complete description can be found [on this page](google-play-product-details).                                                                                                                                                                    |
| `prettyPrice`                | A localized product price with currency symbol provided by Apple or Google. It can be displayed to a user. For example, \$99.99                                                                                                                                                                                                                                                                                                                                                            |

## Get Offering by ID

You can request the specific offering using its ID:

<CodeGroup>
  ```swift Swift theme={null}
  Qonversion.shared().offerings { (offerings, error) in
    if let offering = offerings?.offering(forIdentifier: "discount") {
      // offering is available
    }
  }
  ```

  ```objectivec Objective-C theme={null}
  [[Qonversion sharedInstance] offerings:^(QONOfferings * _Nullable offerings, NSError * _Nullable error) {
    QONOffering *offering = [offerings offeringForIdentifier:@"discount"];
    if (offering) {
      // offering is available
    }
  }];
  ```

  ```java Java theme={null}
  Qonversion.getSharedInstance().offerings(new QonversionOfferingsCallback() {
      @Override
      public void onSuccess(@NotNull QOfferings offerings) {
          QOffering offering = offerings.offeringForID("discount");
          if (offering != null) {
              // offering is available
          }
      }
      @Override
      public void onError(@NotNull QonversionError error) {
          // handle error here
      }
  });
  ```

  ```kotlin Kotlin theme={null}
  Qonversion.shared.offerings(object: QonversionOfferingsCallback {
      override fun onSuccess(offerings: QOfferings) {
          val offering = offerings.offeringForID("discount")
          if (offering != null) {
              // offering is available
          }
      }
      override fun onError(error: QonversionError) {
          // handle error here
      }
  })
  ```

  ```dart Flutter theme={null}
  try {
    final QOfferings offerings = await Qonversion.getSharedInstance().offerings();
    final QOffering discount = offerings.offeringForIdentifier("discount");
    if (discount != null) {
      // Offering is available
      // Display products
    }
  } catch (e) {
    print(e);
  }
  ```

  ```typescript React Native theme={null}
  try {
    const offerings = await Qonversion.getSharedInstance().offerings();
    const specialOffering = offerings.offeringForIdentifier('discount');
    if (specialOffering != null) {
      // offering is available
    }
  } catch (e) {
    // handle error here
  }
  ```

  ```csharp Unity theme={null}
  Qonversion.GetSharedInstance().Offerings((offerings, error) =>
  {
     if (error == null)
     {
          var discount = offerings.OfferingForID("discount");
          if (discount != null)
          {
             // Offering is available
             // Display products
          }
     }
     else
     {
         // Handle the error
         Debug.Log("Error" + error.ToString());
     }
  });
  ```

  ```typescript Cordova theme={null}
  try {
    const offerings = await Qonversion.getSharedInstance().offerings();
    const specialOffering = offerings.offeringForIdentifier('discount');
    if (specialOffering != null) {
      // offering is available
    }
  } catch (e) {
    // handle error here
  }
  ```

  ```typescript Capacitor theme={null}
  try {
    const offerings = await Qonversion.getSharedInstance().offerings();
    const specialOffering = offerings.offeringForIdentifier('discount');
    if (specialOffering != null) {
      // offering is available
    }
  } catch (e) {
    // handle error here
  }
  ```
</CodeGroup>

## Get The List of Available Products

Use the `products` method to get the list of the products available:

<CodeGroup>
  ```swift Swift theme={null}
  Qonversion.shared().products { productsList, error in
      let product = productsList["main"]
      if product?.type == .trial {

      }
  }
  ```

  ```objectivec Objective-C theme={null}
  [[Qonversion sharedInstance] products:^(NSDictionary<NSString *,QONProduct *> * _Nonnull productsList, NSError * _Nullable error) {
    if (error) {
      // Handle error
    }
    QONProduct *product = productsList[@"main"];
    if (product && product.type == QONProductTypeTrial) {

    }
  }];
  ```

  ```java Java theme={null}
  Qonversion.getSharedInstance().products(new QonversionProductsCallback() {
              @Override
              public void onSuccess(@NotNull Map<String, QProduct> productsList) {
                  // handle available products here
              }

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

  ```kotlin Kotlin theme={null}
  Qonversion.shared.products(callback = object: QonversionProductsCallback {
              override fun onSuccess(products: Map<String, QProduct>) {
                  // handle available products here
              }

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

  ```dart Flutter theme={null}
  try {
    final Map<String, QProduct> products = await Qonversion.getSharedInstance().products();
  } catch (e) {
    print(e);
  }
  ```

  ```typescript React Native theme={null}
  const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
  ```

  ```csharp Unity theme={null}
  Qonversion.GetSharedInstance().Products((products, error) =>
  {
     if (error == null)
     {
         // Display products
     }
     else
     {
         // Handle the error
         Debug.Log("Error" + error.ToString());
     }
  });
  ```

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

  ```typescript Capacitor theme={null}
  const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
  ```
</CodeGroup>

The method returns the following two vars:

* `productsList` – dictionary with available products
* `error` - error

Qonversion Product IDs are the keys to the products' dictionary objects. The values are the objects of the `Qonversion.Product` class.

## Trial and introductory offer eligibility

You can check if a user is eligible for an introductory offer, including a free trial. It is calculated differently for iOS and Android users.

On iOS, users who have not previously used an introductory offer for any products in the same subscription group are eligible for an introductory offer.

On Android, eligibility is computed based on the store details. If Google Play Billing Library returned any of the trial or intro offers as a possible purchase option for a specific product, then it means that the user is eligible for it.

You can show only a regular price for users not eligible for an introductory offer. Use the following function to determine eligibility:

<CodeGroup>
  ```swift Swift theme={null}
  Qonversion.shared().checkTrialIntroEligibility(["main", "secondary"]) { (result, error) in
    if let mainProductIntroEligibility = result["main"],
       mainProductIntroEligibility.status == .eligible {
       // handle available trial
    }
  }
  ```

  ```objectivec Objective-C theme={null}
  [[Qonversion sharedInstance] checkTrialIntroEligibility:@[@"main", @"secondary"] completion:^(NSDictionary<NSString *,QNIntroEligibility *> * _Nonnull result, NSError * _Nullable error) {
    QONIntroEligibility *mainProductEligibility = result[@"main"];
    if (mainProductEligibility && mainProductEligibility.status == QONIntroEligibilityStatusEligible) {
      // handle available trial
    }
  }];
  ```

  ```java Java theme={null}
  List<String> productIds = Arrays.asList("main", "secondary");
  Qonversion.getSharedInstance().checkTrialIntroEligibility(productIds, new QonversionEligibilityCallback() {
      @Override
      public void onSuccess(@NotNull Map<String, QEligibility> eligibilities) {
          QEligibility mainProductEligibility = eligibilities.get("main");
          if (mainProductEligibility != null && mainProductEligibility.getStatus() == QIntroEligibilityStatus.Eligible) {
              // handle available trial
          }
      }

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

  ```kotlin Kotlin theme={null}
  Qonversion.shared.checkTrialIntroEligibility(listOf("main", "secondary"), object : QonversionEligibilityCallback {
      override fun onSuccess(eligibilities: Map<String, QEligibility>) {
          val mainProductEligibility = eligibilities["main"];
          if (mainProductEligibility != null && mainProductEligibility.status == QIntroEligibilityStatus.Eligible) {
              // handle result here
          }
      }

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

  ```dart Flutter theme={null}
  try {
    final Map<String, QEligibility> eligibility = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'premium']);
    final QEligibility mainProductStatus = eligibility['main'];
    if (mainProductStatus.status == QEligibilityStatus.eligible) {
        // handle available trial
    }
  } catch (e) {
    print(e);
  }
  ```

  ```typescript React Native theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```

  ```csharp Unity theme={null}
  string[] productIds = {"premium"};

  Qonversion.GetSharedInstance().CheckTrialIntroEligibility(productIds, (eligibility, error) =>
  {
      if (error == null)
      {
          if (eligibility.TryGetValue("premium", out Eligibility premiumEligibility) && premiumEligibility.Status == EligibilityStatus.Eligible)
          {
              // handle available eligibility
          }
      }
  });
  ```

  ```typescript Cordova theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === Qonversion.IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```

  ```typescript Capacitor theme={null}
  try {
      const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
      const mainProductEligibility = eligibilityStatuses.get('main');
      if (mainProductEligibility && mainProductEligibility.status === IntroEligibilityStatus.ELIGIBLE) {
          // handle result here
      }
  } catch (error) {
      // handle error here
  }
  ```
</CodeGroup>

***

[Create Products](create-products)

[Making Purchases](making-purchases)
