Making Purchases

Make in-app purchases with Qonversion SDK

Make sure to configure Products, Entitlements and Offerings in the Qonversion dashboard before you start handling purchases with Qonversion SDK:

1. Make a purchase

When Products and Entitlements are set, you can start making purchases with thepurchaseProduct method for iOS and purchase for Android:

Qonversion.shared().purchaseProduct(product) { (entitlements, error, isCancelled) in  
  if let premium: Qonversion.Entitlement = entitlements["premium"], premium.isActive {
    // Flow for success state
  }
}
[[Qonversion sharedInstance] purchaseProduct:product completion:^(NSDictionary<NSString *,QONEntitlement *> * _Nonnull entitlements,
                                                                  NSError * _Nullable error,
                                                                  BOOL cancelled) {
  QONEntitlement *premiumEntitlement = entitlements[@"premium"];
  
  if (premiumEntitlement && premiumEntitlement.isActive) {
    // Flow for success state
  }
}];
final QPurchaseModel purchaseModel = product.toPurchaseModel();
Qonversion.getSharedInstance().purchase(this, purchaseModel, new QonversionEntitlementsCallback() {
    @Override
    public void onSuccess(@NotNull Map<String, QEntitlement> entitlements) {
        QEntitlement premiumEntitlement = entitlements.get("premium");

        if (premiumEntitlement != null && premiumEntitlement.isActive()) {
            // Handle active entitlement here
        }
    }

    @Override
    public void onError(@NotNull QonversionError error) {
        // Handle error here
        if (error.getCode() == QonversionErrorCode.CanceledPurchase) {
            // Purchase canceled by the user  
        }
    }
});
val purchaseModel = product.toPurchaseModel()
Qonversion.shared.purchase(this, purchaseModel, callback = object: QonversionEntitlementsCallback {
    override fun onSuccess(entitlements: Map<String, QEntitlement>) {
        val premiumEntitlement = entitlements["premium"]
        if (premiumEntitlement != null && premiumEntitlement.isActive) {
            // Handle active entitlement here
        }
    }

    override fun onError(error: QonversionError) {
        // Handle error here
        if (error.code === QonversionErrorCode.CanceledPurchase) {
            // Purchase canceled by the user
        }
    }
})
try {
  final QPurchaseModel purchaseModel = product.toPurchaseModel();
  final Map<String, QEntitlement> entitlements = await Qonversion.getSharedInstance().purchase(purchaseModel);
} on QPurchaseException catch (e) {
  if (e.isUserCancelled) {
    // Purchase canceled by the user
  }
  print(e);
}
try {
  const purchaseModel: PurchaseModel = product.toPurchaseModel();
  const entitlements: Map<string, Entitlement> = await Qonversion.getSharedInstance().purchase(purchaseModel);
} catch (e) {
  if (e.userCanceled) {
    // Purchase canceled by the user
  }
  console.log(e);
}
PurchaseModel purchaseModel = product.ToPurchaseModel();
Qonversion.GetSharedInstance().Purchase(purchaseModel, (entitlements, error, isCancelled) =>
{
      if (error == null)
      {
          if (entitlements.TryGetValue("premium", out Entitlement premium) && premium.IsActive)
          {
             // Handle the active entitlement here
          }
      }
      else
      {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
      }
});
try {
  const purchaseModel: PurchaseModel = product.toPurchaseModel();
  const entitlements = await Qonversion.getSharedInstance().purchase(purchaseModel);
} catch (e) {
  if (e.userCanceled) {
    // Purchase canceled by the user
  }
  console.log(e);
}

Where "product" is the Qonversion Product created in the Dashboard. See the previous step to display Products.

1.1. Define a specific offer (Android only)

Google Play Billing Library allows you to sell a subscription with different offers. You can get information about available offers from QProduct.storeDetails. Use one of the following options to provide the chosen offer for the purchase.

// Specifying offer via the `toPurchaseModel` method:
final QProductOfferDetails productOfferDetails = ...; // Choose an offer from `storeDetails`
final QPurchaseModel purchaseModel = product.toPurchaseModel(productOfferDetails);

// Specifying offer ID via the `toPurchaseModel` method:
final QPurchaseModel purchaseModel = product.toPurchaseModel("offer_id");

// Specifying offer ID via the constructor:
final QPurchaseModel purchaseModel = new QPurchaseModel("productId", "offer_id");
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId", "offer_id");

// Specifying offer ID after the purchase model creation:
purchaseModel.setOfferId("offer_id");
// Specifying offer via the `toPurchaseModel` method:
val productOfferDetails = ... // Choose an offer from `storeDetails`
val purchaseModel = product.toPurchaseModel(productOfferDetails)

// Specifying offer ID via the `toPurchaseModel` method:
val purchaseModel = product.toPurchaseModel("offer_id")

// Specifying offer ID via the constructor:
val purchaseModel = QPurchaseModel("productId", "offer_id")
val purchaseUpdateModel = QPurchaseUpdateModel("newProductId", "oldProductId", "offer_id")

// Specifying offer ID after the purchase model creation:
purchaseModel.offerId = "offer_id"
// Specifying offer via the `toPurchaseModel` method:
final QProductOfferDetails productOfferDetails = ...; // Choose an offer from `storeDetails`
final QPurchaseModel purchaseModel = product.toPurchaseModelWithOffer(productOfferDetails);

// Specifying offer ID via the `toPurchaseModel` method:
final QPurchaseModel purchaseModel = product.toPurchaseModel('offer_id');

// Specifying offer ID via the constructor:
final QPurchaseModel purchaseModel = new QPurchaseModel('productId', 'offer_id');
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel('newProductId', 'oldProductId', 'offer_id');

// Specifying offer ID after the purchase model creation:
purchaseModel.offerId = 'offer_id';
// Specifying offer via the `toPurchaseModel` method:
const productOfferDetails = ...; // Choose an offer from `storeDetails`
const purchaseModel = product.toPurchaseModelWithOffer(productOfferDetails);

// Specifying offer ID via the `toPurchaseModel` method:
const purchaseModel = product.toPurchaseModel('offer_id');

// Specifying offer ID via the constructor:
const purchaseModel = new PurchaseModel('productId', 'offer_id');
const purchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId', 'offer_id');

// Specifying offer ID after the purchase model creation:
purchaseModel.offerId = 'offer_id';
// Specifying offer via the `ToPurchaseModel` method:
ProductOfferDetails productOfferDetails = ...; // Choose an offer from `StoreDetails`
PurchaseModel purchaseModel = product.ToPurchaseModel(productOfferDetails);

// Specifying offer ID via the `ToPurchaseModel` method:
PurchaseModel purchaseModel = product.ToPurchaseModel("offer_id");

// Specifying offer ID via the constructor:
PurchaseModel purchaseModel = PurchaseModel("productId", "offer_id");
PurchaseUpdateModel purchaseUpdateModel = PurchaseUpdateModel("newProductId", "oldProductId", "offer_id");

// Specifying offer ID after the purchase model creation:
purchaseModel.OfferId = "offer_id";
// Specifying offer via the `toPurchaseModel` method:
const productOfferDetails = ...; // Choose an offer from `storeDetails`
const purchaseModel = product.toPurchaseModelWithOffer(productOfferDetails);

// Specifying offer ID via the `toPurchaseModel` method:
const purchaseModel = product.toPurchaseModel('offer_id');

// Specifying offer ID via the constructor:
const purchaseModel = new PurchaseModel('productId', 'offer_id');
const purchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId', 'offer_id');

// Specifying offer ID after the purchase model creation:
purchaseModel.offerId = 'offer_id';

If provided, we will try to find and purchase the offer with the specified ID for the requested Qonversion product. If there is no offer with the specified ID, an error will be returned. If no offer ID is provided for the subscription purchase of Qonversion product with a specified base plan ID, then we will choose the most profitable offer for the client from all the available offers. We calculate the cheapest price for the client by comparing all the trial or intro phases and the base plan. For old Qonversion products (where the base plan ID is not specified), as well as for in-app products, the offer ID is ignored.

You can also remove any intro/trial offer from the purchase (to keep only a base plan). For that purpose, you should call removeOffer method of purchase model:

purchaseModel.removeOffer();
purchaseModel.removeOffer()
purchaseModel.removeOffer();
purchaseModel.removeOffer();
purchaseModel.RemoveOffer();
purchaseModel.removeOffer();

2. Handle a purchase result

The product purchase method returns a dictionary with the user's entitlements on success (either via callback/completion block or as return value depending on the platform). If something goes wrong, the method returns an error or throws an exception with the failure description.

There is aisCancelled flag available, which equals true if a user cancels the purchasing process on iOS and Unity. You can check the same behavior for Android by comparing the error code with theQonversionErrorCode.CanceledPurchase. For Flutter and React-Native, there is an additional field in the thrown exception for that purpose (see the code examples above).

Entitlement IDs are the keys to the entitlements dictionary.
The values are the objects of the Qonversion.Entitlement class.

3. Update purchases (Android only)

Upgrading, downgrading, or changing a subscription on Google Play Store requires calling the updatePurchase() function.
See Google Play Documentation for more details.

final QPurchaseUpdateModel purchaseUpdateModel = newProduct.toPurchaseUpdateModel("oldProductId");
Qonversion.getSharedInstance().updatePurchase(this, purchaseUpdateModel, new QonversionEntitlementsCallback() {
    @Override
    public void onSuccess(@NotNull Map<String, QEntitlement> entitlements) {
        QEntitlement premiumEntitlement = entitlements.get("premium");

        if (premiumEntitlement != null && premiumEntitlement.isActive()) {
            // Handle active entitlement here
        }
    }

    @Override
    public void onError(@NotNull QonversionError error) {
        // Handle error here
    }
});
val purchaseUpdateModel = product.toPurchaseUpdateModel("oldProductId")
Qonversion.shared.updatePurchase(this, purchaseUpdateModel, callback = object: QonversionEntitlementsCallback {
    override fun onSuccess(entitlements: Map<String, QEntitlement>) {
        val premiumEntitlement = entitlements["premium"]
        if (premiumEntitlement != null && premiumEntitlement.isActive) {
            // Handle active entitlement here
        }
    }

    override fun onError(error: QonversionError) {
        // Handle error here
    }
})
try {
  final QPurchaseUpdateModel purchaseUpdateModel = product.toPurchaseUpdateModel('oldProductId');
  final Map<String, QEntitlement> entitlements = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  print(e);
}
try {
  const purchaseUpdateModel: PurchaseUpdateModel = product.toPurchaseUpdateModel('oldProductId');
  const entitlement: Map<string, Entitlement> = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  console.log(e);
}
PurchaseUpdateModel purchaseUpdateModel = product.ToPurchaseUpdateModel("oldProductId");
Qonversion.GetSharedInstance().UpdatePurchase(purchaseUpdateModel, (entitlements, error, isCancelled) =>
{
    if (error == null)
      {
          if (permissions.TryGetValue("premium", out Entitlement premium) && premium.IsActive)
          {
             // Handle the active entitlement here
          }
      }
      else
      {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
      }
});
try {
  const purchaseUpdateModel = product.toPurchaseUpdateModel('oldProductId');
  const entitlement = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  console.log(e);
}

Alternatively, you can use the Qonversion product ID for the new product (this option will be deprecated soon):

final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId");
Qonversion.getSharedInstance().updatePurchase(this, purchaseUpdateModel, new QonversionEntitlementsCallback() {
    @Override
    public void onSuccess(@NotNull Map<String, QEntitlement> entitlements) {
        QEntitlement premiumEntitlement = entitlements.get("premium");

        if (premiumEntitlement != null && premiumEntitlement.isActive()) {
            // Handle active entitlement here
        }
    }

    @Override
    public void onError(@NotNull QonversionError error) {
        // Handle error here
    }
});
val purchaseUpdateModel = QPurchaseUpdateModel("newProductId", "oldProductId")
Qonversion.shared.updatePurchase(this, purchaseUpdateModel, callback = object: QonversionEntitlementsCallback {
    override fun onSuccess(entitlements: Map<String, QEntitlement>) {
        val premiumEntitlement = entitlements["premium"]
        if (premiumEntitlement != null && premiumEntitlement.isActive) {
            // Handle active entitlement here
        }
    }

    override fun onError(error: QonversionError) {
        // Handle error here
    }
})
try {
  final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel('newProductId', 'oldProductId');
  final Map<String, QEntitlement> entitlements = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  print(e);
}
try {
  const purchaseUpdateModel: PurchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId');
  const entitlement: Map<string, Entitlement> = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  console.log(e);
}
PurchaseUpdateModel purchaseUpdateModel = new PurchaseUpdateModel("newProductId", "oldProductId");
Qonversion.GetSharedInstance().UpdatePurchase(purchaseUpdateModel, (entitlements, error, isCancelled) =>
{
      if (error == null)
      {
         // Handle entitlements here
      }
      else
      {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
      }
});
try {
  const purchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId');
  const entitlement = await Qonversion.getSharedInstance().updatePurchase(purchaseUpdateModel);
} catch (e) {
  console.log(e);
}

Also, Qonversion supports providing any replacement mode for the old purchase. Just provide the necessary purchase update policy to a purchase model in one of the following ways:

// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
final QPurchaseUpdateModel purchaseUpdateModel = newProduct.toPurchaseUpdateModel("oldProductId", QPurchaseUpdatePolicy.WithTimeProration);

// Specifying purchase update policy via the constructor:
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId", QPurchaseUpdatePolicy.WithTimeProration);

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.setUpdatePolicy(QPurchaseUpdatePolicy.WithTimeProration);
// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
val purchaseUpdateModel = newProduct.toPurchaseUpdateModel("oldProductId", QPurchaseUpdatePolicy.WithTimeProration)

// Specifying purchase update policy via the constructor:
val purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId", QPurchaseUpdatePolicy.WithTimeProration)

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.updatePolicy = QPurchaseUpdatePolicy.WithTimeProration)
// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
final QPurchaseUpdateModel purchaseUpdateModel = newProduct.toPurchaseUpdateModel('oldProductId', updatePolicy: QPurchaseUpdatePolicy.withTimeProration);

// Specifying purchase update policy via the constructor:
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel('newProductId', 'oldProductId', updatePolicy: QPurchaseUpdatePolicy.withTimeProration);

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.updatePolicy = QPurchaseUpdatePolicy.withTimeProration;
// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
const purchaseUpdateModel = newProduct.toPurchaseUpdateModel('oldProductId', QPurchaseUpdatePolicy.WITH_TIME_PRORATION);

// Specifying purchase update policy via the constructor:
const purchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId', PurchaseUpdatePolicy.WITH_TIME_PRORATION);

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.updatePolicy = PurchaseUpdatePolicy.WITH_TIME_PRORATION;
// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
PurchaseUpdateModel purchaseUpdateModel = newProduct.ToPurchaseUpdateModel("oldProductId", PurchaseUpdatePolicy.WithTimeProration);

// Specifying purchase update policy via the constructor:
PurchaseUpdateModel purchaseUpdateModel = new PurchaseUpdateModel("newProductId", "oldProductId", PurchaseUpdatePolicy.WithTimeProration);

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.UpdatePolicy = PurchaseUpdatePolicy.WithTimeProration;
// Specifying purchase update policy via the `toPurchaseUpdatePolicy` method:
const purchaseUpdateModel = newProduct.toPurchaseUpdateModel('oldProductId', QPurchaseUpdatePolicy.WITH_TIME_PRORATION);

// Specifying purchase update policy via the constructor:
const purchaseUpdateModel = new PurchaseUpdateModel('newProductId', 'oldProductId', PurchaseUpdatePolicy.WITH_TIME_PRORATION);

// Specifying purchase update policy after the purchase update model creation:
purchaseUpdateModel.updatePolicy = PurchaseUpdatePolicy.WITH_TIME_PRORATION;

Purchase update policy can be one of the following values:

NameDescription
ChargeFullPriceThe new plan takes effect immediately, and the user is charged full price of new plan and is given a full billing cycle of subscription, plus remaining prorated time from the old plan.
ChargeProratedPriceThe new plan takes effect immediately, and the billing cycle remains the same.
WithTimeProrationThe new plan takes effect immediately, and the remaining time will be prorated and credited to the user.
DeferredThe new purchase takes effect immediately, the new plan will take effect when the old item expires.
WithoutProrationThe new plan takes effect immediately, and the new price will be charged on next recurrence time.

The default update policy is WithTimeProration.

5. Check user entitlements

Use the checkEntitlements() SDK method in case you want to check usersโ€™ entitlements separately from a purchase. Learn more here.

6. Restore purchases

When users, for example, upgrade to a new phone, they need to restore purchases so they can keep access to your premium features.

Call the restore() method to restore purchases:

Qonversion.shared().restore { [weak self] (entitlements, error) in
  if let error = error {
    // Handle error
  }
  
  if let entitlement: Qonversion.Entitlement = entitlements["plus"], entitlement.isActive {
    // Restored and entitlement is active 
  }
}
[[Qonversion sharedInstance] restore:^(NSDictionary<NSString *, QONEntitlement *> * _Nonnull result, NSError * _Nullable error) {
  if (error) {
    // Handle error
  }
  QONEntitlement *entitlement = result[@"active"];
  if (entitlement && entitlement.isActive) {
    // Restored and entitlement is active
  }
}];
Qonversion.getSharedInstance().restore(new QonversionEntitlementsCallback() {
    @Override
    public void onSuccess(@NotNull Map<String, QEntitlement> entitlements) {
        QEntitlement premiumEntitlement = entitlements.get("premium");

        if (premiumEntitlement != null && premiumEntitlement.isActive()) {
            // handle active entitlement here
        }
    }

    @Override
    public void onError(@NotNull QonversionError error) {
        // handle error here
    }
});
Qonversion.shared.restore(object : QonversionEntitlementsCallback {
    override fun onSuccess(entitlements: Map<String, QEntitlement>) {
        val premiumEntitlement = entitlements["premium"]
        if (premiumEntitlement != null && premiumEntitlement.isActive) {
            // handle active entitlement here
        }
    }

    override fun onError(error: QonversionError) {
        // handle error here
    }
})
try {
  final Map<String, QEntitlement> entitlements = await Qonversion.getSharedInstance().restore();
} catch (e) {
  print(e);
}
try {
  const entitlements: Map<string, Entitlement> = await Qonversion.getSharedInstance().restore();
 } catch (e) {
  console.log(e);
}
Qonversion.GetSharedInstance().Restore((entitlements, error) =>
{
      if (error == null)
      {
         // Handle entitlements here
      }
      else
      {
        // Handle the error  
        Debug.Log("Error" + error.ToString());
      }
});
try {
    const entitlements = await Qonversion.getSharedInstance().restore();
} catch (e) {
    console.log(e);
}