处理 Flow 与付费墙事件 - Android

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

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

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

如果需要控制或监控购买界面上发生的流程,请实现 AdaptyFlowEventListener 的各个方法。 如果您希望在某些情况下保留默认行为,可以继承 AdaptyFlowDefaultEventListener 并仅覆盖您想修改的方法。

以下是 AdaptyFlowDefaultEventListener 中的默认行为。

用户生成的事件

选择产品

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

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

开始购买

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

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

购买成功、取消或待处理

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

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
事件示例(点击展开)
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

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

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

我们建议在这种情况下关闭该页面。

在观察者模式下,该方法不会被调用。详情请参阅 Android - 在观察者模式下展示付费墙编辑工具付费墙 主题。

购买失败

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

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

该方法在 Observer 模式下不会被调用。详情请参阅 Android - 在 Observer 模式下展示付费墙编辑工具付费墙 主题。

完成 Web 支付导航

此方法在尝试为特定产品打开 Web 付费墙 后调用,无论导航成功还是失败均会触发:

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

参数:

参数描述
product打开网页付费墙时对应的 AdaptyPaywallProduct。可以为 null
error如果网页付费墙导航失败,则为 AdaptyError 对象;导航成功时为 null
事件示例(点击展开)
// Successful 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 navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}

购买恢复成功

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
事件示例(点击展开)
{
  "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 时关闭该页面。请参阅订阅状态了解如何进行检查。

恢复失败

如果 Adapty.restorePurchases() 执行失败,将会调用以下方法:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

升级订阅

当用户在已有订阅激活的情况下尝试购买新订阅时,你可以通过重写此方法来控制新购买的处理方式。你有两个选项:

  1. 用新订阅替换当前订阅
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
  1. 同时保留两个订阅(单独添加新订阅):
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}

如果你不覆盖此方法,默认行为是保持两个订阅同时有效(等同于使用 AdaptyPurchaseParameters.Empty)。

你也可以根据需要设置额外的购买参数:

AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}

数据获取与渲染

产品加载错误

如果你在初始化时未传入产品数据,AdaptyUI 会自行从服务器获取所需对象。若该操作失败,AdaptyUI 将调用以下方法来上报错误:

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
事件示例(点击展开)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

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

渲染错误

如果界面渲染过程中发生错误,系统会通过调用以下方法来上报:

public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}

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

系统返回按钮

默认情况下,用户无法通过系统返回按钮或返回手势退出流程——用户只能通过你定义的路径离开,例如 Close 按钮或编辑工具中的 on_device_back 动作。如果你希望系统返回按钮能够关闭流程,请重写 onBackPressed 并返回 false,将该操作交由宿主 Activity 或 Fragment 处理:

public override fun onBackPressed(context: Context): Boolean {
    return false // let the host handle the back press (e.g. finish the activity or pop the fragment)
}

仅当当前屏幕未配置 on_device_back 动作时,才会触发此回调——已配置的动作优先,由内部处理。返回 true 表示消费该返回事件(默认行为),返回 false 则让宿主自身的返回逻辑继续执行。

保留事件

AdaptyFlowEventListener 声明了一些回调,对应流程尚未使用的功能。你无需自行实现它们——AdaptyFlowDefaultEventListener 已经提供了默认的空操作实现。

方法描述
onAnalyticEvent为流程中的自定义分析事件预留。目前流程尚未向您的代码发送此类事件,因此无需实现。
onShowAppRate为流程中的应用评价请求预留。目前流程尚未触发应用评价请求,因此无需实现。
onShowRequestPermission为流程中的系统权限请求(如推送通知或摄像头访问)预留。目前流程尚未触发权限请求,因此无需实现。

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

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

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

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

如果需要控制或监控购买页面上发生的流程,请实现 AdaptyUiEventListener 的相关方法。

如果某些情况下希望保留默认行为,可以继承 AdaptyUiDefaultEventListener,只覆写需要更改的方法。

以下是 AdaptyUiDefaultEventListener 的默认行为。

用户触发事件

产品选择

当用户或系统选择某个产品进行购买时,将调用此方法:

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

已开始购买

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

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

购买成功、取消或待处理

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

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
事件示例(点击展开)
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

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

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

我们建议在这种情况下关闭当前页面。

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

购买失败

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

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

该方法在 Observer 模式下不会被调用。详情请参考 Android - 在 Observer 模式下展示付费墙编辑工具付费墙

完成网页支付导航

此方法在尝试为特定产品打开网页付费墙后触发,包括导航成功和失败两种情况:

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

参数:

参数说明
product打开网页付费墙时对应的 AdaptyPaywallProduct。可以为 null
error若网页付费墙跳转失败,则为 AdaptyError 对象;跳转成功时为 null
事件示例(点击展开)
// Successful 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 navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}

购买恢复成功

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
事件示例(点击展开)
{
  "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 时关闭该页面。请参阅订阅状态主题,了解如何进行检查。

恢复失败

如果 Adapty.restorePurchases() 失败,将调用此方法:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

升级订阅

事件示例(点击展开)
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}

数据获取与渲染

产品加载错误

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

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
事件示例(点击展开)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

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

渲染错误

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

public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
事件示例(点击展开)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

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