Kotlin Multiplatform - Handle paywall events
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.
To control or monitor processes occurring on the paywall screen within your mobile app, implement the AdaptyUIObserver interface methods. Some methods have default implementations that handle common scenarios automatically.
Implementation Notes: These methods are where you add your custom logic to respond to paywall events. You can use view.dismiss() to close the paywall, or implement any other custom behavior you need.
User-generated events
Paywall appearance and disappearance
When a paywall appears or disappears, these methods will be invoked:
override fun paywallViewDidAppear(view: AdaptyUIView) {
// Handle paywall appearance
// You can track analytics or update UI here
}
override fun paywallViewDidDisappear(view: AdaptyUIView) {
// Handle paywall disappearance
// You can track analytics or update UI here
}
- On iOS,
paywallViewDidAppearis also invoked when a user taps the web paywall button inside a paywall, and a web paywall opens in an in-app browser. - On iOS,
paywallViewDidDisappearis also invoked when a web paywall opened from a paywall in an in-app browser disappears from the screen.
Event examples (Click to expand)
// Paywall appeared
{
// No additional data
}
// Paywall disappeared
{
// No additional data
}
Product selection
If a user selects a product for purchase, this method will be invoked:
override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) {
// Handle product selection
// You can update UI or track analytics here
}
Event example (Click to expand)
{
"productId": "premium_monthly"
}
Started purchase
If a user initiates the purchase process, this method will be invoked:
override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) {
// Handle purchase start
// You can show loading indicators or track analytics here
}
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, canceled, or pending purchase
If a purchase succeeds, this method will be invoked. By default, it automatically dismisses the paywall unless the purchase was canceled by the user:
override fun paywallViewDidFinishPurchase(
view: AdaptyUIView,
product: AdaptyPaywallProduct,
purchaseResult: AdaptyPurchaseResult
) {
when (purchaseResult) {
is AdaptyPurchaseResult.Success -> {
// Check if user has access to premium features
if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) {
view.dismiss()
}
}
AdaptyPurchaseResult.Pending -> {
// Handle pending purchase (e.g., user will pay offline with cash)
}
AdaptyPurchaseResult.UserCanceled -> {
// Handle user cancellation
}
}
}
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"
}
}
}
}
}
// Pending purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "Pending"
}
}
// User canceled purchase
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"purchaseResult": {
"type": "UserCanceled"
}
}
We recommend dismissing the paywall screen in case of successful purchase.
Failed purchase
If a purchase fails due to an error, this method will be invoked. This includes StoreKit/Google Play Billing errors (payment restrictions, invalid products, network failures), transaction verification failures, and system errors. Note that user cancellations trigger paywallViewDidFinishPurchase with a cancelled result instead, and pending payments do not trigger this method.
override fun paywallViewDidFailPurchase(
view: AdaptyUIView,
product: AdaptyPaywallProduct,
error: AdaptyError
) {
// Add your purchase failure handling logic here
// For example: show error message, retry option, or custom error handling
}
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"
}
}
}
Started restore
If a user initiates the restore process, this method will be invoked:
override fun paywallViewDidStartRestore(view: AdaptyUIView) {
// Handle restore start
// You can show loading indicators or track analytics here
}
Successful restore
If restoring a purchase succeeds, this method will be invoked:
override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) {
// Add your successful restore handling logic here
// For example: show success message, update UI, or dismiss paywall
// Check if user has access to premium features
if (profile.accessLevels["premium"]?.isActive == true) {
view.dismiss()
}
}
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 the user 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:
override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) {
// Add your restore failure handling logic here
// For example: show error message, retry option, or custom error handling
}
Event example (Click to expand)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
Web payment navigation completion
If a user initiates the purchase process using a web paywall, this method will be invoked:
override fun paywallViewDidFinishWebPaymentNavigation(
view: AdaptyUIView,
product: AdaptyPaywallProduct?,
error: AdaptyError?
) {
if (error != null) {
// Handle web payment navigation error
} else {
// Handle successful web payment navigation
}
}
Event examples (Click to expand)
// Successful web payment navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": null
}
// Failed web payment navigation
{
"product": null,
"error": {
"code": "web_payment_failed",
"message": "Web payment navigation failed",
"details": {
"underlyingError": "Network connection error"
}
}
}
Data fetching and rendering
Product loading errors
If you don't pass the products 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:
override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) {
// Add your product loading failure handling logic here
// For example: show error message, retry option, or custom error handling
}
Event example (Click to expand)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}