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 in Qonversion.
Once you have everything configured, Qonversion provides two ways of displaying products:
- with Qonversion 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
Local cache
Qonversion SDK caches the data on products, offerings, and entitlements. 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.
Displaying Products
Use the offerings
method to get the wrapper object Offerings
:
Qonversion.shared().offerings { (offerings, error) in
if let products = offerings?.main?.products {
// Display products for sale
}
}
[[Qonversion sharedInstance] offerings:^(QONOfferings * _Nullable offerings, NSError * _Nullable error) {
if (offerings.main.products.count > 0) {
// Display products for sale
}
}];
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
}
});
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
}
})
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);
}
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
}
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());
}
});
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
}
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
}
The products are listed in the same order as they were added to the Qonversion Offerings settings.
Additionally, in the the same settings, 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).

Qonversion Offerings
Qonversion.Product
contains the following data:
Property | Description |
---|---|
| Qonversion product ID. For example, main |
| App Stores Product ID |
|
|
| Product type. It can have the following values:
|
|
Please note, there is no 2-month duration option since Google Play does not have that option. Qonversion Product is designed to support cross-platform in-app purchases. This field is deprecated. Use |
| Subscription period for the product. Nil if the product is not a subscription. |
|
This field is deprecated. Use |
| Trial period for the product. Nil if the product is not a subscription or trial not available. |
|
|
|
This field is deprecated. Use |
|
|
| A localized product price with currency symbol provided by Apple or Google. |
Get Offering by ID
You can request the specific offering using its ID:
Qonversion.shared().offerings { (offerings, error) in
if let offering = offerings?.offering(forIdentifier: "discount") {
// offering is available
}
}
[[Qonversion sharedInstance] offerings:^(QONOfferings * _Nullable offerings, NSError * _Nullable error) {
QONOffering *offering = [offerings offeringForIdentifier:@"discount"];
if (offering) {
// offering is available
}
}];
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
}
});
Qonversion.sharedInstance.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
}
})
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);
}
try {
const offerings = await Qonversion.getSharedInstance().offerings();
const specialOffering = offerings.offeringForIdentifier('discount');
if (specialOffering != null) {
// offering is available
}
} catch (e) {
// handle error here
}
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());
}
});
try {
const offerings = await Qonversion.getSharedInstance().offerings();
const specialOffering = offerings.offeringForIdentifier('discount');
if (specialOffering != null) {
// offering is available
}
} catch (e) {
// handle error here
}
try {
const offerings = await Qonversion.getSharedInstance().offerings();
const specialOffering = offerings.offeringForIdentifier('discount');
if (specialOffering != null) {
// offering is available
}
} catch (e) {
// handle error here
}
Get The List of Available Products
Use the products
method to get the list of the products available:
Qonversion.shared().products { productsList, error in
let product = productsList["main"]
if product?.type == .trial {
}
}
[[Qonversion sharedInstance] products:^(NSDictionary<NSString *,QONProduct *> * _Nonnull productsList, NSError * _Nullable error) {
if (error) {
// Handle error
}
QONProduct *product = productsList[@"main"];
if (product && product.type == QONProductTypeTrial) {
}
}];
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
}
});
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
}
})
try {
final Map<String, QProduct> products = await Qonversion.getSharedInstance().products();
} catch (e) {
print(e);
}
const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
Qonversion.GetSharedInstance().Products((products, error) =>
{
if (error == null)
{
// Display products
}
else
{
// Handle the error
Debug.Log("Error" + error.ToString());
}
});
const products = await Qonversion.getSharedInstance().products();
const products: Map<string, Product> = await Qonversion.getSharedInstance().products();
The method returns the following two vars:
productsList
– dictionary with available productserror
- 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:
Qonversion.shared().checkTrialIntroEligibility(["main", "secondary"]) { (result, error) in
if let mainProductIntroEligibility = result["main"],
mainProductIntroEligibility.status == .eligible {
// handle available trial
}
}
[[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
}
}];
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
}
});
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
}
})
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);
}
try {
const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibilityForProductIds(['main', 'secondary']);
const mainProductEligibility = eligibilityStatuses.get('main');
if (mainProductEligibility && mainProductEligibility === IntroEligibilityStatus.ELIGIBLE) {
// handle result here
}
} catch (error) {
// handle error here
}
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
}
}
});
try {
const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibility(['main', 'secondary']);
const mainProductEligibility = eligibilityStatuses.get('main');
if (mainProductEligibility && mainProductEligibility === Qonversion.IntroEligibilityStatus.ELIGIBLE) {
// handle result here
}
} catch (error) {
// handle error here
}
try {
const eligibilityStatuses = await Qonversion.getSharedInstance().checkTrialIntroEligibilityForProductIds(['main', 'secondary']);
const mainProductEligibility = eligibilityStatuses.get('main');
if (mainProductEligibility && mainProductEligibility === IntroEligibilityStatus.ELIGIBLE) {
// handle result here
}
} catch (error) {
// handle error here
}
Updated 7 days ago