Skip to main content

Handle paywall events in iOS SDK

important

This guide covers event handling for purchases, restorations, product selection, and paywall rendering. You must also implement button handling (closing paywall, opening links, etc.). See our guide on handling button actions for details.

Paywalls configured with the Paywall Builder don't need extra code to make and restore purchases. However, they generate some events that your app can respond to. Those events include button presses (close buttons, URLs, product selections, and so on) as well as notifications on purchase-related actions taken on the paywall. Learn how to respond to these events below.

This guide is for new Paywall Builder paywalls only which require Adapty SDK v3.0 or later. For presenting paywalls in Adapty SDK v2 designed with legacy Paywall Builder, see iOS - Handle paywall events designed with legacy Paywall Builder.

tip

Want to see a real-world example of how Adapty SDK is integrated into a mobile app? Check out our sample apps, which demonstrate the full setup, including displaying paywalls, making purchases, and other basic functionality.

Handling events in SwiftUI

To control or monitor processes occurring on the paywall screen within your mobile app, use the .paywall modifier in SwiftUI:

Swift
@State var paywallPresented = false

var body: some View {
Text("Hello, AdaptyUI!")
.paywall(
isPresented: $paywallPresented,
paywall: paywall,
viewConfiguration: viewConfig,
didPerformAction: { action in
switch action {
case .close:
paywallPresented = false
case let .openURL(url):
// handle opening the URL (incl. for terms and privacy)
default:
// handle other actions
}
},
didSelectProduct: { /* Handle the event */ },
didStartPurchase: { /* Handle the event */ },
didFinishPurchase: { product, info in /* Handle the event */ },
didFailPurchase: { product, error in /* Handle the event */ },
didStartRestore: { /* Handle the event */ },
didFinishRestore: { /* Handle the event */ },
didFailRestore: { /* Handle the event */ },
didFailRendering: { error in
paywallPresented = false
},
didFailLoadingProducts: { error in
return false
}
)
}

You can register only the closure parameters you need, and omit those you do not need. In this case, unused closure parameters will not be created.

ParameterRequiredDescription
isPresentedrequiredA binding that manages whether the paywall screen is displayed.
paywallConfigurationrequiredAn AdaptyUI.PaywallConfiguration object containing visual details of the paywall. Use the AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:) method. Refer to Fetch Paywall Builder paywalls and their configuration topic for more details.
didFailPurchaserequiredInvoked when Adapty.makePurchase() fails.
didFinishRestorerequiredInvoked when Adapty.restorePurchases() completes successfully.
didFailRestorerequiredInvoked when Adapty.restorePurchases() fails.
didFailRenderingrequiredInvoked if an error occurs while rendering the interface. In this case, contact Adapty Support.
fullScreenoptionalDetermines if the paywall appears in full-screen mode or as a modal. Defaults to true.
didAppearoptionalInvoked when the paywall view was presented.
didDisappearoptionalInvoked when the paywall view was dismissed.
didPerformActionoptionalInvoked when a user clicks a button. Different buttons have different action IDs. Two action IDs are pre-defined: close and openURL, while others are custom and can be set in the builder.
didSelectProductoptionalIf the product was selected for purchase (by a user or by the system), this callback will be invoked.
didStartPurchaseoptionalInvoked when the user begins the purchase process.
didFinishPurchaseoptionalInvoked when Adapty.makePurchase() completes successfully.
didFinishWebPaymentNavigationoptionalInvoked when web payment navigation finishes.
didStartRestoreoptionalInvoked when the user starts the restore process.
didFailLoadingProductsoptionalInvoked when errors occur during product loading. Return true to retry loading.
didPartiallyLoadProductsoptionalInvoked when products are partially loaded.
showAlertItemoptionalA binding that manages the display of alert items above the paywall.
showAlertBuilderoptionalA function for rendering the alert view.
placeholderBuilderoptionalA function for rendering the placeholder view while the paywall is loading.

Handling events in UIKit

To control or monitor processes occurring on the paywall screen within your mobile app, implement the AdaptyPaywallControllerDelegate methods.

User-generated events

Product selection

If a user selects a product for purchase, this method will be invoked:

Swift
    func paywallController(
_ controller: AdaptyPaywallController,
didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer
) { }
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}

Started purchase

If a user initiates the purchase process, this method will be invoked:

Swift
func paywallController(_ controller: AdaptyPaywallController,
didStartPurchase product: AdaptyPaywallProduct) {
}
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}

It will not be invoked in Observer mode. Refer to the iOS - Present Paywall Builder paywalls in Observer mode topic for details.

Started purchase using a web paywall

If a user initiates the purchase process using a web paywall, this method will be invoked:

Swift
func paywallController(
_ controller: AdaptyPaywallController,
shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct
) {
}
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}

Successful or canceled purchase

If Adapty.makePurchase() succeeds, this method will be invoked:

Swift
func paywallController(
_ controller: AdaptyPaywallController,
didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer,
purchaseResult: AdaptyPurchaseResult
) {
if !purchaseResult.isPurchaseCancelled {
controller.dismiss(animated: true)
}
}
Event examples (Click to expand)
// Successful purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "success",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
}
}

// Cancelled purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "cancelled"
}
}

We recommend dismissing the paywall screen in that case.

It will not be invoked in Observer mode. Refer to the iOS - Present Paywall Builder paywalls in Observer mode topic for details.

Failed purchase

If Adapty.makePurchase() fails, this method will be invoked:

Swift
func paywallController(
_ controller: AdaptyPaywallController,
didFailPurchase product: AdaptyPaywallProduct,
error: AdaptyError
) { }
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
}
}

It will not be invoked in Observer mode. Refer to the iOS - Present Paywall Builder paywalls in Observer mode topic for details.

Failed purchase using a web paywall

If Adapty.openWebPaywall() fails, this method will be invoked:

Swift
func paywallController(
_ controller: AdaptyPaywallController,
didFailWebPaymentNavigation product: AdaptyPaywallProduct,
error: AdaptyError
) { }
Event example (Click to expand)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "web_payment_failed",
"message": "Web payment navigation failed",
"details": {
"underlyingError": "Network connection error"
}
}
}

Successful restore

If Adapty.restorePurchases() succeeds, this method will be invoked:

Swift
func paywallController(
_ controller: AdaptyPaywallController,
didFinishRestoreWith profile: AdaptyProfile
) { }
Event example (Click to expand)
{
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
},
"subscriptions": [
{
"vendorProductId": "premium_monthly",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
]
}
}

We recommend dismissing the screen if a the has the required accessLevel. Refer to the Subscription status topic to learn how to check it.

Failed restore

If Adapty.restorePurchases() fails, this method will be invoked:

Swift
public func paywallController(
_ controller: AdaptyPaywallController,
didFailRestoreWith error: AdaptyError
) { }
Event example (Click to expand)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}

Data fetching and rendering

Product loading errors

If you don't pass the product array during the initialization, AdaptyUI will retrieve the necessary objects from the server by itself. If this operation fails, AdaptyUI will report the error by calling this method:

Swift
public func paywallController(
_ controller: AdaptyPaywallController,
didFailLoadingProductsWith error: AdaptyError
) -> Bool {
return true
}
Event example (Click to expand)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}

If you return true, AdaptyUI will repeat the request after 2 seconds.

Rendering errors

If an error occurs during the interface rendering, it will be reported by this method:

Swift
public func paywallController(
_ controller: AdaptyPaywallController,
didFailRenderingWith error: AdaptyError
) { }
Event example (Click to expand)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}

In a normal situation, such errors should not occur, so if you come across one, please let us know.