Skip to main content

Overview

You still pick the product in App Store Connect and the Encore Dashboard exactly as in Add Subscription Product. What changes with Qonversion is which billing code your EncorePurchaseController calls. Qonversion ships no paywall component, so Presenting Offers needs no change: call show() from your own UI.

Add the product to Qonversion first

Creating the product in App Store Connect is not enough. Qonversion keeps its own product catalog, and the mapping from a product to an entitlement lives there rather than in the store.
1

Create the product

In the Qonversion dashboard, create a product whose store identifier matches the one in App Store Connect.
2

Attach it to an entitlement

Open the entitlement the product should unlock and add the product to it. The association is only editable from the entitlement side: Qonversion’s POST /products has no entitlement field, and the API equivalent is the entitlement’s product_ids list.
Qonversion fails early when you skip the first step, which makes this easier to catch than the equivalent gap in other managers. A product Qonversion does not know about is rejected with a typed QONErrorCodeProductNotFound (2) on your first test purchase rather than in a support ticket months later.
The second step fails quietly instead. A product that exists in Qonversion but belongs to no entitlement is purchasable, and the purchase completes, but it grants nothing. The snippet below guards against that by reading the returned entitlements.

Route the purchase through Qonversion

Qonversion’s iOS purchase API is completion-based, so the controller bridges it with withCheckedThrowingContinuation before it can return the EncorePurchaseResult Encore expects:
The entitlements.isEmpty check is deliberate. Qonversion hands you the granted entitlements in the same callback, so the controller can tell “bought and unlocked” from “bought and unlocked nothing” without a second call. Reporting .purchased for an empty set would record a conversion for a user who received no access.
An empty entitlement set is also the correct state for a consumable, which is not meant to grant ongoing access. If you sell consumables through this controller, branch on the product id rather than treating every empty set as a cancel.

Promotional offers

A discount hangs off the product’s SKProduct, so the promotional path needs the product object rather than the id. Signing is completion-based too, and a nil offer is not an error: it means this user buys at list price.
Purchase with Qonversion.shared().purchaseProduct(product, options: options) and bridge the completion exactly as in the plain path.

Next