处理流程与付费墙事件 - iOS

本指南涵盖购买、恢复、产品选择和付费墙渲染的事件处理。你还需要实现按钮处理(关闭付费墙、打开链接等)。详情请参阅处理按钮操作指南

流程和付费墙无需额外代码即可完成购买和恢复购买操作。但它们会产生一些事件,你的应用可以对这些事件做出响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及与购买相关的操作通知。请参阅以下内容,了解如何响应这些事件。

想查看 Adapty SDK 集成到移动应用的真实示例吗?欢迎参考我们的示例应用,其中展示了完整的配置流程,包括显示付费墙、进行购买及其他基础功能。

在 SwiftUI 中处理事件

要控制或监控移动应用中流程或付费墙页面上发生的操作,请在 SwiftUI 中使用 .flow 修饰符:

@State var flowPresented = false

var body: some View {
    Text("Hello, AdaptyUI!")
        .flow(
            isPresented: $flowPresented,
            flowConfiguration: flowConfiguration,
            didPerformAction: { action in
                switch action {
                    case .close:
                        flowPresented = false
                    case let .openURL(url):
                        // handle opening the URL (incl. for terms and privacy)
                    default:
                        // handle other actions
                }
            },
            didSelectProduct: { product in /* Handle the event */ },
            didStartPurchase: { product in /* Handle the event */ },
            didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ },
            didFailPurchase: { product, error in /* handle the error */ },
            didStartRestore: { /* Handle the event */ },
            didFinishRestore: { profile in /* check access level and dismiss */ },
            didFailRestore: { error in /* handle the error */ },
            didReceiveError: { error in
                flowPresented = false
            },
            didFailLoadingProducts: { error in
                // Return `true` to retry loading
                return false
            }
        )
}

你只需注册自己用得到的闭包参数,不需要的可以直接省略。

参数是否必填描述
isPresented必填控制流程或付费墙页面是否显示的绑定值。
flowConfiguration必填包含流程或付费墙视觉详情的 AdaptyUI.FlowConfiguration 对象。详情请参阅获取流程和付费墙
didFinishPurchase必填Adapty.makePurchase() 成功完成时触发。流程不会自动关闭——可在此处将展示绑定设为 false,或不做任何操作以让流程在购买后继续进行。
didFailPurchase必填Adapty.makePurchase() 失败时触发。
didFinishRestore必填Adapty.restorePurchases() 成功完成时触发。
didFailRestore必填Adapty.restorePurchases() 失败时触发。
didReceiveError必填当流程遇到渲染错误或流程脚本运行时错误(例如 JavaScript 异常,AdaptyUIError 代码 4105)时触发。若为渲染错误,请联系 Adapty 支持
placeholderBuilder可选在流程或付费墙加载期间渲染占位视图的函数。默认为 ProgressView
fullScreen可选控制流程或付费墙是以全屏模式还是以 sheet 形式显示。默认为 true
didAppear可选当流程或付费墙视图出现在屏幕上时触发。
didDisappear可选当流程或付费墙视图被关闭时触发。
didPerformAction可选当用户点击按钮时触发。预定义了两个动作 ID:closeopenURL;其他为自定义动作,可在编辑工具中设置。
didSelectProduct可选当用户或系统选择某个产品进行购买时触发。
didStartPurchase可选当用户开始购买流程时触发。
didFinishWebPaymentNavigation可选当网页支付导航完成时触发。
didStartRestore可选当用户开始恢复购买流程时触发。
didFailLoadingProducts可选当产品加载发生错误时触发。返回 true 可重试加载。
didPartiallyLoadProducts可选当产品仅部分加载完成时触发。
showAlertItem可选控制在流程或付费墙上方显示提示项的绑定值。
showAlertBuilder可选渲染提示视图的函数。

在 UIKit 中处理事件

对于 UIKit 应用,事件通过 AdaptyFlowControllerDelegate 协议来处理。请参阅展示流程与付费墙 - iOS,了解如何配置带有 AdaptyFlowControllerDelegateAdaptyFlowController。 该协议声明了 13 个方法。其中 4 个没有默认实现,必须在遵循协议时实现:didFinishPurchasedidFailPurchasedidFinishRestoreWithdidFailRestoreWith。其余方法提供默认的空操作实现,可在需要自定义行为时覆盖。以下按用途对方法进行分组。

生命周期

func flowControllerDidAppear(_ controller: AdaptyFlowController) { }

func flowControllerDidDisappear(_ controller: AdaptyFlowController) { }

这两个方法分别在流程或付费墙视图显示和关闭时触发。

用户操作

func flowController(
    _ controller: AdaptyFlowController,
    didPerform action: AdaptyUI.Action
) { }

AdaptyUI.Action 的枚举值:

  • .close — 默认行为是关闭当前控制器。可以重写此方法以保持控制器显示或执行额外的清理操作。
  • .openURL(url:) — 默认行为是通过 UIApplication.shared.open(...) 打开 URL。
  • .custom(id:) — 当用户点击在编辑工具中设置了自定义动作 ID 的按钮时触发。

产品选择

func flowController(
    _ controller: AdaptyFlowController,
    didSelectProduct product: AdaptyPaywallProduct
) { }

当用户或系统选择某个产品进行购买时触发。该产品包含完整的优惠信息(v4 中资格会自动判断,不再有单独的 AdaptyPaywallProductWithoutDeterminingOffer 类型)。

购买事件

func flowController(
    _ controller: AdaptyFlowController,
    didStartPurchase product: AdaptyPaywallProduct
) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFinishPurchase product: AdaptyPaywallProduct,
    purchaseResult: AdaptyPurchaseResult
) {
    if !purchaseResult.isPurchaseCancelled {
        controller.dismiss(animated: true) // or do nothing to let the flow continue
    }
}

func flowController(
    _ controller: AdaptyFlowController,
    didFailPurchase product: AdaptyPaywallProduct,
    error: AdaptyError
) { }

didFinishPurchasedidFailPurchase 没有默认实现,必须自行实现。控制器在购买成功后不会自动关闭——在适当时机调用 controller.dismiss(animated:),或者不做任何处理,让多屏幕流程在购买完成后继续进行。

恢复事件

func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFinishRestoreWith profile: AdaptyProfile
) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFailRestoreWith error: AdaptyError
) { }

didFinishRestoreWithdidFailRestoreWith 没有默认实现。在关闭控制器之前,请检查返回的 AdaptyProfile 是否包含所需的访问等级。

流程错误与产品加载错误

func flowController(
    _ controller: AdaptyFlowController,
    didReceiveError error: AdaptyUIError
) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFailLoadingProductsWith error: AdaptyError
) -> Bool {
    // Return `true` to retry product loading; default returns `false`.
    return false
}

func flowController(
    _ controller: AdaptyFlowController,
    didPartiallyLoadProducts failedIds: [String]
) { }

didReceiveError 会在渲染错误和流程脚本运行时错误(JavaScript 异常,AdaptyUIError 代码 4105)时触发。对于渲染错误,请联系 Adapty 支持。对于加载错误,可在 didFailLoadingProductsWith 中返回 true 来重试——适用于处理瞬时网络故障。

Web 支付导航

func flowController(
    _ controller: AdaptyFlowController,
    didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
    error: AdaptyError?
) { }

Web 支付导航完成后触发,无论成功或失败。

本指南介绍如何处理购买、恢复、产品选择和付费墙渲染等相关事件。你还必须实现按钮的处理逻辑(关闭付费墙、打开链接等)。详情请参阅处理按钮操作的指南

使用付费墙编辑工具配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上购买相关操作的通知。请参阅下文了解如何响应这些事件。

本指南仅适用于新版付费墙编辑工具付费墙,需要 Adapty SDK v3.0 或更高版本。

想看 Adapty SDK 集成到移动应用中的真实示例?请查看我们的示例应用,其中演示了完整的集成流程,包括展示付费墙、发起购买以及其他基本功能。

在 SwiftUI 中处理事件

要在移动应用的付费墙界面中控制或监听相关流程,请在 SwiftUI 中使用 .paywall 修饰符:

@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
          }
      )
}

你只需注册实际用到的闭包参数,不需要的参数可以直接省略。这样一来,未使用的闭包参数就不会被创建。

参数是否必填描述
isPresented必填用于控制付费墙页面是否显示的绑定值。
paywallConfiguration必填包含付费墙视觉信息的 AdaptyUI.PaywallConfiguration 对象。使用 AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:) 方法。详情请参阅获取付费墙编辑工具的付费墙及其配置
didFailPurchase必填购买因错误失败时触发(例如,不允许支付、网络问题、无效产品)。用户取消或待处理付款时不触发。
didFinishRestore必填购买成功完成时触发。
didFailRestore必填恢复购买失败时触发。
didFailRendering必填渲染界面时发生错误则触发。遇到此情况请联系 Adapty 支持
fullScreen可选决定付费墙以全屏模式还是弹窗模式显示。默认为 true
didAppear可选付费墙视图出现在屏幕上时触发。当用户点击付费墙内的网页付费墙按钮并在应用内浏览器中打开网页付费墙时,也会触发。
didDisappear可选付费墙视图关闭时触发。从付费墙在应用内浏览器中打开的网页付费墙从屏幕上消失时,也会触发。
didPerformAction可选用户点击按钮时触发。不同按钮对应不同的动作 ID。其中两个动作 ID 为预定义:closeopenURL,其余为自定义 ID,可在编辑工具中设置。
didSelectProduct可选当产品被选中购买(由用户或系统触发)时,此回调将被调用。
didStartPurchase可选用户开始购买流程时触发。
didFinishPurchase可选购买成功完成时触发。
didFinishWebPaymentNavigation可选尝试打开网页付费墙进行购买后触发,无论成功与否。
didStartRestore可选用户开始恢复流程时触发。
didFailLoadingProducts可选产品加载过程中发生错误时触发。返回 true 可重试加载。
didPartiallyLoadProducts可选产品部分加载完成时触发。
showAlertItem可选用于管理付费墙上方提示项显示的绑定值。
showAlertBuilder可选用于渲染提示视图的函数。
placeholderBuilder可选付费墙加载期间用于渲染占位视图的函数。

在 UIKit 中处理事件

要控制或监听移动应用中付费墙页面上发生的事件,请实现 AdaptyPaywallControllerDelegate 的各个方法。

用户生成的事件

产品选择

当用户选择某个产品进行购买时,将调用以下方法:

    func paywallController(
        _ controller: AdaptyPaywallController,
        didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer
    ) { }
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

开始购买

当用户发起购买流程时,将调用此方法:

func paywallController(_ controller: AdaptyPaywallController,
                       didStartPurchase product: AdaptyPaywallProduct) {
}
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

在 Observer 模式下不会触发此回调。详情请参阅 iOS - 在 Observer 模式下展示付费墙编辑工具付费墙

通过网页付费墙发起购买

如果用户通过付费墙网页发起购买流程,将会调用以下方法:

func paywallController(
        _ controller: AdaptyPaywallController,
        shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct
    ) {
    }
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

购买成功或取消

如果购买成功,将调用以下方法:

func paywallController(
    _ controller: AdaptyPaywallController,
    didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer,
    purchaseResult: AdaptyPurchaseResult
) { }
}
事件示例(点击展开)
// 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"
  }
}

在这种情况下,我们建议关闭付费墙页面。

在 Observer 模式下,该回调不会被调用。详情请参阅 iOS - 在 Observer 模式下展示付费墙编辑工具付费墙

购买失败

如果购买因错误而失败,此方法将被调用。这包括 StoreKit 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 didFinishPurchase(结果为已取消),待处理的支付不会触发此方法。

func paywallController(
    _ controller: AdaptyPaywallController,
    didFailPurchase product: AdaptyPaywallProduct,
    error: AdaptyError
) { }
事件示例(点击展开)
{
  "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"
    }
  }
}

在 Observer 模式下不会调用此方法。详情请参阅 iOS - 在 Observer 模式下展示付费墙编辑工具构建的付费墙

通过网页付费墙购买失败

如果 Adapty.openWebPaywall() 失败,将调用以下方法:

func paywallController(
        _ controller: AdaptyPaywallController,
        didFailWebPaymentNavigation product: AdaptyPaywallProduct,
        error: AdaptyError
    ) { }
事件示例(点击展开)
{
  "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"
    }
  }
}

恢复购买成功

如果恢复购买成功,将调用以下方法:

func paywallController(
    _ controller: AdaptyPaywallController, 
    didFinishRestoreWith profile: AdaptyProfile
) { }
事件示例(点击展开)
{
  "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"
      }
    ]
  }
}

我们建议在用户拥有所需 accessLevel 时关闭该界面。请参阅订阅状态了解如何检查。

恢复失败

如果恢复购买失败,将调用以下方法:

public func paywallController(
    _ controller: AdaptyPaywallController, 
    didFailRestoreWith error: AdaptyError
) { }
事件示例(点击展开)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

数据获取与渲染

产品加载错误

如果你在初始化时没有传入产品数组,AdaptyUI 会自动从服务器获取所需对象。如果此操作失败,AdaptyUI 会通过调用以下方法来上报错误:

public func paywallController(
    _ controller: AdaptyPaywallController,
    didFailLoadingProductsWith error: AdaptyError
) -> Bool {
    return true
}
事件示例(点击展开)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

如果返回 true,AdaptyUI 将在 2 秒后重新发起请求。

渲染错误

如果在界面渲染过程中发生错误,将通过以下方法上报:

public func paywallController(
    _ controller: AdaptyPaywallController,
    didFailRenderingWith error: AdaptyError
) { }
事件示例(点击展开)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

正常情况下不应出现此类错误,如果您遇到了,请告知我们。