# Adapty Documentation (Full Content) > Complete documentation content across all platforms. Locale: zh Generated on: 2026-07-24T13:01:53.557Z --- # ANDROID - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.266Z Total files: 41 --- # File: sdk-installation-android --- --- title: "安装与配置 Android SDK" description: "在 Android 上为订阅类应用安装 Adapty SDK 的分步指南。" --- Adapty SDK 包含两个关键模块,帮助您无缝集成到移动应用中: - **Core Adapty**:这是核心 SDK,是 Adapty 正常运行的必要组件。 - **AdaptyUI**:如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码即可轻松创建跨平台付费墙的可视化工具),则需要此模块。AdaptyUI 会随核心模块一同自动激活。 :::tip 想看看 Adapty SDK 如何集成到真实移动应用中?查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app),其中展示了完整的配置流程,包括展示付费墙、进行购买以及其他基本功能。 ::: ## 系统要求 \{#requirements\} 最低 SDK 要求:`minSdkVersion 21` :::info Adapty 兼容 Google Play Billing Library 8.x 及以下版本。默认情况下,Adapty 使用 Google Play Billing Library v.7.0.0,但如果您希望强制使用更新版本,可以手动[添加依赖项](https://developer.android.com/google/play/billing/integrate#dependency)。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} 选择你的依赖配置方式: - 标准 Gradle:在 **模块级** `build.gradle` 中添加依赖 - 如果你的项目使用 `.gradle.kts` 文件,请在模块级 `build.gradle.kts` 中添加依赖 - 如果你使用版本目录,请在 `libs.versions.toml` 文件中添加依赖,然后在 `build.gradle.kts` 中引用它 [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Android.svg?style=flat&logo=android)](https://github.com/adaptyteam/AdaptySDK-Android/releases) ```groovy showLineNumbers dependencies { ... implementation platform('io.adapty:adapty-bom:') implementation 'io.adapty:android-sdk' // Only add this line if you plan to use Paywall Builder implementation 'io.adapty:android-ui' } ``` ```kotlin showLineNumbers dependencies { ... implementation(platform("io.adapty:adapty-bom:")) implementation("io.adapty:android-sdk") // Only add this line if you plan to use Paywall Builder: implementation("io.adapty:android-ui") } ``` ```toml showLineNumbers //libs.versions.toml [versions] .. adaptyBom = "" [libraries] .. adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } adapty = { module = "io.adapty:android-sdk" } // Only add this line if you plan to use Paywall Builder: adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... implementation(platform(libs.adapty.bom)) implementation(libs.adapty) // Only add this line if you plan to use Paywall Builder: implementation(libs.adapty.ui) } ``` 如果依赖无法解析,请确保你的 Gradle 脚本中包含 `mavenCentral()`。
添加方法说明 如果你的项目 `settings.gradle` 中没有 `dependencyResolutionManagement`,请在顶层 `build.gradle` 的 repositories 末尾添加以下内容: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` 否则,请将以下内容添加到 `settings.gradle` 中 `dependencyResolutionManagement` 部分的 `repositories` 里: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... mavenCentral() } } ```
:::important Adapty Android SDK 4.0 目前处于预发布阶段。Gradle 不会通过动态版本范围(如 `+` 或 `latest.release`)自动选择预发布版本,因此你必须手动指定确切版本。请将 `adapty-bom` 版本设置为 4.0 预发布版本,例如 `io.adapty:adapty-bom:4.0.0-beta.2`,或在 `libs.versions.toml` 中填写 `adaptyBom = "4.0.0-beta.2"`。BOM 会自动解析匹配的 `android-sdk` 和 `android-ui` 版本。详情请参阅[将 Adapty Android SDK 迁移至 v4](migration-to-android-sdk-v4)。 ::: ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} ### 基本设置 \{#basic-setup\} 在你的应用代码中激活 Adapty SDK。 :::note Adapty SDK 在应用中只需激活一次。 ::: 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 ```kotlin showLineNumbers // In your Application class class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ) } } ``` ```java showLineNumbers // In your Application class public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( getApplicationContext(), new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ); } } ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `Adapty.activate` 完成。完整的调用顺序请参阅 [Android SDK 调用顺序](android-sdk-call-order)。 ::: 接下来在应用中配置付费墙: - 如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请参阅[付费墙编辑工具快速入门](android-quickstart-paywalls)。 - 如果你自行构建付费墙界面,请参阅[自定义付费墙快速入门](android-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果您计划使用[付费墙编辑工具](adapty-paywall-builder),则需要 AdaptyUI 模块。当您激活核心模块时,它会自动激活,无需执行任何其他操作。 ## 配置 Proguard \{#configure-proguard\} 在将应用发布到生产环境之前,请将 `-keep class com.adapty.** { *; }` 添加到您的 Proguard 配置中。 ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解当前运行状态。可用的日志级别如下: | 级别 | 描述 | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.NONE` | 不记录任何日志。默认值 | | `AdaptyLogLevel.ERROR` | 仅记录错误日志 | | `AdaptyLogLevel.WARN` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息。 | | `AdaptyLogLevel.INFO` | 记录错误、警告和各类信息消息。 | | `AdaptyLogLevel.VERBOSE` | 记录调试时可能有用的所有附加信息,例如函数调用、API 请求等。 | 在配置 Adapty 之前,你可以在应用中设置日志级别。 ```kotlin showLineNumbers Adapty.logLevel = AdaptyLogLevel.VERBOSE //recommended for development and the first production release ``` ```java showLineNumbers Adapty.setLogLevel(AdaptyLogLevel.VERBOSE); //recommended for development and the first production release ``` #### 将日志系统消息重定向 \{#redirect-the-logging-system-messages\} 如果你需要将 Adapty 的日志消息发送到自己的系统或保存到文件中,可以覆盖默认行为: ```kotlin showLineNumbers Adapty.setLogHandler { level, message -> //handle the log } ``` ```java showLineNumbers Adapty.setLogHandler((level, message) -> { //handle the log }); ``` ### 数据政策 \{#data-policies\} Adapty 不会存储用户的个人数据,除非您明确发送,但您可以实施额外的数据安全政策,以遵守应用商店或国家/地区的相关规定。 #### 禁用 IP 地址收集与共享 \{#disable-ip-address-collection-and-sharing\} 激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 可禁用用户 IP 地址的收集与共享。默认值为 `false`。 使用此参数可以保护用户隐私、遵守地区数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据采集。 ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build(); ``` #### 禁用广告 ID(Ad ID)的收集与共享 \{#disable-advertising-id-ad-id-collection-and-sharing\} 激活 Adapty 模块时,将 `adIdCollectionDisabled` 设置为 `true` 可禁止收集用户的[广告 ID](https://support.google.com/googleplay/android-developer/answer/6048248)。默认值为 `false`。 使用此参数可遵守 Play Store 政策,避免触发广告 ID 权限提示,或在您的应用不需要基于广告 ID 进行广告归因或数据分析时使用。 ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build(); ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} 默认情况下,AdaptyUI 会缓存媒体(如图片和视频),以提升性能并减少网络请求。你可以通过自定义配置来调整缓存设置。 使用 `AdaptyUI.configureMediaCache` 可以覆盖默认的缓存大小和有效期。此步骤为可选——如果不调用此方法,将使用默认值(磁盘大小 100MB,有效期 7 天)。 ```kotlin showLineNumbers val cacheConfig = MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(3.days) .build() AdaptyUI.configureMediaCache(cacheConfig) ``` ```java showLineNumbers MediaCacheConfiguration cacheConfig = new MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(TimeInterval.days(3)) .build(); AdaptyUI.configureMediaCache(cacheConfig); ``` **参数:** | 参数 | 是否必填 | 描述 | |-------------------------|----------|-------------------------------------------------------| | diskStorageSizeLimit | 可选 | 磁盘缓存总大小,单位为字节。默认为 100 MB。 | | diskCacheValidityTime | 可选 | 缓存文件的有效期。默认为 7 天。 | :::tip 您可以在运行时使用 `AdaptyUI.clearMediaCache(strategy)` 清除媒体缓存,其中 `strategy` 可以是 `CLEAR_ALL` 或 `CLEAR_EXPIRED_ONLY`。 ::: ### 设置混淆账户 ID \{#set-obfuscated-account-ids\} Google Play 在某些场景下需要使用混淆账户 ID,以增强用户隐私与安全性。这些 ID 可帮助 Google Play 识别购买行为,同时保持用户信息匿名,对防欺诈和数据分析尤为重要。 如果您的应用处理敏感用户数据,或需要遵守特定的隐私法规,则可能需要设置这些 ID。混淆 ID 让 Google Play 能够追踪购买记录,而无需暴露真实的用户标识符。 ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build(); ``` ### 在自定义进程中运行 Adapty \{#run-adapty-in-a-custom-process\} 默认情况下,Adapty 只能在应用的主进程中运行。 如果你的应用使用多个进程,请只初始化 Adapty 一次,否则可能会出现意外行为。 如果需要在其他进程中运行 Adapty,请在配置中指定进程名称: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build(); ``` 如果你尝试在另一个进程中激活 Adapty 但未设置此值,SDK 将记录警告并跳过激活。 ### 启用本地访问等级 \{#enable-local-access-levels\} 默认情况下,Android 上的[本地访问等级](local-access-levels)是禁用的。要启用它,请将 `withLocalAccessLevelAllowed` 设置为 `true`: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build(); ``` ## 故障排查 \{#troubleshooting\} #### Android 备份规则(自动备份配置)\{#android-backup-rules-auto-backup-configuration\} 部分 SDK(包括 Adapty)会附带自己的 Android 自动备份配置。如果你同时使用多个定义了备份规则的 SDK,Android 清单合并工具可能会报错,提示涉及 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup`。 典型报错示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/sample_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` 要解决这个问题,你需要: - 告诉 manifest 合并工具使用你的应用中与备份相关属性的值。 - 将 Adapty 和其他 SDK 的备份规则合并到单个 XML 文件中(Android 12+ 可使用一对文件)。 #### 1. 将 `tools` 命名空间添加到你的 manifest \{#1-add-the-tools-namespace-to-your-manifest\} 如果尚未添加,请将 `tools` 命名空间添加到根标签 `` 中: ```xml ... ``` #### 2. 在 `` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在应用的 `AndroidManifest.xml` 中,更新 `` 标签,使应用提供最终值并告知清单合并工具替换库中的值: ```xml ... ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 `app/src/main/res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并在一起。由于 Android 在不同系统版本中使用不同的备份规则格式,同时创建两个文件可确保与你的应用所支持的所有 Android 版本兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请根据你在应用中使用的其他 SDK,替换或添加相应的规则。 ::: **适用于 Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" ``` **适用于 Android 11 及以下版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" ``` 完成此配置后: - Adapty 的备份排除项(`AdaptySDKPrefs.xml`)得以保留。 - 其他 SDK 的排除项(例如 `appsflyer-data`)同样生效。 - Manifest 合并器将使用您应用的配置,不再因备份属性冲突而报错。 #### 从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会错误地重建或复用该 Activity,导致购买结果丢失或被视为已取消。 为确保购买流程正常运行,请仅对启动购买流程的 Activity 使用 `standard` 或 `singleTop` 启动模式,避免使用其他模式。 在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml ``` --- # File: android-quickstart-paywalls --- --- title: "在 Android SDK 中使用 Flow Builder 启用购买功能" description: "使用 Adapty Flow Builder 启用应用内购买的快速入门指南。" --- 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可购买的任何内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 向用户展示产品的屏幕序列,使用无代码的 Flow Builder 构建,SDK 通过 `getFlow` 获取。如果你更倾向于用自己的代码构建 UI,请改用付费墙——参见[手动实现付费墙](android-quickstart-manual)。 - [**版位**](placements) – 在应用中展示流程的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 请求对应流程。这样可以轻松进行 A/B 测试,并向不同用户展示不同的流程。 Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |---|---|---| | Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可直接购买的流程](quickstart-paywalls)。Adapty 自动渲染并在后台处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 您在应用代码中实现付费墙 UI,但仍从 Adapty 获取流程对象,以保持产品offerings的灵活性。请参阅[指南](android-quickstart-manual)。 | | 观察者模式 | 🔴 复杂 | 您已有自己的购买处理基础设施,并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。请参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤介绍如何实现在 Adapty Flow Builder 中创建的流程。** 如果你更倾向于自行构建付费墙 UI,请参阅[手动实现付费墙](android-quickstart-manual)。 ::: 要在应用中显示在 Adapty Flow Builder 中创建的流程,你只需在代码中完成以下步骤: 1. **获取流程**:从 Adapty 获取流程。 2. **显示流程,Adapty 将自动处理购买**:在应用中展示视图。 3. **处理按钮操作**:将用户交互与应用的响应逻辑关联起来,例如在用户点击按钮时打开链接或关闭流程。 ## 开始之前 \{#before-you-start\} 开始之前,请完成以下步骤: 1. 在 Adapty 看板中[将应用连接到 Google Play](initial-android)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建流程并添加产品](create-paywall)。 4. [创建版位并添加流程](create-placement)。 5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-android)。本指南使用 Adapty Android SDK v4 API。 :::tip 完成这些步骤最快的方式是参考[快速入门指南](quickstart),或使用 [Developer CLI](developer-cli-quickstart) 创建流程和版位。 ::: ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。通过版位,你可以针对不同的目标受众运行不同的流程,或进行 [A/B 测试](ab-tests)。 要获取在 Adapty 流程编辑器中创建的流程,需要: 1. 使用 `getFlow` 方法,通过[版位](placements) ID 获取 `flow` 对象,并检查它是否包含视图配置。 2. 使用 `getFlowConfiguration` 方法获取视图配置。视图配置包含显示流程所需的 UI 元素和样式信息。 :::important 要获取视图配置,必须在 Flow Builder 中开启 **Show on device** 开关。否则将获得空的视图配置,流程将无法显示。 ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { val flow = result.value if (!flow.hasViewConfiguration) { return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); if (!flow.hasViewConfiguration()) { return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); // use loaded configuration } }); } }); ``` ## 2. 展示流程 \{#display-the-flow\} 获取到流程配置后,只需添加几行代码即可展示你的流程。 要在设备屏幕上渲染可视化流程,必须先对其进行配置。调用 `AdaptyUI.getFlowView()` 方法或直接创建 `AdaptyFlowView`: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, null, // products = null means auto-fetch eventListener, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener); ``` ```xml showLineNumbers ``` 视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示出来。 :::tip 有关如何显示 flow 的更多详情,请参阅我们的[指南](android-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#handle-button-actions\} 当用户点击流程中的按钮时,Android SDK 会自动处理购买、恢复、关闭流程和打开链接等操作。 不过,其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作,或者你可能希望覆盖其默认行为。 例如,以下是关闭按钮的默认行为。你不需要在代码中添加它,但如果有需要,可以参考这里的实现方式。 :::tip 阅读我们关于如何处理按钮[操作](android-handle-paywall-actions)和[事件](android-handling-events)的指南。 ::: ```kotlin showLineNumbers title="Kotlin" override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ```java showLineNumbers @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } ``` ## 下一步 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的流程已准备好在应用中展示。[在 Google Play Store 中测试购买](testing-on-android),确保您能从流程中完成测试购买。 接下来,您需要[检查用户的访问等级](android-check-subscription-status),确保向正确的用户展示流程或开放付费功能。 ## 完整示例 \{#full-example\} 以下是如何将所有步骤整合到你的应用中的完整示例。 ```kotlin showLineNumbers title="Kotlin" class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Adapty.getFlow("YOUR_PLACEMENT_ID") { flowResult -> if (flowResult is AdaptyResult.Success) { val flow = flowResult.value if (!flow.hasViewConfiguration) { // Use custom logic return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value val flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch object : AdaptyFlowDefaultEventListener() { override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Close -> { (context as? Activity)?.onBackPressed() } } } } ) setContentView(flowView) } } } } } } ``` ```java showLineNumbers public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Adapty.getFlow("YOUR_PLACEMENT_ID", flowResult -> { if (flowResult instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) flowResult).getValue(); if (!flow.hasViewConfiguration()) { // Use custom logic return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); AdaptyFlowView flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch new AdaptyFlowDefaultEventListener() { @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } } ); setContentView(flowView); } }); } }); } } ``` --- # File: android-check-subscription-status --- --- title: "在 Android SDK 中检查订阅状态" description: "了解如何使用 Adapty 在您的 Android 应用中检查订阅状态。" --- 要决定用户是否可以访问付费内容或查看付费墙,您需要检查用户画像中的[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种选择: - 如果需要立即获取最新的用户画像数据(例如在应用启动时)或想要强制更新,请调用 `getProfile`。 - 设置**自动用户画像更新**,以保存一份本地副本,该副本会在订阅状态发生变化时自动刷新。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方法是使用 `getProfile` 方法访问用户画像: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 要在应用中自动接收用户画像更新: 1. 使用 `Adapty.setOnProfileUpdatedListener()` 监听用户画像变化——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。 2. 在此方法被调用时存储更新后的用户画像数据,以便在整个应用中使用,而无需发起额外的网络请求。 ```kotlin class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true } } ``` ```java public class SubscriptionManager { private AdaptyProfile currentProfile; public SubscriptionManager() { // Listen for profile updates Adapty.setOnProfileUpdatedListener(profile -> { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() public boolean hasAccess() { if (currentProfile == null) { return false; } AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); return premiumAccess != null && premiumAccess.isActive(); } } ``` :::note Adapty 会在应用启动时自动调用用户画像更新监听器,即使设备处于离线状态,也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当您需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。此方法适用于应用启动、进入付费专区或显示特定内容之前等场景。 ```kotlin private fun initializePaywall() { loadPaywall { paywallView -> checkAccessLevel { result -> when (result) { is AdaptyResult.Success -> { if (!result.value && paywallView != null) { setContentView(paywallView) // Show paywall if no access } } is AdaptyResult.Error -> { if (paywallView != null) { setContentView(paywallView) // Show paywall if access check fails } } } } } } private fun checkAccessLevel(callback: ResultCallback) { Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true callback.onResult(AdaptyResult.Success(hasAccess)) } is AdaptyResult.Error -> { callback.onResult(AdaptyResult.Error(result.error)) } } } } ``` ```java private void initializePaywall() { loadPaywall(paywallView -> { checkAccessLevel(result -> { if (result instanceof AdaptyResult.Success) { boolean hasAccess = ((AdaptyResult.Success) result).getValue(); if (!hasAccess && paywallView != null) { setContentView(paywallView); // Show paywall if no access } } else if (result instanceof AdaptyResult.Error) { if (paywallView != null) { setContentView(paywallView); // Show paywall if access check fails } } }); }); } private void checkAccessLevel(ResultCallback callback) { Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); boolean hasAccess = premiumAccess != null && premiumAccess.isActive(); callback.onResult(AdaptyResult.success(hasAccess)); } else if (result instanceof AdaptyResult.Error) { callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError())); } }); } ``` ## 后续步骤 \{#next-steps\} 现在您已经了解如何追踪订阅状态,接下来请学习如何[使用用户画像](android-quickstart-identify),以确保用户能够访问他们已付费的内容。 --- # File: android-quickstart-identify --- --- title: "在 Android SDK 中识别用户" description: "在 Android 中设置 Adapty 进行应用内订阅管理的快速入门指南。" --- :::important 如果您有自己的身份验证系统,本指南适合您。在这里,您将学习如何在 Adapty 中管理用户画像,以确保其与您现有的身份验证系统保持一致。 ::: 您如何管理用户购买取决于您的应用的身份验证模型: - 如果您的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户部分](#anonymous-users)。 - 如果您的应用已有(或将有)后端身份验证,请参阅[已识别用户部分](#identified-users)。 **核心概念**: - **用户画像** 是 SDK 运行所需的实体。Adapty 会自动创建它们。 - 它们可以是匿名的**(无 customer user ID)**或已识别的**(有 customer user ID)**。 - 您提供 **customer user ID** 是为了将 Adapty 中的用户画像与您内部的身份验证系统进行关联。 以下是匿名用户和已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|---------------------------------------------------|-------------------------------------------------------------------------| | **购买管理** | 应用商店级别的购买恢复 | 通过 customer user ID 在设备间维护购买历史 | | **用户画像管理** | 每次重新安装时创建新的用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用安装间持续保存 | ## 匿名用户 \{#anonymous-users\} 如果您没有后端身份验证,**您无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时激活,Adapty **为用户创建一个新的用户画像**。 2. 当用户在应用中购买任何内容时,该购买将**与其 Adapty 用户画像和应用商店账户关联**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty **在激活时创建一个新的匿名用户画像**。 4. 如果用户之前在您的应用中进行过购买,默认情况下,他们的购买记录会在 SDK 激活时自动从 App Store 同步。 因此,对于匿名用户,每次安装都会创建新的用户画像,但这不是问题,因为在 Adapty 分析中,您可以[配置什么将被视为新安装](general#4-installs-definition-for-analytics)。 对于匿名用户,您需要按**设备 ID** 统计安装量。在这种情况下,设备上的每次应用安装(包括重新安装)都计为一次安装。 ## 已识别用户 \{#identified-users\} 您有两种方式在应用中识别用户: - [**在登录/注册时:**](#during-loginsignup) 如果用户在您的应用启动后登录,请在用户完成身份验证时使用 customer user ID 调用 `identify()`。 - [**在 SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时您已有存储的 customer user ID,请在调用 `activate()` 时传入它。 :::important 默认情况下,当 Adapty 收到一个当前与另一个 Customer User ID 关联的 Customer User ID 的购买时,访问等级将被共享,因此两个用户画像都具有付费访问权限。您可以配置此设置,将付费访问从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[此文章](general#6-sharing-paid-access-between-user-accounts)。 ::: ### 在登录/注册时 \{#during-loginsignup\} 如果您在应用启动后识别用户(例如,在他们登录或注册后),请使用 `identify` 方法设置他们的 customer user ID。 - 如果您**之前从未使用过此 customer user ID**,Adapty 将自动将其与当前用户画像关联。 - 如果您**之前已使用此 customer user ID 识别过用户**,Adapty 将切换到与该 customer user ID 关联的用户画像。 :::important 每位用户的 customer user ID 必须唯一。如果将该参数值硬编码,所有用户将被视为同一人。 ::: 等待 `identify` 的回调触发后,再调用其他 SDK 方法。并发调用可能会落到匿名用户画像上,而非已识别的用户画像。详见 [Android SDK 的调用顺序](android-sdk-call-order)。 ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // 每个用户的 ID 必须唯一 if (error == null) { // 识别成功 } } ``` ```java showLineNumbers // 每个用户的 ID 必须唯一 Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // 识别成功 } }); ``` ### 在 SDK 激活时 \{#during-the-sdk-activation\} 如果您在激活 SDK 时已知晓 customer user ID,可以在 `activate` 方法中传入,而无需单独调用 `identify`。 如果您知道 customer user ID 但仅在激活后才设置它,这意味着在激活时,Adapty 将创建一个新的匿名用户画像,只有在您调用 `identify` 后才会切换到现有用户画像。 您可以传入现有的 customer user ID(之前已使用过的)或新的 customer user ID。如果传入新的,激活时创建的新用户画像将自动与该 customer user ID 关联。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装次数基于设备 ID 统计。 设备 ID 代表从应用商店在设备上的一次应用安装,仅在应用重新安装后才会重新生成。 它不取决于是首次安装还是重复安装,也不取决于是否使用了现有的 customer user ID。 创建用户画像(在 SDK 激活或退出登录时)、登录或在不重新安装应用的情况下升级应用,都不会产生额外的安装事件。 如果您希望按唯一用户而非设备统计安装量,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build(); ``` ### 退出登录用户 \{#log-users-out\} 如果您有用于用户退出登录的按钮,请使用 `logout` 方法。 :::important 退出登录会为用户创建一个新的匿名用户画像。 ::: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录时购买 \{#allow-purchases-without-login\} 如果您的用户可以在登录前后都进行购买,您需要确保他们登录后仍能保留访问权限: 1. 当未登录用户进行购买时,Adapty 将购买与其匿名用户画像 ID 绑定。 2. 当用户登录账户时,Adapty 切换到其已识别的用户画像。 - 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 将 customer user ID 分配给当前用户画像,从而保留所有购买历史。 - 如果是现有的 customer user ID(该 customer user ID 已与一个用户画像关联),您需要在用户画像切换后获取实际的访问等级。您可以在识别完成后立即调用 [`getProfile`](android-check-subscription-status),或[监听用户画像更新](android-check-subscription-status)以便数据自动同步。 ## 后续步骤 \{#next-steps\} 恭喜!您已在应用中实现了应用内支付逻辑!祝您的应用变现一切顺利! 为了从 Adapty 中获取更多价值,您可以探索以下主题: - [**测试**](troubleshooting-test-purchases):确保一切按预期运行 - [**用户引导**](android-onboardings):通过用户引导吸引用户并提升留存率 - [**集成**](configuration):只需一行代码即可与营销归因和分析服务集成 - [**设置自定义用户画像属性**](android-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,以便针对不同用户发起 A/B 测试或展示不同付费墙 --- # File: adapty-sdk-integration-skill-android --- --- title: "使用 SDK 集成技能将 Adapty 接入 Android 应用" description: "使用 adapty-sdk-integration 技能,通过 AI 编码工具将 Adapty SDK 端到端集成到你的 Android 应用中。" --- [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 :::important 该技能目前处于测试阶段。如果出现卡顿或异常行为,请改用[分步集成指南](adapty-cursor-android)——它会引导您的 AI 工具逐步完成每个阶段所需的文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-android --- --- title: "借助 AI 将 Adapty 集成到您的 Android 应用" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具将 Adapty 集成到 Android 应用的分步指南。" --- 本指南将带你一步步将 Adapty 集成到 Android 应用中,借助 AI 编程工具完成整个流程——只需按正确顺序将相关 Adapty 文档喂给它即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始之前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。您可以使用交互式 LLM 技能来完成,也可以通过看板手动操作。 ### 技能方式(推荐) \{#skill-approach-recommended\} Adapty CLI 技能让您的 LLM 能够直接设置您的应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。您只需在看板中[连接您的应用商店](integrate-payments)即可。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在您的智能代理中运行 `/adapty-cli`。它将引导您完成每个步骤——包括何时需要打开看板连接应用商店。 ### 看板方式 \{#dashboard-approach\} 如果您更倾向于手动配置,以下是编写任何代码之前需要完成的内容。您的 LLM 无法为您查找看板中的值——您需要自行提供。 1. **连接您的应用商店**:在 Adapty 看板中,前往 **App settings → General**。这是购买功能正常运作的必要条件。 [连接 Google Play](integrate-payments) 2. **复制您的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这是您传递给 Adapty 配置构建器的字符串。 3. **创建至少一个产品**:在 Adapty 看板中,前往 **Products** 页面。您不需要在代码中直接引用产品——Adapty 通过付费墙来传递产品。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配给一个版位。在代码中,版位 ID 是您传递给 `Adapty.getPaywall("YOUR_PLACEMENT_ID")` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板中,在 **Products** 页面按产品进行配置。在代码中,通过 `profile.accessLevels["premium"]?.isActive` 检查该字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户根据产品获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编码之前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 一旦您完成上述五项配置,就可以开始编写代码了。告诉您的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",以便它能生成正确的初始化和付费墙获取代码。 ::: ### 准备就绪后再进行配置 \{#set-up-when-ready\} 以下内容在开始编码时并非必须,但随着集成的成熟您会需要它们: - **A/B 测试**:在 **Placements** 页面配置。无需更改代码。 [A/B 测试](ab-tests) - **更多付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。 - **分析集成**:在 **Integrations** 页面配置。具体设置因集成而异。参见[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 将 Adapty 文档提供给您的 LLM \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) \{#use-context7-recommended\} [Context7](https://context7.com) 是一个 MCP 服务器,可让您的 LLM 直接访问最新的 Adapty 文档。您的 LLM 会根据您的问题自动获取正确的文档——无需手动粘贴 URL。 Context7 适用于 **Cursor**、**Claude Code**、**Windsurf** 及其他兼容 MCP 的工具。运行以下命令进行设置: ``` npx ctx7 setup ``` 此命令会检测您的编辑器并配置 Context7 服务器。如需手动设置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the Android SDK ``` :::warning 即使 Context7 无需手动粘贴文档链接,实施顺序依然重要。请按照下方的[实施流程](#implementation-walkthrough)逐步操作,以确保一切正常运作。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 您可以以纯文本 Markdown 格式访问任何 Adapty 文档。在 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-android.md](https://adapty.io/docs/zh/adapty-cursor-android.md)。 下方[实施流程](#implementation-walkthrough)中的每个阶段都包含"发送给您的 LLM"代码块,其中包含可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下方的[索引文件和平台专属子集](#plain-text-doc-index-files)。 ## 实施流程 \{#implementation-walkthrough\} 本指南的其余部分按实施顺序介绍 Adapty 集成。每个阶段包含发送给 LLM 的文档、完成后应看到的内容,以及常见问题。 ### 规划您的集成 \{#plan-your-integration\} 在编写代码之前,请让您的 LLM 分析您的项目并制定实施计划。如果您的 AI 工具支持规划模式(例如 Cursor 或 Claude Code 的规划模式),请使用它,以便 LLM 在编写任何代码之前能同时读取您的项目结构和 Adapty 文档。 告诉您的 LLM 您使用哪种购买方式——这会影响它应遵循的指南: - [**Adapty 付费墙编辑工具**](adapty-paywall-builder):您在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染它们。 - [**手动创建的付费墙**](android-making-purchases):您在代码中构建自己的付费墙 UI,但仍使用 Adapty 获取产品和处理购买。 - [**Observer 模式**](observer-vs-full-mode):您保留现有的购买基础设施,仅使用 Adapty 进行分析和集成。 不确定选哪种?请阅读[快速入门中的对比表](android-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 通过 Android Studio 中的 Gradle 添加 Adapty SDK 依赖项,并使用您的 Public SDK key 激活它。这是基础——没有它,其他一切都无法正常运作。 **指南:**[安装并配置 Adapty SDK](sdk-installation-android) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-android.md ``` :::tip[检查点] - **预期结果:** 应用构建并运行。Logcat 显示 Adapty 激活日志。 - **注意事项:** "Public API key is missing" → 检查您是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 显示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙、显示它,并处理购买事件。所需的指南取决于您处理购买的方式。 在操作过程中逐个在沙盒中测试购买——不要等到最后再测试。请参阅[在沙盒中测试购买](test-purchases-in-sandbox)了解设置说明。 **指南:** - [使用付费墙启用购买(快速入门)](android-quickstart-paywalls) - [获取付费墙编辑工具付费墙及其配置](android-get-pb-paywalls) - [展示付费墙](android-present-paywalls) - [处理付费墙事件](android-handling-events) - [响应按钮操作](android-handle-paywall-actions) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/android-quickstart-paywalls.md - https://adapty.io/docs/zh/android-get-pb-paywalls.md - https://adapty.io/docs/zh/android-present-paywalls.md - https://adapty.io/docs/zh/android-handling-events.md - https://adapty.io/docs/zh/android-handle-paywall-actions.md ``` :::tip[检查点] - **预期结果:** 付费墙显示您配置的产品。点击产品会触发沙盒购买对话框。 - **注意事项:** 付费墙为空或 `getPaywall` 报错 → 验证版位 ID 与看板中完全一致,且版位已分配目标受众。 ::: **指南:** - [在自定义付费墙中启用购买(快速入门)](android-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-android) - [渲染由远程配置设计的付费墙](present-remote-config-paywalls-android) - [进行购买](android-making-purchases) - [恢复购买](android-restore-purchase) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/android-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-android.md - https://adapty.io/docs/zh/present-remote-config-paywalls-android.md - https://adapty.io/docs/zh/android-making-purchases.md - https://adapty.io/docs/zh/android-restore-purchase.md ``` :::tip[检查点] - **预期结果:** 您的自定义付费墙显示从 Adapty 获取的产品。点击产品会触发沙盒购买对话框。 - **注意事项:** 产品数组为空 → 验证付费墙在看板中已分配产品,且版位已分配目标受众。 ::: **指南:** - [Observer 模式概述](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-android) - [在 Observer 模式中上报交易](report-transactions-observer-mode-android) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode-android.md - https://adapty.io/docs/zh/report-transactions-observer-mode-android.md ``` :::tip[检查点] - **预期结果:** 使用您现有的购买流程完成沙盒购买后,交易出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 验证您已向 Adapty 上报交易,且 Google Play 实时开发者通知已配置。 ::: ### 检查订阅状态 \{#check-subscription-status\} 购买完成后,检查用户画像中的活跃访问等级,以控制高级内容的访问权限。 **指南:**[检查订阅状态](android-check-subscription-status) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/android-check-subscription-status.md ``` :::tip[检查点] - **预期结果:** 沙盒购买后,`profile.accessLevels["premium"]?.isActive` 返回 `true`。 - **注意事项:** 购买后 `accessLevels` 为空 → 检查产品在看板中是否已分配访问等级。 ::: ### 识别用户 \{#identify-users\} 将您的应用用户账户与 Adapty 用户画像关联,以便购买记录能在不同设备间持久保存。 :::important 如果您的应用没有身份验证功能,请跳过此步骤。 ::: **指南:**[识别用户](android-quickstart-identify) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/android-quickstart-identify.md ``` :::tip[检查点] - **预期结果:** 调用 `Adapty.identify("your-user-id")` 后,看板的 **Profiles** 部分显示您的自定义用户 ID。 - **注意事项:** 在激活之后、获取付费墙之前调用 `identify`,以避免匿名用户画像归因问题。 ::: ### 准备发布 \{#prepare-for-release\} 一旦您的集成在沙盒中正常运行,请按照发布清单检查,确保一切已为生产环境做好准备。 **指南:**[发布清单](release-checklist) 发送给您的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[检查点] - **预期结果:** 所有清单项目均已确认:应用商店连接、服务器通知、购买流程、访问等级检查以及隐私要求。 - **注意事项:** 缺少 Google Play 实时开发者通知 → 在 **App settings → Android SDK** 中配置,否则事件不会出现在看板中。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果您需要为 LLM 提供超出单个页面的更广泛上下文,我们提供了列出或合并所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面及其 `.md` 链接。这是[一种新兴标准](https://llmstxt.org/),旨在让网站对 LLM 更易访问。请注意,对于某些 AI 智能代理(例如 ChatGPT),您需要下载 `llms.txt` 并将其作为文件上传到聊天中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站点合并为一个文件。体积非常大——仅在需要完整内容时使用。 - Android 专属的 [`android-llms.txt`](https://adapty.io/docs/zh/android-llms.txt) 和 [`android-llms-full.txt`](https://adapty.io/docs/zh/android-llms-full.txt):与完整站点相比,平台专属子集可节省 token 用量。 --- # File: android-get-pb-paywalls --- --- title: "获取流程与付费墙 - Android" description: "在 Android 应用中从 Adapty 获取流程和付费墙。" --- 在[设计好流程或付费墙编辑工具付费墙](adapty-paywall-builder)之后,你可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体如下所述。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 :::
在移动应用中展示流程之前(点击展开) 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程/付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程/付费墙添加到其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-android)。
## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已经使用流程编辑器或付费墙编辑工具设计了流程或付费墙,则无需在移动应用代码中手动处理渲染逻辑来向用户展示它。此类流程或付费墙已包含展示内容和展示方式的完整定义。不过,你仍需通过版位获取其 ID 及视图配置,然后在移动应用中进行呈现。 为了确保最佳性能,请尽早获取流程或付费墙及其[视图配置](android-get-pb-paywalls#fetch-the-view-configuration),以便在向用户展示之前有足够的时间下载图片。 使用 `getFlow` 方法获取流程或付费墙: ```kotlin showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow/paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow/paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方案,因为它能确保用户始终获取最新数据。

但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种方式下用户可能无法获取最新数据,但无论网络状况如何,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。

请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会被清除。

Adapty SDK 通过两层机制在本地存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速获取,并配备了独立的备用服务器以应对 CDN 不可用的情况。整套系统旨在确保你始终获取最新版本,同时在网络条件较差的情况下也能保证可靠性。

| | **loadTimeout** | 默认值:5 秒 |

此值限制该方法的超时时间。达到超时时间后,将返回缓存数据或本地备用数据。

请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。

对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或者使用 `TimeInterval.seconds(5)`。如需不设限制,请使用 `TimeInterval.INFINITE`。

| | 参数 | 描述 | | :-------- | :---------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位信息、标识符(`id`、`variationId`)、名称、远程配置,以及 `hasViewConfiguration` 标志(用于指示该流程是否包含视图配置)。如需为预加载、自定义 UI 或程序化检查获取实际产品,请调用 `getPaywallProducts(flow)`。 | ## 获取视图配置 \{#fetch-the-view-configuration\} 获取流程或付费墙之后,通过 `flow.hasViewConfiguration` 检查其是否包含视图配置。该标志用于区分版位在 Adapty 看板中的设计方式: - **`true`** — 该版位是在 **Flow Builder**(流程)或 **付费墙编辑工具**(付费墙)中设计的,Adapty 会为您渲染 UI。请继续执行以下步骤,获取视图配置并[展示流程或付费墙](android-present-paywalls)。 - **`false`** — 该版位是没有编辑工具 UI 的自定义付费墙。[将其作为远程配置付费墙处理](present-remote-config-paywalls-android)。 :::important 请确保在 Flow Builder 中启用 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 使用 `getFlowConfiguration` 方法加载视图配置。 ```kotlin showLineNumbers if (!flow.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getFlowConfiguration(flow, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val flowConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | 参数 | 是否必填 | 描述 | | :-------------- | :----------------------------------------------- | :----------------------------------------------------------- | | **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | | **locale** |

可选

默认:设备语言

| [本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符,格式为语言代码,可包含一至两个以 `-` 分隔的子标签(例如 `en`、`pt-br`)。详见[本地化与语言代码](android-localizations-and-locale-codes)。 | | **loadTimeout** | 默认:5 秒 | 该参数限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。注意,在极少数情况下,由于该方法底层可能包含多个请求,实际超时时间可能略晚于 `loadTimeout` 指定的时间。 |
使用 `getFlowConfiguration` 方法加载视图配置。 ```java showLineNumbers if (!flow.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getFlowConfiguration(flow, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | | **locale** |

可选

默认值:设备语言

| [本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符,格式为由 `-` 分隔的一个或两个子标签的语言代码(例如 `en`、`pt-br`)。详见[本地化与语言代码](android-localizations-and-locale-codes)。 | | **loadTimeout** | 默认值:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,由于该操作在底层可能包含多个请求,实际超时时间可能略晚于 `loadTimeout` 中指定的值。 |
:::note 如果您使用多种语言,请了解如何添加[编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码,详见[此处](android-localizations-and-locale-codes)。 ::: 加载完成后,[展示流程或付费墙](android-present-paywalls)。 ## 获取默认目标受众的流程或付费墙以加速加载 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,流程和付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和版位,且用户的网络连接较差,获取流程或付费墙可能会比预期耗时更长。在这种情况下,你可能希望优先展示默认流程或付费墙,以保障流畅的用户体验,而不是让用户看到空白页面。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的流程或付费墙。但请务必了解,推荐的做法是通过 `getFlow` 方法来获取流程或付费墙,详情请参阅上方的[获取流程/付费墙](#fetch-flowpaywall)部分。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺点: - **潜在的向后兼容性问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的流程,可能会面临挑战。你要么设计出兼容当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能遇到流程无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的流程或付费墙获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。 ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

不过,如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这种方式下用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。

请注意,缓存在应用重启后仍会保留,仅在应用重新安装或手动清除时才会被清空。

| ## 自定义资源 \{#customize-assets\} 要自定义流程或付费墙中的图片和视频,请实现自定义资源。 主图和视频具有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,您可以通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,您可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在视频播放前显示预览图。 以下是通过简单字典提供自定义资源的示例: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, ) ``` :::note 如果找不到资源,流程将回退到其默认外观。 ::: 对于视频,您可以选择传入 `resolution`,在视频加载前预留布局空间并设置宽高比(`width / height`): ```kotlin showLineNumbers AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920), ) ```
在 [Adapty 看板中使用新版付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,你可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体方法如下。 :::warning 新版付费墙编辑工具需要 Android SDK 3.0 或更高版本。 ::: 请注意,本文档适用于通过付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙与产品](fetch-paywalls-and-products-android)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 :::
在移动应用中展示付费墙之前(点击展开) 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在你的移动应用中安装 [Adapty SDK](sdk-installation-android)。
## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动端代码中手动渲染并展示给用户。这类付费墙已经包含了展示内容和展示方式的完整配置。不过,你仍需通过版位获取其 ID、视图配置,然后在移动应用中将其呈现出来。 为确保最佳性能,请尽早获取付费墙及其[视图配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够的时间下载图片。 使用 `getPaywall` 方法获取付费墙: ```kotlin showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

但如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时优先返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。

请注意,缓存在应用重启后依然保留,只有在应用重新安装或手动清理时才会被清除。

Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙的加载,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保你始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。

| | **loadTimeout** | 默认值:5 秒 |

该值用于限制此方法的超时时间。超时后将返回缓存数据或本地备用数据。

请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作底层可能由多个请求组成。

对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。若不设置限制,请使用 `TimeInterval.INFINITE`。

| 响应参数: | 参数 | 描述 | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中开启 **Show on device** 开关。如果未开启该选项,将无法获取视图配置。 ::: 获取付费墙后,检查其是否包含 `ViewConfiguration`——这表明该付费墙是使用付费墙编辑工具创建的,并将指导你如何展示该付费墙。如果存在 `ViewConfiguration`,则将其视为付费墙编辑工具付费墙;否则,请[将其作为远程配置付费墙处理](present-remote-config-paywalls)。 使用 `getViewConfiguration` 方法加载视图配置。 ```kotlin showLineNumbers if (!paywall.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val viewConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | 参数 | 是否必填 | 说明 | | :-------------- | :------------- | :----------------------------------------------------------- | | **paywall** | 必填 | `AdaptyPaywall` 对象,用于获取目标付费墙的控制器。 | | **loadTimeout** | 默认:5 秒 | 该参数限制此方法的超时时间。若超时,将返回缓存数据或本地备用内容。注意:在极少数情况下,实际超时时间可能比 `loadTimeout` 中指定的略长,因为该操作在底层可能由多个请求组成。 | 使用 `getViewConfiguration` 方法加载视图配置。 ```java showLineNumbers if (!paywall.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.LocalizedViewConfiguration viewConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | | :----------------------- | :----------------- | :----------------------------------------------------------- | | **paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 | | **loadTimeout** | 默认值:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,此方法实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能由多个请求组成。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码,详见[此处](android-localizations-and-locale-codes)。 ::: 加载完成后,[展示付费墙](android-present-paywalls)。 ## 为默认目标受众获取付费墙以提升加载速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络状况较差,获取付费墙的时间可能会超出预期。在这种情况下,你可能希望先展示一个默认付费墙,以保证流畅的用户体验,而不是让用户看到空白。 为解决此问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中**所有用户**目标受众的付费墙。但请务必注意,推荐的做法是通过 `getPaywall` 方法获取付费墙,详见上方[获取付费墙信息](#fetch-paywall-designed-with-paywall-builder)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显缺陷: - **潜在的向后兼容问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么将付费墙设计为兼容当前(旧版)版本,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些不足之处以换取更快的付费墙加载速度,可以按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-paywall-designed-with-paywall-builder)介绍的 `getPaywall`。 ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note `getPaywallForDefaultAudience` 方法从 Android SDK 2.11.3 开始支持。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,如果请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——如果缓存数据存在,则直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。

请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清除时才会被清空。

| ## 自定义资源 \{#customize-assets\} 要自定义付费墙中的图片和视频,需要实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你可以通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 为部分用户展示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在播放视频前显示预览图。 :::important 要使用此功能,请将 Adapty Android SDK 更新至 3.7.0 或更高版本。 ::: 以下是如何通过简单字典提供自定义资源的示例: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, customAssets, ) ``` :::note 如果找不到某个资源,付费墙将回退到其默认外观。 :::
--- # File: android-present-paywalls --- --- title: "展示流程与付费墙 - Android" description: "在 Android 应用中向用户展示流程和付费墙。" --- 如果你已经创建了流程或付费墙,就不需要在移动应用代码中手动处理其渲染逻辑来将其展示给用户。这类流程或付费墙本身已包含展示内容及展示方式的完整定义。 :::warning 本指南适用于由 Adapty 渲染的流程和**新付费墙编辑工具付费墙**。远程配置付费墙和 [Observer 模式](observer-vs-full-mode)的处理方式有所不同。 - 有关展示**远程配置付费墙**,请参阅[展示远程配置设计的付费墙](present-remote-config-paywalls)。 - 有关展示**观察者模式付费墙**,请参阅[Android - 在观察者模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode) ::: 如需获取下文使用的 `flowConfiguration` 对象,请参阅[获取流程与付费墙](android-get-pb-paywalls)。 要在设备屏幕上显示可视化流程,必须先进行配置。调用 `AdaptyUI.getFlowView()` 方法或直接创建 `AdaptyFlowView`: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` 视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示。 如果你获取 `AdaptyFlowView` 时_没有_调用 `AdaptyUI.getFlowView()`,还需要额外调用 `.showFlow()` 方法。 要在设备屏幕上显示可视化流程,必须先进行配置。请使用以下可组合函数: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **flowConfiguration** | 必填 | 提供一个包含流程视觉详情的 `AdaptyUI.FlowConfiguration` 对象。使用 `AdaptyUI.getFlowConfiguration(flow)` 方法加载它。详情请参阅[获取视图配置](android-get-pb-paywalls#fetch-the-view-configuration)。 | | **products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 | | **eventListener** | 可选 | 提供一个 `AdaptyFlowEventListener` 来监听流程事件。建议继承 `AdaptyFlowDefaultEventListener` 以简化使用。详情请参阅[处理流程与付费墙事件](android-handling-events)。 | | **insets** | 可选 |

Insets 是流程周围的间距,用于防止可点击元素被系统状态栏遮挡。

默认值:`Unspecified`,即 Adapty 会自动调整 insets,这对边到边的流程效果很好。

如果你的流程不是边到边的,可能需要设置自定义 insets。具体方法请参阅下方的[修改流程 insets](android-present-paywalls#change-flow-insets) 部分。

| | **customAssets** | 可选 | 传入一个 `AdaptyCustomAssets` 对象,在运行时替换流程或付费墙中的图片和视频。详情请参阅[自定义资源](android-get-pb-paywalls#customize-assets)。 | | **tagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析流程文本中的自定义标签。该解析器接受标签参数并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 | | **timerResolver** | 可选 | 如果你需要使用自定义计时器功能,请在此传入对应的解析器。 | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 修改流程边距 \{#change-flow-insets\} 边距是指流程周围的空白区域,用于防止可点击元素被系统栏遮挡。默认情况下,Adapty 会自动调整边距,非常适合全面屏流程。 如果你的流程不是全面屏,可以自定义边距: - 如果状态栏和导航栏都不与 `AdaptyFlowView` 重叠,请使用 `AdaptyFlowInsets.None`。 - 对于更复杂的场景,例如流程与顶部状态栏重叠但不与底部重叠,可以仅将 `bottomInset` 设置为 `0`,如下例所示: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view flowView.onReceiveSystemBarsInsets { insets -> val flowInsets = AdaptyFlowInsets.vertical(insets.top, 0) flowView.showFlow( flowConfiguration, products, eventListener, flowInsets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(flowView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(flowView, null); AdaptyFlowInsets flowInsets = AdaptyFlowInsets.vertical(systemBarInsets.top, 0); flowView.showFlow(flowConfiguration, products, eventListener, flowInsets); return insets; }); ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请创建一个 `timerResolver` 对象——这是一个字典或映射,用于将自定义计时器与流程渲染时替换它们的字符串值进行配对。示例如下: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* 1小时后 */ } }; ``` 在此示例中,`CUSTOM_TIMER_NY` 是您在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。`timerResolver` 确保您的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(计算方式为计时器结束时间(如元旦)减去当前时间)。 ## 使用自定义标签 \{#use-custom-tags\} 要在移动应用中使用自定义标签,需要创建一个 `tagResolver` 对象——一个将自定义标签与渲染流程时用于替换的字符串值配对的字典或映射。示例如下: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` 在此示例中,`USERNAME` 是你在 Adapty 看板中以 `` 形式输入的自定义标签。`tagResolver` 会确保应用动态地将此自定义标签替换为指定的值——例如 `John`。 我们建议在呈现流程之前立即创建并填充 `tagResolver`。准备好后,将其传递给用于呈现流程的 AdaptyUI 方法。 ## 更改流程加载指示器颜色 \{#change-flow-loading-indicator-color\} 您可以通过以下方式覆盖加载指示器的默认颜色: ```xml showLineNumbers title = "XML" ```
如果您已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示。此类付费墙已包含展示内容及展示方式的全部配置。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 SDK v3.0。不同版本付费墙编辑工具设计的付费墙、远程配置付费墙以及 [Observer 模式](observer-vs-full-mode)的付费墙呈现流程各有不同。 - 如需呈现**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls)。 - 如需呈现 **Observer 模式付费墙**,请参阅 [Android - 在 Observer 模式下呈现付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。 ::: 要获取下文中使用的 `viewConfiguration` 对象,请参阅[获取付费墙编辑工具付费墙及其配置](android-get-pb-paywalls)。 要在设备屏幕上显示可视化付费墙,必须先对其进行配置。为此,调用 `AdaptyUI.getPaywallView()` 方法,或直接创建 `AdaptyPaywallView`: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` 视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示出来。 如果你获取 `AdaptyPaywallView` 的方式_不是_通过调用 `AdaptyUI.getPaywallView()`,还需要额外调用 `.showPaywall()` 方法。 要在设备屏幕上显示可视化付费墙,必须先对其进行配置。请使用以下可组合函数: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyUI.LocalizedViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅[获取付费墙的视觉配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)主题。 | | **products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 | | **eventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 以监听付费墙事件。推荐继承 AdaptyUiDefaultEventListener 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)主题。 | | **insets** | 可选 |

Insets 是付费墙周围的间距,用于防止可点击元素被系统栏遮挡。

默认值为 `UNSPECIFIED`,即 Adapty 将自动调整 insets,非常适合全屏边到边付费墙。

如果你的付费墙不是边到边布局,可能需要设置自定义 insets。具体方法请参阅下方的[更改付费墙 insets](android-present-paywalls#change-paywall-insets) 部分。

| | **personalizedOfferResolver** | 可选 | 如需标记个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver` 并传入你自己的逻辑,将 `AdaptyPaywallProduct` 映射为 true(表示该产品价格已个性化)或 false。 | | **tagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收一个标签参数,并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签主题。 | | **timerResolver** | 可选 | 如果你要使用自定义计时器功能,请在此处传入对应的解析器。 | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 更改付费墙边距 \{#change-paywall-insets\} 边距是付费墙周围的空白区域,用于防止可点击元素被系统栏遮挡。默认情况下,Adapty 会自动调整边距,这对全屏付费墙效果很好。 如果你的付费墙不是全屏布局,可能需要自定义边距: - 如果状态栏和导航栏都不与 `AdaptyPaywallView` 重叠,请使用 `AdaptyPaywallInsets.NONE`。 - 对于更复杂的自定义场景,例如付费墙与顶部状态栏重叠但不与底部重叠,可以仅将 `bottomInset` 设置为 `0`,如下例所示: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0) paywallView.showPaywall( viewConfiguration, products, eventListener, paywallInsets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, 0); paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver); return insets; }); ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请创建一个 `timerResolver` 对象——这是一个字典或映射,用于将自定义计时器与付费墙渲染时替换它们的字符串值进行配对。示例如下: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */ } }; ``` 在此示例中,`CUSTOM_TIMER_NY` 是您在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。`timerResolver` 确保您的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(由计时器结束时间(如元旦)减去当前时间计算得出)。 ## 使用自定义标签 \{#use-custom-tags\} 要在移动应用中使用自定义标签,需创建一个 `tagResolver` 对象——一个将自定义标签与渲染付费墙时用于替换它们的字符串值配对的字典或映射。示例如下: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` 在此示例中,`USERNAME` 是你在 Adapty 看板中以 `` 格式输入的自定义标签。`tagResolver` 会确保应用将该自定义标签动态替换为指定的值——例如 `John`。 建议在展示付费墙之前创建并填充 `tagResolver`。准备好后,将其传入用于展示付费墙的 AdaptyUI 方法。 ## 更改付费墙加载指示器颜色 \{#change-paywall-loading-indicator-color\} 您可以通过以下方式覆盖加载指示器的默认颜色: ```xml showLineNumbers title = "XML" ```
--- # File: android-handle-paywall-actions --- --- title: "响应流程操作 - Android" description: "在 Android 应用中处理流程和付费墙的按钮操作。" --- 如果你正在使用 Adapty 流程编辑工具或付费墙编辑工具构建流程或付费墙,正确设置按钮至关重要: 1. 在[编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理每个已分配的操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **只有购买、恢复购买、关闭流程/付费墙以及打开 URL 这些操作会自动处理。** 其他所有按钮操作都需要在应用代码中自行实现响应逻辑。 ::: ## 关闭流程和付费墙 \{#close-flows-and-paywalls\} 要添加一个用于关闭流程或付费墙的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,为 `close` 操作实现一个处理函数。 :::info 在 Android SDK 中,`close` 操作默认会触发关闭流程或付费墙。不过,如果需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。 ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加 **Link** 元素,并以与具有 **Open URL** 动作的按钮相同的方式处理它。 ::: 要添加一个从你的流程或付费墙中打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在编辑工具中,添加一个按钮,为其分配 **Open URL** 动作,并输入你想打开的 URL。 2. 在你的应用代码中,为 `openUrl` 动作实现一个处理程序,用于在浏览器中打开接收到的 URL。 :::info 在 Android SDK 中,`openUrl` 操作默认会触发打开 URL 的行为。不过,你可以在代码中根据需要覆盖此行为。 ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 如需添加一个处理其他操作的按钮: 1. 在编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并设置一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现对应的处理逻辑。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮,用于展示另一个流程或付费墙: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another flow or paywall } } } } ``` 如果您使用 Adapty 付费墙编辑工具构建付费墙,请务必正确设置按钮: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理你分配的每个操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **购买、恢复购买、关闭付费墙和打开 URL 会自动处理。** 其他所有按钮操作都需要在应用代码中实现相应的处理逻辑。 ::: ## 关闭付费墙 \{#close-paywalls\} 要添加一个关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,为 `close` 操作实现一个处理程序,用于关闭付费墙。 :::info 在 Android SDK 中,`close` 操作默认会触发关闭付费墙。不过,如果需要,你可以在代码中覆盖此行为。例如,关闭一个付费墙时可以触发打开另一个付费墙。 ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在付费墙编辑工具中添加 **Link** 元素,并像处理带有 **Open URL** 动作的按钮一样处理它。 ::: 要添加一个从付费墙打开链接的按钮(例如 **Terms of use** 或 **Privacy policy**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 动作,并输入你想打开的 URL。 2. 在应用代码中,为 `openUrl` 动作实现一个处理器,用于在浏览器中打开接收到的 URL。 :::info 在 Android SDK 中,`openUrl` 动作默认会触发打开 URL 的行为。不过,你也可以在代码中按需覆盖此行为。 ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## 登录应用 \{#log-into-the-app\} 要添加一个用于用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 操作。 2. 在应用代码中,实现 `login` 操作的处理逻辑,用于识别您的用户。 ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Login -> { val intent = Intent(context, LoginActivity::class.java) context.startActivity(intent) } } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并指定一个 ID。 2. 在你的应用代码中,为你创建的操作 ID 实现相应的处理逻辑。 例如,如果你有另一组订阅套餐或一次性购买商品,可以添加一个按钮来展示另一个付费墙: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another paywall } } } } ``` --- # File: android-handling-events --- --- title: "处理 Flow 与付费墙事件 - Android" description: "在 Android 应用中处理 Flow 与付费墙事件。" --- :::important 本指南涵盖购买、恢复、产品选择和流程渲染的事件处理。你还必须实现按钮处理(关闭流程、打开链接等)。详情请参阅我们的[按钮操作处理指南](android-handle-paywall-actions)。 ::: 使用[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)配置的流程和付费墙无需额外代码即可完成购买和恢复购买。不过,它们会产生一些可供应用响应的事件,包括按钮点击(关闭按钮、URL、产品选择等)以及购买相关操作的通知。请参阅以下内容了解如何响应这些事件。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: 如果需要控制或监控购买界面上发生的流程,请实现 `AdaptyFlowEventListener` 的各个方法。 如果您希望在某些情况下保留默认行为,可以继承 `AdaptyFlowDefaultEventListener` 并仅覆盖您想修改的方法。 以下是 `AdaptyFlowDefaultEventListener` 中的默认行为。 ### 用户生成的事件 \{#user-generated-events\} #### 选择产品 \{#product-selection\} 当用户或系统选择某个产品进行购买时,将调用以下方法: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### 开始购买 \{#started-purchase\} 当用户发起购买流程时,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
在 Observer 模式下,该方法不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。 #### 购买成功、取消或待处理 \{#successful-canceled-or-pending-purchase\} 如果购买成功,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
事件示例(点击展开) ```javascript // 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 - 在观察者模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode) 主题。 #### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,此方法将被调用。这包括 Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `onPurchaseFinished`(结果为已取消),而待处理的支付不会触发此方法。 ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "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 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode) 主题。 #### 完成 Web 支付导航 \{#finished-web-payment-navigation\} 此方法在尝试为特定产品打开 [Web 付费墙](web-paywall) 后调用,无论导航成功还是失败均会触发: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **参数:** | 参数 | 描述 | |:------------|:------------------------------------------------------------------------------------| | **product** | 打开网页付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 | | **error** | 如果网页付费墙导航失败,则为 `AdaptyError` 对象;导航成功时为 `null`。 |
事件示例(点击展开) ```javascript // 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" } } } ```
#### 购买恢复成功 \{#successful-restore\} 如果购买恢复成功,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "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` 时关闭该页面。请参阅[订阅状态](android-listen-subscription-changes)了解如何进行检查。 #### 恢复失败 \{#failed-restore\} 如果 `Adapty.restorePurchases()` 执行失败,将会调用以下方法: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### 升级订阅 \{#upgrade-subscription\} 当用户在已有订阅激活的情况下尝试购买新订阅时,你可以通过重写此方法来控制新购买的处理方式。你有两个选项: 1. **用新订阅替换当前订阅**: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **同时保留两个订阅**(单独添加新订阅): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note 如果你不覆盖此方法,默认行为是保持两个订阅同时有效(等同于使用 `AdaptyPurchaseParameters.Empty`)。 ::: 你也可以根据需要设置额外的购买参数: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ```
事件示例(点击展开) ```javascript { "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" } } ```
### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果你在初始化时未传入产品数据,AdaptyUI 会自行从服务器获取所需对象。若该操作失败,AdaptyUI 将调用以下方法来上报错误: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
事件示例(点击展开) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
如果返回 `true`,AdaptyUI 将在 2 秒后重新发起请求。 #### 渲染错误 \{#rendering-errors\} 如果界面渲染过程中发生错误,系统会通过调用以下方法来上报: ```kotlin showLineNumbers title="Kotlin" public override fun onError( error: AdaptyError, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ```
正常情况下不应出现此类错误,如果你遇到了,请告知我们。 ### 导航 \{#navigation\} #### 系统返回按钮 \{#system-back-button\} 默认情况下,用户无法通过系统返回按钮或返回手势退出流程——用户只能通过你定义的路径离开,例如 **Close** 按钮或编辑工具中的 `on_device_back` 动作。如果你希望系统返回按钮能够关闭流程,请重写 `onBackPressed` 并返回 `false`,将该操作交由宿主 Activity 或 Fragment 处理: ```kotlin showLineNumbers title="Kotlin" 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` 则让宿主自身的返回逻辑继续执行。 ### 保留事件 \{#reserved-events\} `AdaptyFlowEventListener` 声明了一些回调,对应流程尚未使用的功能。你无需自行实现它们——`AdaptyFlowDefaultEventListener` 已经提供了默认的空操作实现。 | 方法 | 描述 | |:-------|:------------| | **onAnalyticEvent** | 为流程中的自定义分析事件预留。目前流程尚未向您的代码发送此类事件,因此无需实现。 | | **onShowAppRate** | 为流程中的应用评价请求预留。目前流程尚未触发应用评价请求,因此无需实现。 | | **onShowRequestPermission** | 为流程中的系统权限请求(如推送通知或摄像头访问)预留。目前流程尚未触发权限请求,因此无需实现。 |
:::important 本指南介绍购买、恢复、产品选择和付费墙渲染的事件处理。你还需要实现按钮处理(关闭付费墙、打开链接等)。详情请参阅[按钮操作处理指南](android-handle-paywall-actions)。 ::: 通过[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上与购买相关操作的通知。请阅读以下内容,了解如何响应这些事件。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.0 或更高版本。 ::: :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: 如果需要控制或监控购买页面上发生的流程,请实现 `AdaptyUiEventListener` 的相关方法。 如果某些情况下希望保留默认行为,可以继承 `AdaptyUiDefaultEventListener`,只覆写需要更改的方法。 以下是 `AdaptyUiDefaultEventListener` 的默认行为。 ### 用户触发事件 \{#user-generated-events\} #### 产品选择 \{#product-selection\} 当用户或系统选择某个产品进行购买时,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### 已开始购买 \{#started-purchase\} 当用户发起购买流程时,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
该方法在 Observer 模式下不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。 #### 购买成功、取消或待处理 \{#successful-canceled-or-pending-purchase\} 购买成功时,将调用以下方法: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
事件示例(点击展开) ```javascript // 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 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。 #### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,将调用此方法。这包括 Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `onPurchaseFinished`(结果为 cancelled),待处理的支付则不会触发此方法。 ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "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 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。 #### 完成网页支付导航 \{#finished-web-payment-navigation\} 此方法在尝试为特定产品打开[网页付费墙](web-paywall)后触发,包括导航成功和失败两种情况: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **参数:** | 参数 | 说明 | |:------------|:-----------------------------------------------------------------------------------------| | **product** | 打开网页付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 | | **error** | 若网页付费墙跳转失败,则为 `AdaptyError` 对象;跳转成功时为 `null`。 |
事件示例(点击展开) ```javascript // 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" } } } ```
#### 购买恢复成功 \{#successful-restore\} 如果购买恢复成功,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "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` 时关闭该页面。请参阅[订阅状态](android-listen-subscription-changes)主题,了解如何进行检查。 #### 恢复失败 \{#failed-restore\} 如果 `Adapty.restorePurchases()` 失败,将调用此方法: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### 升级订阅 \{#upgrade-subscription\} 当用户在已有活跃订阅的情况下尝试购买新订阅时,你可以通过重写此方法来控制新购买的处理方式。你有两个选项: 1. **用新订阅替换当前订阅**: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **保留两个订阅**(单独添加新订阅): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note 如果不覆盖此方法,默认行为是保持两个订阅同时有效(等同于使用 `AdaptyPurchaseParameters.Empty`)。 ::: 您也可以根据需要设置额外的购买参数: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ``` 如果在某个订阅仍处于活跃状态时购买了新订阅,可覆盖此方法,将当前订阅替换为新订阅。如果希望保留原有的活跃订阅,同时单独添加新订阅,则调用 `onSubscriptionUpdateParamsReceived(null)`: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingSubscriptionUpdateParams( product: AdaptyPaywallProduct, context: Context, onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, ) { onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) } ```
事件示例(点击展开) ```javascript { "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" } } ```
### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果你在初始化时没有传入产品,AdaptyUI 会自动从服务器获取所需对象。若该操作失败,AdaptyUI 将调用以下方法来报告错误: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
事件示例(点击展开) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
如果返回 `true`,AdaptyUI 将在 2 秒后重试请求。 #### 渲染错误 \{#rendering-errors\} 如果在界面渲染过程中发生错误,系统将通过调用以下方法来上报: ```kotlin showLineNumbers title="Kotlin" public override fun onRenderingError( error: AdaptyError, context: Context, ) {} ```
事件示例(点击展开) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
正常情况下不应出现此类错误,如果遇到,请告知我们。
--- # File: android-use-fallback-paywalls --- --- title: "Android - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况。" --- :::warning 备用付费墙需要 Android SDK v2.11 及更高版本支持。 ::: 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} 1. 将备用配置文件移动到 Android 项目的 `assets` 或 `res/raw` 目录中。 2. 在获取目标流程、付费墙或用户引导**之前**,调用 `.setFallback` 方法。 ```kotlin showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory val location = FileLocation.fromAsset("android_fallback.json") //or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory val location = FileLocation.fromResId(context, R.raw.android_fallback) //you can also pass a file URI val fileUri: Uri = //get Uri for the file with fallback paywalls val location = FileLocation.fromFileUri(fileUri) //pass the file location Adapty.setFallback(location, callback) ``` ```java showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory FileLocation location = FileLocation.fromAsset("android_fallback.json"); //or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback); //you can also pass a file URI Uri fileUri = //get Uri for the file with fallback paywalls FileLocation location = FileLocation.fromFileUri(fileUri); //pass the file location Adapty.setFallback(location, callback); ``` 参数: | 参数 | 描述 | | :----------- | :----------------------------------------------------------- | | **location** | 备用配置文件的 [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) 对象 | --- # File: android-localizations-and-locale-codes --- --- title: "在 Android SDK 中使用本地化和语言区域代码" description: "管理应用本地化和语言区域代码,触达全球用户(Android)。" --- ## 为什么这很重要 \{#why-this-is-important\} 语言区域代码会在多种场景下发挥作用——例如,当您尝试为应用的当前本地化版本获取正确的付费墙时。 由于语言区域代码较为复杂,且在不同平台间存在差异,我们为所有支持的平台制定了统一的内部标准。然而,正因为这些代码较为复杂,了解您究竟向服务器发送了什么内容以获取正确的本地化版本,以及后续发生了什么,对您来说非常重要——这样您才能始终获得预期的结果。 ## Adapty 的语言区域代码标准 \{#locale-code-standard-at-adapty\} Adapty 使用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag)作为语言区域代码规范:每个代码由小写子标签组成,以连字符分隔。示例如下:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 发来的包含语言区域代码的请求,并开始查找付费墙对应本地化版本时,将执行以下步骤: 1. 传入的语言区域字符串转换为小写,所有下划线(`_`)替换为连字符(`-`) 2. 查找与完整语言区域代码完全匹配的本地化版本 3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 对应 `pt`),并再次查找匹配的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 通过这种方式,发送了 `'pt_BR'` 的 iOS 设备、发送了 `pt-BR` 的 Android 设备,以及发送了 `pt-br` 的其他设备,都将获得相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果您正在考虑本地化问题,很可能已经在项目中处理了本地化字符串文件。在这种情况下,我们建议在每个本地化对应的字符串文件中,添加一个键值对来存储预期的 Adapty 语言区域代码,然后在调用 SDK 时提取该键对应的值,如下所示: ```kotlin showLineNumbers // 1. Modify your strings.xml files /* strings.xml - Spanish */ es /* strings.xml - Portuguese (Brazil) */ pt-br // 2. Extract and use the locale code val localeCode = context.getString(R.string.adapty_paywalls_locale) // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 这样,您可以完全掌控每位应用用户将获取的本地化版本。 ## 实现本地化:其他方式 \{#implementing-localizations-the-other-way\} 您也可以在不为每个本地化版本显式定义语言区域代码的情况下,获得类似(但不完全相同)的结果。这意味着需要从平台提供的其他对象中提取语言区域代码,如下所示: ```kotlin showLineNumbers val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.resources.configuration.locales[0] else context.resources.configuration.locale val localeCode = locale.toLanguageTag() // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 请注意,我们不推荐此方式,因为很难预测 Adapty 服务器实际接收到的内容。 如果您仍决定使用此方式,请确保已覆盖所有相关的使用场景。 --- # File: android-web-paywall --- --- title: "在 Android SDK 中实现网页付费墙" description: "设置网页付费墙,无需支付 Play Store 费用和审核即可收款。" --- :::important 在开始之前,请确保您已[在看板中配置了网页付费墙](web-paywall),并安装了 Adapty SDK 3.15 或更高版本。 ::: ## 打开网页付费墙 \{#open-web-paywalls\} 如果您使用的是自行开发的付费墙,则需要通过 SDK 方法来处理网页付费墙。`.openWebPaywall` 方法会: 1. 生成一个唯一 URL,使 Adapty 能够将向特定用户展示的付费墙与其被重定向到的网页关联起来。 2. 追踪用户返回应用的时机,并以短时间间隔请求 `.getProfile`,以判断用户画像的访问权限是否已更新。 这样一来,如果付款成功且访问权限已更新,订阅几乎会立即在应用中激活。 :::note 用户返回应用后,请刷新 UI 以反映用户画像的更新。Adapty 将接收并处理用户画像更新事件。 ::: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error } } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall(product)`:通过付费墙生成 URL,并将产品数据添加到 URL 中。 2. `openWebPaywall(paywall)`:通过付费墙生成 URL,但不将产品数据添加到 URL 中。当 Adapty 付费墙中的产品与网页付费墙中的产品不同时,请使用此版本。 ::: ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} 默认情况下,网页付费墙会在外部浏览器中打开。 为提供无缝的用户体验,您可以在应用内浏览器中打开网页付费墙。这样会在您的应用内显示网页购买页面,让用户无需切换应用即可完成交易。 要启用此功能,请将 `presentation` 参数设置为 `AdaptyWebPresentation.InAppBrowser`: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, presentation = AdaptyWebPresentation.InAppBrowser, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error val adaptyError = error } } ``` --- # File: android-troubleshoot-paywall-builder --- --- title: "在 Android SDK 中排查付费墙编辑工具问题" description: "在 Android SDK 中排查付费墙编辑工具问题" --- 本指南帮助您解决在 Android SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:`getViewConfiguration` 方法无法获取付费墙配置。 **原因**:该付费墙未在付费墙编辑工具中启用设备显示。 **解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。 ## 付费墙展示次数过多 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙展示次数显示为预期值的两倍。 **原因**:你可能在代码中调用了 `logShowFlow`(Android SDK v4+)/ `logShowPaywall`,如果你使用的是付费墙编辑工具或 Flow Builder,这会导致展示次数重复计算。对于使用这些工具构建的流程和付费墙,分析数据会自动追踪,无需手动调用此方法。 **解决方案**:如果你使用的是付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(Android SDK v4+)/ `logShowPaywall`。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。 **解决方案**:如有需要,请参考[迁移指南](android-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: android-quickstart-manual --- --- title: "在 Android SDK 的自定义付费墙中启用购买功能" description: "将 Adapty SDK 集成到自定义 Android 付费墙中,以启用应用内购买功能。" --- 本指南介绍如何将 Adapty 集成到自定义付费墙中。你可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。 :::important **本指南适用于需要自行实现自定义付费墙的开发者。** 如果你希望以最简便的方式开启购买功能,请使用 [Adapty Flow Builder](android-quickstart-paywalls)。使用 Flow Builder,你可以在无代码可视化编辑器中创建流程,Adapty 自动处理所有购买逻辑,并且无需重新发布应用即可测试不同的设计方案。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,你需要了解三个关键概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,但这种设计让你无需修改应用代码就能调整产品、价格和优惠。 - [**版位**](placements) – 应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码里通过版位 ID 请求对应的付费墙。这样就能轻松运行 A/B 测试,并向不同用户展示不同的付费墙。 即使你使用自定义付费墙,也需要了解这些概念。简单来说,它们就是你在应用中管理所售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到一个**版位**。这样才能获取你的产品。如果想了解需要在看板中完成哪些操作,请参考[这里](quickstart)的快速入门指南。 ### 管理用户 \{#manage-users\} 您可以选择在您的后端使用或不使用身份验证。 但是,Adapty SDK 对匿名用户和已识别用户的处理方式不同。请阅读[用户识别快速入门指南](android-quickstart-identify)以了解具体细节,确保您正确处理用户信息。 ## 第一步:获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,你需要: 1. 通过将[版位](placements) ID 传递给 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该流程的产品数组。 ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value Adapty.getPaywallProducts(flow) { productResult -> when (productResult) { is AdaptyResult.Success -> { val products = productResult.value // Use products to build your custom paywall UI } is AdaptyResult.Error -> { val error = productResult.error // Handle the error } } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); Adapty.getPaywallProducts(flow, productResult -> { if (productResult instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) productResult).getValue(); // Use products to build your custom paywall UI } else if (productResult instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) productResult).getError(); // Handle the error } }); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## 步骤 2. 接受购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,请调用 `makePurchase` 方法并传入所选产品。该方法会处理购买流程并返回更新后的用户画像。 ```kotlin showLineNumbers fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) { Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) { Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // 购买成功,用户画像已更新 } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // 用户取消了购买 } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // 购买待处理(例如,用户将以现金线下付款) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // 处理错误 } }); } ``` ## 第三步:恢复购买 \{#step-3-restore-purchases\} Google Play 及其他应用商店要求所有包含订阅功能的应用提供让用户恢复购买的方式。 当用户点击恢复购买按钮时,调用 `restorePurchases` 方法。该方法会将用户的购买历史与 Adapty 同步,并返回更新后的用户画像。 ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Restore successful, profile updated } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void restorePurchases() { Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // Restore successful, profile updated } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。[在 Google Play Store 上测试您的购买流程](testing-on-android),确保您可以从付费墙完成测试购买。如需了解生产环境中的完整实现方式,请参阅我们示例应用中的 [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt),其中演示了包含完善错误处理、UI 反馈和订阅管理的购买处理逻辑。 接下来,[检查用户是否已完成购买](android-check-subscription-status),以判断是否需要展示付费墙或开放付费功能。 --- # File: fetch-paywalls-and-products-android --- --- title: "在 Android SDK 中获取远程配置付费墙的付费墙和产品" description: "在 Adapty Android SDK 中获取付费墙和产品,提升用户变现效果。" --- 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本文内容涉及远程配置和自定义付费墙。如需了解如何获取在 **Flow Builder** 或 **Paywall Builder** 中配置的流程或付费墙,请参阅[获取流程与付费墙](android-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 :::
在开始获取流程和产品之前(点击展开) 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程或付费墙,并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位,并将流程或付费墙添加到版位中](create-placement)。 4. 在移动应用中[安装 Adapty SDK](sdk-installation-android)。
## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合体。这些跨平台产品被整合到流程和付费墙中,让你可以在移动应用的特定版位中展示它们。 要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。流程是远程配置的,因此产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果一个流程今天返回两个产品,明天返回三个,无需修改代码即可全部展示。 ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符,即您在 Adapty 看板中创建版位时所指定的值。 || **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这种情况下用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。

请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会清除。

Adapty SDK 通过两个层级存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](android-use-fallback-paywalls)。我们还使用 CDN 加速流程和付费墙的获取,并在 CDN 不可达时提供独立的备用服务器。

| | **loadTimeout** | 默认值:5 秒 |

此值限制该方法的超时时间。若超时,将返回缓存数据或本地备用数据。

请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能包含多个请求。

| 不要硬编码产品 ID!由于流程是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果之后获取到 3 个产品,应用无需修改任何代码即可显示全部 3 个产品。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、`remoteConfigs` 数组(每个已配置语言区域对应一条记录)以及 `hasViewConfiguration` 标志。如需获取该 flow 的产品,请调用 `getPaywallProducts(flow)`。 | :::note 在 v4 中,`locale` 参数已从 `getFlow` 移至 `getFlowConfiguration`(仅在使用 AdaptyUI 渲染时使用)。对于自定义付费墙,所有可用的语言区域将一并在 `flow.remoteConfigs` 中返回——请选择与用户设备或应用设置相匹配的语言区域。 ::: ## 获取产品 \{#fetch-products\} 获取流程后,你可以查询与其对应的产品数组: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(flow, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 | 在实现自定义流程设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象中的相关属性。以下列出了最常用的属性,完整属性列表请参阅上方链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 | | **Price** | 要显示本地化价格,请使用 `product.price.localizedString`。该本地化基于设备的语言区域信息。你也可以通过 `product.price.amount` 以数字形式获取价格,该值以当地货币为单位。要获取对应的货币符号,请使用 `product.price.currencySymbol`。 | | **Subscription Period** | 要显示订阅周期(如周、月、年等),请使用 `product.subscriptionDetails?.localizedSubscriptionPeriod`。该本地化基于设备的语言区域。若需以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。通过该属性可访问 `unit` 枚举以获取周期单位(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `MONTH`,`numberOfUnits` 属性值为 `3`。 | | **Introductory Offer** | 要显示徽标或其他指示器来表明订阅包含新用户优惠,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。这是一个列表,最多可包含两个折扣阶段:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下实用属性:
• `paymentMode`:枚举类型,取值为 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。
• `price`:折扣价格(数字形式)。免费试用时该值为 `0`。
• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `3 days`。
• `subscriptionPeriod`:也可通过此属性获取优惠周期的详细信息,其使用方式与上一节关于订阅周期的描述相同。
• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期字符串。 | ## 通过默认目标受众流程加速流程加载 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流程的加载几乎是即时完成的,无需为此担心。但如果你配置了大量目标受众和版位,且用户的网络连接较差,流程加载可能会比预期慢。在这种情况下,你可能希望展示一个默认流程,以确保用户体验流畅,而不是什么都不显示。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程。但需要特别注意的是,推荐的方式是通过 `getFlow` 方法来获取流程,详情请参阅上文的[获取流程信息](fetch-paywalls-and-products-android#fetch-flow-information)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺陷: - **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)展示不同的流程,可能会面临挑战。您要么必须设计能够支持当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能无法正常渲染流程的风险。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个流程,这意味着您将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的流程获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-android#fetch-flow-information)中介绍的 `getFlow`。 ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 || **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这样一来,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。

请注意,重启应用不会清除缓存,只有卸载重装或手动清理才会清空缓存。

|
在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题适用于远程配置和自定义付费墙。如需了解如何获取付费墙编辑工具自定义付费墙的相关指导,请参阅[获取付费墙编辑工具的付费墙及其配置](android-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 :::
在应用中开始获取付费墙和产品之前(点击展开) 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位中](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-android)。
## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被集成到付费墙中,让你可以在移动应用的特定版位展示它们。 要展示产品,你需要通过 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果付费墙今天返回两个产品,明天返回三个,则应在不修改代码的情况下全部展示。 ::: ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐用法,请参阅[本地化与语言区域代码](android-localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。

但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。

请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清理时才会被清除。

Adapty SDK 通过两层机制存储付费墙:上述定期更新的缓存,以及[备用付费墙](android-use-fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时提供独立的备用服务器。这套机制旨在确保您始终能获取最新版本的付费墙,同时在网络条件较差时也能保持可靠性。

| | **loadTimeout** | 默认值:5 秒 |

该值限制了此方法的超时时间。若超时,将返回缓存数据或本地备用数据。

请注意,在少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能包含多个不同的请求。

| 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品的数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果后来获取到 3 个产品,应用无需修改代码即可显示全部 3 个。唯一需要硬编码的是版位 ID。 返回参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,你可以查询与其对应的产品数组: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(paywall, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象中的以下属性。下面列出的是最常用的属性,完整的属性说明请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **标题** | 使用 `product.localizedTitle` 显示产品名称。请注意,本地化内容基于用户在应用商店选择的国家/地区,而非设备本身的语言设置。 | | **价格** | 使用 `product.price.localizedString` 显示本地化价格,该本地化基于设备的语言区域信息。你也可以通过 `product.price.amount` 以数字形式获取价格,值以本地货币为单位。要获取对应的货币符号,请使用 `product.price.currencySymbol`。 | | **订阅周期** | 使用 `product.subscriptionDetails?.localizedSubscriptionPeriod` 显示周期(如周、月、年等),该本地化基于设备的语言区域。如需以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。通过该属性可访问 `unit` 枚举,获取周期长度(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 表示周期单位的数量。例如,季度订阅的 unit 属性为 `MONTH`,numberOfUnits 属性为 `3`。 | | **新用户优惠** | 如需显示"包含新用户优惠"的徽章或其他标识,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。该列表最多包含两个折扣阶段:免费试用阶段和优惠价格阶段。每个阶段对象包含以下实用属性:
• `paymentMode`:枚举值,包括 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。
• `price`:以数字表示的折扣价格。免费试用时,该值为 `0`。
• `localizedNumberOfPeriods`:根据设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段显示为 `3 days`。
• `subscriptionPeriod`:也可通过此属性获取优惠周期的具体详情,其使用方式与上文描述的订阅周期相同。
• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期。 | ## 通过默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙的获取几乎是即时完成的,无需特别担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络连接较差,获取付费墙可能会比预期耗时更长。在这种情况下,你可能希望先展示一个默认付费墙,以确保用户体验流畅,而不是什么都不显示。 要解决这个问题,你可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的付费墙。但需要特别注意的是,推荐的做法是通过 `getPaywall` 方法来获取付费墙,详情请参阅上方的[获取付费墙信息](fetch-paywalls-and-products-android#fetch-paywall-information)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显缺陷: - **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么将付费墙设计成兼容当前(旧版)版本,要么接受使用当前(旧版)版本的用户可能会遇到付费墙无法渲染的问题。 - **精准定向缺失**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些不足之处,以换取更快的付费墙加载速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请使用上文介绍的 `getPaywall` 方法([详见上文](fetch-paywalls-and-products-android#fetch-paywall-information))。 ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note `getPaywallForDefaultAudience` 方法从 Android SDK 2.11.3 版本开始支持。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](android-localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此选项,因为它能确保用户始终获取最新数据。

不过,如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时优先返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免额外的网络请求是安全可靠的。

请注意,缓存在应用重启后依然保留,仅在重新安装应用或手动清除时才会被清空。

|
--- # File: present-remote-config-paywalls-android --- --- title: "在 Android SDK 中渲染远程配置设计的付费墙" description: "了解如何在 Adapty Android SDK 中展示远程配置付费墙以个性化用户体验。" --- 如果你使用远程配置自定义了付费墙,则需要在移动应用代码中自行实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,付费墙的内容和外观都取决于你的设计。Adapty 提供了获取远程配置的方法,让你能够自主展示自定义付费墙。 ## 获取 Flow 远程配置并展示 \{#get-flow-remote-config-and-present-it\} 在 v4 中,一个 flow 在 `remoteConfigs` 数组中为每个已配置的语言环境携带一个 `AdaptyRemoteConfig` 条目。选取与用户偏好匹配的语言环境,然后读取所需的值。 ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); AdaptyRemoteConfig config = null; for (AdaptyRemoteConfig remoteConfig : flow.getRemoteConfigs()) { if ("en".equals(remoteConfig.getLocale())) { config = remoteConfig; break; } } if (config == null && !flow.getRemoteConfigs().isEmpty()) { config = flow.getRemoteConfigs().get(0); } if (config != null && config.getDataMap().get("header_text") instanceof String) { String headerText = (String) config.getDataMap().get("header_text"); } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 至此,一旦您获取了所有必要的数据,就可以将其渲染并组装成一个美观的页面。请确保设计能够适配各种手机屏幕尺寸和方向,为不同设备提供流畅且友好的用户体验。 :::warning 请务必按照以下说明[记录付费墙浏览事件](present-remote-config-paywalls-android#track-paywall-view-events),以便 Adapty 分析系统能够采集漏斗和 A/B 测试所需的数据。 ::: 完成付费墙的展示后,继续设置购买流程。当用户发起购买时,只需使用流程中的产品调用 `.makePurchase()` 方法即可。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](android-making-purchases)。 我们建议[创建一个备用付费墙](android-use-fallback-paywalls)。当用户没有网络连接或无可用缓存时,备用付费墙会自动展示,确保用户在任何情况下都能获得流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 帮助你衡量流程和付费墙的表现。购买数据会自动收集,但浏览事件需要你手动记录,因为只有你知道用户何时看到了某个流程。 要记录浏览事件,只需调用 `.logShowFlow(flow)`,相关数据便会反映在漏斗和 A/B 测试的数据指标中。 :::important 如果你使用 [Flow Builder](adapty-flow-builder) 或 [付费墙编辑工具](adapty-paywall-builder) 渲染流程或付费墙,则无需调用 `.logShowFlow(flow)`。Adapty 在这些情况下会自动记录浏览行为。 ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:-----------------------------------------------------------------------------------------| | **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | 如果你通过远程配置自定义了付费墙,则需要在移动应用代码中实现渲染逻辑,才能将其展示给用户。远程配置具有高度灵活性,完全由你掌控——付费墙包含哪些内容、界面如何呈现,都取决于你的设计。我们提供了获取远程配置的方法,让你能够自主展示通过远程配置搭建的自定义付费墙。 ## 获取付费墙远程配置并呈现 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig(); if (remoteConfig != null) { if (remoteConfig.getDataMap().get("header_text") instanceof String) { String headerText = (String) remoteConfig.getDataMap().get("header_text"); } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 此时,一旦您获取了所有必要的值,就可以将它们渲染并组合成一个视觉上吸引人的页面。请确保设计能够适配各种手机屏幕尺寸和方向,在不同设备上提供流畅且用户友好的体验。 :::warning 请务必按照下方说明[记录付费墙查看事件](present-remote-config-paywalls-android#track-paywall-view-events),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 展示付费墙后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请阅读[发起购买](android-making-purchases)。 我们建议[创建一个备用付费墙(即备用付费墙)](android-use-fallback-paywalls)。当没有网络连接或缓存可用时,该备用付费墙将向用户展示,确保在这些情况下依然提供流畅的体验。 ## 追踪付费墙查看事件 \{#track-paywall-view-events\} Adapty 帮助您衡量付费墙的效果。虽然我们会自动收集购买数据,但记录付费墙查看事件需要您的参与,因为只有您才知道用户何时看到了付费墙。 要记录付费墙查看事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是在[付费墙编辑工具](adapty-paywall-builder)中创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:------------------------------------------------------------------------------------------------------------| | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象。 | --- # File: android-making-purchases --- --- title: "在 Android SDK 中进行应用内购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙是向用户提供高级内容或服务访问权限的重要步骤。然而,仅仅展示付费墙只有在您使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙时,才足以支持购买流程。 如果您不使用付费墙编辑工具,则必须使用名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成所需交易的入口。 如果您的付费墙对用户尝试购买的产品设有有效的促销活动,Adapty 将在购买时自动应用该优惠。 :::warning 请注意,新用户优惠只有在您使用付费墙编辑工具设置付费墙时才会自动应用。 在其他情况下,您需要[验证用户是否符合 iOS 上新用户优惠的资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布审核时被拒绝,还可能对符合新用户优惠资格的用户收取全价。 ::: 请确保您已[完成初始配置](quickstart),不要跳过任何步骤。没有完成配置,我们将无法验证购买。 ## 进行购买 \{#make-purchase\} :::note **正在使用[付费墙编辑工具](adapty-paywall-builder)?** 购买将自动处理——您可以跳过此步骤。 **需要分步指导?** 请查阅[快速入门指南](android-implement-paywalls-manually),获取包含完整上下文的端到端实现说明。 ::: ```kotlin showLineNumbers Adapty.makePurchase(activity, product, null) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` ```java showLineNumbers Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // Grant access to the paid features } } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Handle the case where the user canceled the purchase } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Handle deferred purchases (e.g., the user will pay offline with cash) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` 请求参数: | 参数 | 必要性 | 描述 | | :------------ | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象。 | 响应参数: | 参数 | 描述 | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

如果请求成功,响应将包含此对象。[AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象提供了关于用户访问等级、订阅及应用内非订阅购买的全面信息。

请检查访问等级状态,以确认用户是否拥有访问应用所需的权限。

| :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。此方法目前已被 Apple 弃用。 ::: ## 购买时更改订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而不是续订当前订阅时,具体行为取决于应用商店。对于 Google Play,订阅不会自动更新。您需要按照以下说明在移动应用代码中管理切换操作。 要在 Android 中将订阅替换为另一个订阅,请使用附加参数调用 `.makePurchase()` 方法: ```kotlin showLineNumbers Adapty.makePurchase( activity, product, AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build() ) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` 附加请求参数: | 参数 | 必要性 | 描述 | | :----------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | 必填 | 一个 [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/) 对象。 | ```java showLineNumbers Adapty.makePurchase( activity, product, new AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build(), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // successful cross-grade } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // user canceled the purchase flow } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // the purchase has not been finished yet, e.g. user will pay offline by cash } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` 附加请求参数: | 参数 | 必要性 | 描述 | | :----------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | 必填 | 一个 [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/) 对象。 | 您可以在 Google 开发者文档中了解更多关于订阅和替换模式的内容: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 对替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更将在当前订阅计费周期结束时才会发生。 ### 管理预付费套餐 \{#manage-prepaid-plans\} 如果您的应用用户可以购买[预付费套餐](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月的非续订订阅),您可以为预付费套餐启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。 ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build(); ``` --- # File: android-restore-purchase --- --- title: "在 Android SDK 中恢复移动应用内的购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 恢复购买是一项允许用户重新获得之前购买内容(例如订阅或应用内购买)访问权限的功能,且无需再次付费。该功能对于那些可能卸载后重新安装了应用,或切换到新设备并希望访问之前购买内容而无需再次付款的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您的情况属于此类,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```kotlin showLineNumbers Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); if (profile != null) { AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // successful access restore } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 响应参数: | 参数 | 描述 | |---------|-----------| | **Profile** |

一个 [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象。该模型包含访问等级、订阅及非订阅购买的相关信息。

请检查**访问等级状态**以确定用户是否有权访问该应用。

| :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: implement-observer-mode-android --- --- title: "在 Android SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以在 Android SDK 中追踪用户订阅事件。" --- 如果您已经拥有自己的购买基础设施,并且还未准备好完全切换到 Adapty,您可以探索[观察者模式](observer-vs-full-mode)。在其基本形式下,观察者模式提供高级分析功能,并可与归因和分析系统无缝集成。 如果这满足您的需求,您只需要: 1. 在配置 Adapty SDK 时通过将 `observerMode` 参数设置为 `true` 来开启该模式。请按照 [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk) 的设置说明进行操作。 2. 将您现有购买基础设施中的[交易上报](report-transactions-observer-mode-android)给 Adapty。 ## 观察者模式设置 \{#observer-mode-setup\} 如果您自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请开启观察者模式。 :::important 在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保您自行处理这些交易。 ::: ```kotlin showLineNumbers class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ) } ``` ```java showLineNumbers public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ); } ``` 参数: | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | observerMode | 一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在观察者模式中使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还希望使用 Adapty 的付费墙和 A/B 测试功能,您可以这样做——但在观察者模式下需要一些额外的设置。除上述步骤外,您还需要执行以下操作: 1. 按照常规方式展示[远程配置付费墙](present-remote-config-paywalls-android)。对于付费墙编辑工具付费墙,请参阅 [Android](android-present-paywall-builder-paywalls-in-observer-mode) 的专项设置指南。 3. 将付费墙与购买交易[关联](report-transactions-observer-mode-android)。 --- # File: report-transactions-observer-mode-android --- --- title: "在 Android SDK 的观察者模式下报告交易" description: "在 Android SDK 的 Adapty 观察者模式下报告购买交易,以获取用户洞察和收入追踪。" --- 在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店手动报告交易。在发布应用之前务必完成此设置,以避免分析数据出现错误。 使用 `reportTransaction` 显式报告每笔交易,以便 Adapty 识别。 :::warning **请勿跳过交易报告!** 如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。 ::: 如果您使用 Adapty 付费墙,请在报告交易时包含 `variationId`。这会将购买与触发该购买的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```kotlin showLineNumbers val transactionInfo = TransactionInfo.fromPurchase(purchase) Adapty.reportTransaction(transactionInfo, variationId) { result -> if (result is AdaptyResult.Success) { // success } } ``` 参数: | 参数 | 是否必填 | 描述 | | --------------- | -------- | ------------------------------------------------------------ | | transactionInfo | 必填 | 来自购买的 TransactionInfo,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | ```java showLineNumbers TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); Adapty.reportTransaction(transactionInfo, variationId, result -> { if (result instanceof AdaptyResult.Success) { // success } }); ``` 参数: | 参数 | 是否必填 | 描述 | | --------------- | -------- | ------------------------------------------------------------ | | transactionInfo | 必填 | 来自购买的 TransactionInfo,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | 在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店报告交易或恢复购买。在发布应用之前务必完成此设置,以避免分析数据出现错误。 使用 `restorePurchases` 向 Adapty 报告交易。 :::warning **请勿跳过购买恢复!** 如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。 ::: 如果您使用 Adapty 付费墙,请使用 `setVariationId` 方法将交易与触发购买的付费墙关联起来。这可确保购买被正确归因到触发付费墙,从而获得准确的分析数据。此步骤仅在您使用 Adapty 付费墙时才有必要。 ```kotlin showLineNumbers Adapty.restorePurchases { result -> if (result is AdaptyResult.Success) { // success } } Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | 购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { // success } }); Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | 购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | **报告交易** 使用 `restorePurchases` 在观察者模式下向 Adapty 报告交易,详情请参阅[在移动端代码中恢复购买](android-restore-purchase)页面。 :::warning **请勿跳过交易报告!** 如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。 ::: **将付费墙与交易关联** 由于您负责处理购买,Adapty SDK 无法确定购买来源。因此,如果您打算在观察者模式下使用付费墙和/或 A/B 测试,则需要在移动应用代码中将来自应用商店的交易与相应的付费墙关联起来。在发布应用之前务必正确完成此设置,否则将导致分析数据出现错误。 ```kotlin Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` 请求参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | 购买的字符串标识符(purchase.getOrderId),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | ```java Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | 购买的字符串标识符(purchase.getOrderId),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 | | variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | --- # File: android-present-paywall-builder-paywalls-in-observer-mode --- --- title: "在 Android SDK 中以观察者模式呈现付费墙编辑工具创建的付费墙" description: "了解如何使用 Adapty 的付费墙编辑工具在观察者模式下呈现付费墙。" --- 如果你使用流程编辑工具或付费墙编辑工具创建了流程或付费墙,无需在移动端代码中手动处理渲染逻辑,即可将其展示给用户。这类流程或付费墙本身已包含展示内容和展示方式的完整定义。 :::warning 本节仅适用于[观察者模式](observer-vs-full-mode)。如果你不使用观察者模式,请参阅 [Android - 展示流程与付费墙](android-present-paywalls)。 :::
开始展示流程之前(点击展开) 1. 完成 Adapty [与 Google Play 的初始集成](initial-android)。 2. 安装并配置 Adapty SDK,确保将 `observerMode` 参数设置为 `true`。请参阅我们针对特定框架的说明 [Android 版](sdk-installation-android)。 3. 在 Adapty 看板中[创建产品](create-product)。 4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。 5. 在 Adapty 看板中[创建版位并为其分配流程或付费墙](create-placement)。 6. 在移动应用代码中[获取流程及其配置](android-get-pb-paywalls)。

1. 实现 `AdaptyUiObserverModeHandler`。 当用户发起购买时,`onPurchaseInitiated` 事件会通知你。你可以在此回调中触发自定义的购买流程: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, flow, flowView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, flow, flowView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` 要在观察者模式下处理恢复操作,请重写 `getRestoreHandler()`。默认情况下它返回 `null`,此时会使用 Adapty 内置的 `Adapty.restorePurchases()` 流程。如需自定义恢复逻辑: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` 记得调用以下回调函数,以便将购买或恢复流程的状态通知 AdaptyUI。这对于正确的流程行为(例如显示加载器)是必要的: | 回调 | 描述 | | :----------------- |:---------------------------------------------------------------------------------------| | onStartPurchase() | 应调用此回调以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 应调用此回调以通知 AdaptyUI 购买已完成。 | | onStartRestore() | 可选。可调用此回调以通知 AdaptyUI 恢复已开始。 | | onFinishRestore() | 可选。可调用此回调以通知 AdaptyUI 恢复已完成。 | 2. 要在设备屏幕上显示可视化流程,必须先对其进行配置。 为此,请调用 `AdaptyUI.getFlowView()` 方法,或直接创建 `AdaptyFlowView`: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` 视图成功创建后,即可将其添加到视图层级并显示。 使用以下可组合函数: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` 请求参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **flowConfiguration** | 必填 | 提供一个包含流程视觉详情的 `AdaptyUI.FlowConfiguration` 对象。使用 `AdaptyUI.getFlowConfiguration(flow)` 方法加载该对象。详情请参阅[获取视图配置](android-get-pb-paywalls#fetch-the-view-configuration)。 | | **products** | 选填 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在页面上的显示时机。若传入 `null`,AdaptyUI 将自动获取所需产品。 | | **eventListener** | 选填 | 提供一个 `AdaptyFlowEventListener` 以监听流程事件。建议继承 `AdaptyFlowDefaultEventListener` 以简化使用。详情请参阅[处理流程与付费墙事件](android-handling-events)。 | | **insets** | 选填 | Insets 是流程四周的空白区域,用于防止可点击元素被系统栏遮挡。默认值为 `Unspecified`,即由 Adapty 自动调整 insets。请参阅[更改流程 insets](android-present-paywalls#change-flow-insets)。 | | **customAssets** | 选填 | 传入一个 `AdaptyCustomAssets` 对象,以在运行时替换流程或付费墙中的图片和视频。详情请参阅[自定义资源](android-get-pb-paywalls#customize-assets)。 | | **tagResolver** | 选填 | 使用 `AdaptyUiTagResolver` 解析流程文本中的自定义标签。该解析器接收标签参数并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 | | **observerModeHandler** | Observer 模式下必填 | 您在上一步中实现的 `AdaptyUiObserverModeHandler`。 | :::warning 不要忘记[将付费墙与购买交易关联](report-transactions-observer-mode-android)。否则,Adapty 将无法确定购买的来源流程。 :::
开始展示付费墙之前(点击展开) 1. 设置 Adapty 与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。 2. 安装并配置 Adapty SDK。确保将 `observerMode` 参数设置为 `true`。请参阅我们针对 [Android](sdk-installation-android) 的框架专属说明。 3. 在 Adapty 看板中[创建产品](create-product)。 4. 在 Adapty 看板中[配置付费墙、为其分配产品](create-paywall),并使用付费墙编辑工具对其进行自定义。 5. 在 Adapty 看板中[创建版位并为其分配付费墙](create-placement)。 6. 在移动应用代码中[获取付费墙编辑工具创建的付费墙及其配置](android-get-pb-paywalls)。

1. 实现 `AdaptyUiObserverModeHandler`。 `onPurchaseInitiated` 事件将通知您用户已发起购买。您可以在此回调中触发自定义的购买流程: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` 要在观察者模式下处理恢复操作,请重写 `getRestoreHandler()`。默认情况下它返回 `null`,此时会使用 Adapty 内置的 `Adapty.restorePurchases()` 流程。如需自定义恢复逻辑,请按以下方式实现: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` 请记得调用以下回调以通知 AdaptyUI 购买或恢复流程。这对于正确的付费墙行为(例如显示加载动画)是必要的: | 回调函数 | 说明 | | :----------------- |:---------------------------------------------------------------------------------------| | onStartPurchase() | 调用此回调函数以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 调用此回调函数以通知 AdaptyUI 购买已完成。 | | onStartRestore() | 可选。调用此回调函数以通知 AdaptyUI 恢复购买已开始。 | | onFinishRestore() | 可选。调用此回调函数以通知 AdaptyUI 恢复购买已完成。 | 2. 要在设备屏幕上显示可视化付费墙,您必须先对其进行配置。 为此,请调用 `AdaptyUI.getPaywallView()` 方法或直接创建 `AdaptyPaywallView`: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` 视图成功创建后,您可以将其添加到视图层级中并显示。 使用以下可组合函数: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, ) ``` 请求参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **Products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的展示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 | | **ViewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅[获取付费墙的视觉配置](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 | | **EventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 来监听付费墙事件。推荐继承 `AdaptyUiDefaultEventListener` 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)。 | | **PersonalizedOfferResolver** | 可选 | 如需标识个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver`,并传入自定义逻辑:若产品价格为个性化定价则返回 true,否则返回 false。 | | **TagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收一个标签参数,并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 | | **ObserverModeHandler** | Observer 模式下必填 | 即你在上一步中实现的 `AdaptyUiObserverModeHandler`。 | | **variationId** | 必填 | 实验变体的字符串标识符。可通过 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | | **transaction** | 必填 |

iOS,StoreKit 1:[`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。

iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。

Android:购买的字符串标识符(`purchase.getOrderId()`),其中 purchase 是 billing library [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。

|
开始呈现付费墙之前(点击展开) 1. 设置 Adapty 与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。 2. 安装并配置 Adapty SDK。确保将 `observerMode` 参数设置为 `true`。请参阅我们针对 [Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) 和 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk) 的框架专属说明。 3. 在 Adapty 看板中[创建产品](create-product)。 4. 在 Adapty 看板中[配置付费墙、为其分配产品](create-paywall),并使用付费墙编辑工具对其进行自定义。 5. 在 Adapty 看板中[创建版位并为其分配付费墙](create-placement)。 6. 在移动应用代码中[获取付费墙编辑工具创建的付费墙及其配置](android-get-pb-paywalls)。
1. 实现 `AdaptyUiObserverModeHandler`。`AdaptyUiObserverModeHandler` 的回调(`onPurchaseInitiated`)将在用户发起购买时通知您。您可以在此回调中触发自定义的购买流程: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` 同时,请记得向 AdaptyUI 调用这些回调。这对于正确的付费墙行为(例如显示加载动画等)是必要的: | Kotlin 中的回调 | Java 中的回调 | 描述 | | :----------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | | onStartPurchase() | onStartPurchase.invoke() | 应调用此回调以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | onFinishPurchase.invoke() | 应调用此回调以通知 AdaptyUI 购买已成功完成、失败或已取消。 | 2. 要显示可视化付费墙,必须先对其进行初始化。为此,请调用 `AdaptyUI.getPaywallView()` 方法,或直接创建 `AdaptyPaywallView`: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler, ) //======= OR ======= val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { setEventListener(eventListener) setObserverModeHandler(observerModeHandler) showPaywall( viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver, tagResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler ); //======= OR ======= AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver); ``` ```xml showLineNumbers ``` 视图成功创建后,您可以将其添加到视图层级并显示。 请求参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 | | **ViewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅 [获取付费墙的视觉配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 | | **Insets** | 必填 | 定义一个 `AdaptyPaywallInsets` 对象,包含系统栏遮挡区域的信息,用于为内容创建垂直边距。如果状态栏和导航栏都不遮挡 `AdaptyPaywallView`,则传入 `AdaptyPaywallInsets.NONE`。在系统栏覆盖部分 UI 的全屏模式下,请按表格下方的说明获取 insets。 | | **EventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 以监听付费墙事件。建议继承 `AdaptyUiDefaultEventListener` 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)。 | | **PersonalizedOfferResolver** | 可选 | 如需标记个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver`,并传入自定义逻辑:若某产品的价格为个性化定价则返回 true,否则返回 false。 | | **TagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收标签参数并将其解析为对应字符串。详情请参阅付费墙编辑工具中的自定义标签相关文档。 | | **ObserverModeHandler** | Observer 模式下必填 | 您在上一步骤中实现的 `AdaptyUiObserverModeHandler`。 | | **variationId** | 必填 | 实验变体的字符串标识符。可通过 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | | **transaction** | 必填 |

iOS,StoreKit 1:[`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。

iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。

Android:购买记录的字符串标识符(`purchase.getOrderId()`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。

| 对于全屏模式下系统状态栏遮挡部分界面的情况,请通过以下方式获取插入值: ```kotlin showLineNumbers import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom) paywallView.setEventListener(eventListener) paywallView.setObserverModeHandler(observerModeHandler) paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver) } ``` ```java showLineNumbers import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom); paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver); return insets; }); ``` 返回值: | 对象 | 说明 | | :------------------ | :------------------------------------------------- | | `AdaptyPaywallView` | 代表所请求付费墙界面的对象。 | :::warning 不要忘记[将付费墙关联到购买交易](report-transactions-observer-mode-android)。否则,Adapty 将无法确定购买来源的付费墙。 :::
--- # File: android-troubleshoot-purchases --- --- title: "排查 Android SDK 中的购买问题" description: "排查 Android SDK 中的购买问题" --- 本指南帮助您解决在 Android SDK 中手动实现购买时遇到的常见问题。 ## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **问题**:`makePurchase` 方法成功完成,但用户的用户画像和订阅状态未在 Adapty 中更新。 **原因**:这通常表明 Google Play Store 设置不完整或存在配置问题。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\} **问题**:同一次购买中 `makePurchase` 方法被多次调用。 **原因**:这通常发生在由于 UI 状态管理问题或用户快速操作导致购买流程被多次触发时。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## 观察者模式下出现 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时收到 `AdaptyError.cantMakePayments`。 **原因**:在观察者模式下,您应在自己这侧处理购买,而不是使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 进行购买,请关闭观察者模式。您需要二选一:要么使用 `makePurchase`,要么在观察者模式下自行处理购买。详情请参见[实现观察者模式](implement-observer-mode-android)。 ## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **问题**:您收到来自 Google Play Store 的计费不可用错误。 **原因**:此错误与 Adapty 无关,它是 Google Play 计费库的错误,表示设备上计费功能不可用。 **解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查看并了解更多信息:[处理 BillingResult 响应代码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。 ## 找不到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:遇到找不到 `makePurchasesCompletionHandlers` 的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建一个新的沙盒用户并重试。这通常可以解决与沙盒相关的购买完成处理程序问题。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他购买相关问题。 **解决方案**:如有需要,请按照[迁移指南](android-sdk-migration-guides)将 SDK 迁移至最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: android-identifying-users --- --- title: "在 Android SDK 中识别用户" description: "在 Adapty 中识别用户,提升个性化订阅体验(Android)。" --- Adapty 会为每位用户创建一个内部 Profile ID。但如果你有自己的身份验证系统,建议设置自定义的 Customer User ID。你可以在[用户画像](profiles-crm)部分通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它——该 ID 会同步发送至所有集成。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果您在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```kotlin showLineNumbers Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID") ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 在配置完成后设置客户用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以在之后任意时刻通过 `.identify()` 方法进行设置。最常见的使用场景是在注册或登录之后,当用户从匿名状态切换为已认证状态时。 ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> if (error == null) { // successful identify } } ``` ```java showLineNumbers Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重复提交重要用户数据 在某些情况下,例如用户重新登录账户时,Adapty 服务器可能已经存有该用户的信息。此时,Adapty SDK 会自动切换到新用户。如果你曾向匿名用户提交过任何数据(例如自定义属性或来自第三方网络的归因数据),需要为已识别的用户重新提交这些数据。 另外请注意,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 退出登录与重新登录 \{#logging-out-and-logging-in\} 你可以随时调用 `.logout()` 方法退出当前用户: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` 之后可以调用 `.identify()` 方法重新登录用户。 ### 跨设备用户识别 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: android-setting-user-attributes --- --- title: "在 Android SDK 中设置用户属性" description: "了解如何在 Adapty 中设置用户属性,以实现更好的目标受众细分。" --- 您可以为应用的用户设置可选属性,例如电子邮件、电话号码等。您可以使用这些属性来创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } ``` ```java showLineNumbers AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(new AdaptyProfile.Date(1970, 1, 3)); Adapty.updateProfile(builder.build(), error -> { if (error != null) { // handle the error } }); ``` 请注意,之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 的允许键 `` 及其对应值 `` 如下所示: | 键 | 值 | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | 枚举,允许的值为:`female`、`male`、`other` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性,这些属性通常与您的应用使用情况相关。例如,对于健身应用,可以是每周锻炼次数;对于语言学习应用,可以是用户的知识水平等。您可以在市场细分中使用这些属性来创建针对性的付费墙和优惠,也可以在数据分析中用于找出哪些产品指标对营收影响最大。 ```kotlin showLineNumbers builder.withCustomAttribute("key1", "value1") ``` ```java showLineNumbers builder.withCustomAttribute("key1", "value1"); ``` 要删除已有的键,请使用 `.withRemoved(customAttributeForKey:)` 方法: ```kotlin showLineNumbers builder.withRemovedCustomAttribute("key2") ``` ```java showLineNumbers builder.withRemovedCustomAttribute("key2"); ``` 有时您可能需要查看已设置的自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能不是最新的,因为用户属性可以随时从不同设备发送,服务器上的属性可能在上次同步后已发生变化。 ::: ### 限制 \{#limits\} - 每个用户最多 30 个自定义属性 - 键名最长 30 个字符,键名可包含字母数字字符及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,最多 50 个字符。 --- # File: android-listen-subscription-changes --- --- title: "在 Android SDK 中检查订阅状态" description: "在 Adapty 中跟踪和管理用户订阅状态,提升 Android 应用的客户留存率。" --- 借助 Adapty,跟踪订阅状态变得轻而易举。您无需在代码中手动插入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可轻松确认其订阅状态。 在开始检查订阅状态之前,请先配置[实时开发者通知 (RTDN)](enable-real-time-developer-notifications-rtdn)。 ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象的属性。我们建议在应用启动时(例如[识别用户](android-identifying-users#setting-customer-user-id-on-configuration)时)获取用户画像,并在发生变更时及时更新。这样,您就可以直接使用用户画像对象,而无需反复请求。 若要在用户画像更新时收到通知,请按照下方[监听用户画像更新(包括访问等级变更)](android-listen-subscription-changes)部分的说明,监听用户画像变更事件。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 使用 `.getProfile()` 方法从服务器获取访问等级: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` 响应参数: | 参数 | 说明 | | --------- | ------------------------------------------------------------ | | Profile |

[AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象。通常情况下,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。

`.getProfile` 方法始终尝试查询 API,因此能提供最新的结果。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。此外,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的实时性。

| `.getProfile()` 方法会返回用户画像,您可以从中获取访问等级状态。每个应用可以拥有多个访问等级。例如,如果您有一个新闻应用并独立销售不同主题的订阅,可以创建"sports"和"science"等访问等级。但在大多数情况下,您只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。 以下是检查默认"premium"访问等级的示例: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium"); if (premium != null && premium.isActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化,Adapty 都会触发一个事件。 要接收来自 Adapty 的消息,您需要进行一些额外配置: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` ```java showLineNumbers t Adapty.setOnProfileUpdatedListener(profile -> { // handle any changes to subscription state }); ``` Adapty 也会在应用启动时触发一个事件,此时将传递缓存的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 中实现的缓存会存储用户画像的订阅状态。这意味着即使服务器不可用,也可以访问缓存数据以获取用户画像的订阅状态信息。 但请注意,目前无法直接从缓存请求数据。SDK 会每分钟定期查询服务器,以检查与用户画像相关的任何更新或变更。如有任何修改(例如新的交易或其他更新),这些变更将被同步到缓存数据中,以保持与服务器的一致性。 --- # File: kids-mode-android --- --- title: "Android SDK 中的儿童模式" description: "轻松启用儿童模式以遵守 Google 政策。Android SDK 中不会收集 GAID 或广告数据。" --- 如果您的 Android 应用面向儿童,则必须遵守 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单步骤即可完成配置,以满足这些政策要求并通过应用商店审核。 ## 需要配置哪些内容?\{#whats-required\} 你需要配置 Adapty SDK,禁止收集以下信息: - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用 customer user ID。格式为 `` 的用户 ID 与使用电子邮件一样,肯定会被视为收集个人数据。对于儿童模式,最佳做法是使用随机或匿名标识符(例如哈希 ID 或设备生成的 UUID),以确保合规性。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。请前往 [App settings](https://app.adapty.io/settings/general),并在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 在移动应用代码中进行更新 \{#updates-in-your-mobile-app-code\} 为遵守相关政策,您需要在初始化 Adapty SDK 时禁用 Android 广告 ID(AAID/GAID)和 IP 地址的收集: **Kotlin:** ```kotlin showLineNumbers override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ) } ``` **Java:** ```java showLineNumbers @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ); } ``` ### Android 清单更新 \{#updates-in-your-android-manifest\} :::note 如果你的应用**仅**面向儿童用户,且编译目标为 Android 13(API 33)或更高版本,Google Play 要求你不得请求 `AD_ID` 权限。应用中的其他 SDK(如分析、归因或广告 SDK)可能通过清单合并的方式添加该权限。设置 `withAdIdCollectionDisabled(true)` 可阻止 Adapty 收集该 ID,但无法移除其他 SDK 声明的权限。 ::: 要移除该权限,请在 `app/src/main/AndroidManifest.xml` 的 `` 元素内添加以下内容。`` 元素必须声明 `xmlns:tools="http://schemas.android.com/tools"`。 ```xml showLineNumbers title="AndroidManifest.xml" ``` --- # File: android-get-onboardings --- --- title: "在 Android SDK 中获取用户引导" description: "了解如何在 Adapty for Android 中获取用户引导。" --- :::tip **从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 Android 视觉风格、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以开始使用。 ::: 在 Adapty 看板中使用编辑工具[设计好用户引导的视觉部分](design-onboarding)后,您可以在 Android 应用中展示它。该流程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。 开始之前,请确保: 1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 ## 获取用户引导 \{#fetch-onboarding\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器的形式存储,其中包含应用需要获取和展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(如问卷回答或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。 为获得最佳性能,请尽早获取用户引导配置,以便图片在展示给用户之前有足够的时间下载。 要获取用户引导,请使用 `getOnboarding` 方法: ```kotlin showLineNumbers Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // the requested onboarding } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` 参数说明: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。

但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。

请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。

Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 来更快地获取用户引导,并在 CDN 不可访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。

| | **loadTimeout** | 默认值:5 秒 |

此值限制该方法的超时时间。如果超时,将返回缓存数据或本地备用数据。

请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作底层可能包含多个不同请求。

对于 Android:您可以使用扩展函数创建 `TimeInterval`(如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。若不设置限制,请使用 `TimeInterval.INFINITE`。

| 响应参数: | 参数 | 描述 | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) 对象,包含:用户引导标识符和配置、远程配置以及其他若干属性。 | ## 使用默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常情况下,用户引导的获取几乎是即时的,因此您无需担心加速此过程。但如果您有大量目标受众和用户引导,且用户的网络连接较弱,则获取用户引导可能需要比预期更长的时间。在这种情况下,您可能希望展示默认用户引导,以确保流畅的用户体验,而不是不显示任何用户引导。 为解决此问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的用户引导。但请务必理解,推荐的方式是通过 `getOnboarding` 方法获取用户引导,详见上方[获取用户引导](#fetch-onboarding)部分。 :::warning 请考虑使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者存在以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要兼容向后设计,否则旧版本可能显示异常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向。 如果对您的使用场景而言,更快的获取速度优先于这些缺点,请按如下所示使用 `getOnboardingForDefaultAudience`。否则,请按[上方](#fetch-onboarding)所述使用 `getOnboarding`。 ::: ```kotlin Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // Handle successful onboarding retrieval } is AdaptyResult.Error -> { val error = result.error // Handle error case } } } ``` 参数说明: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** |

可选

默认值:`en`

|

用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。

示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。

有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。

| | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` |

默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。

但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。

请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。

Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 来更快地获取用户引导,并在 CDN 不可访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。

| --- # File: android-present-onboardings --- --- title: "在 Android SDK 中展示用户引导" description: "了解如何在 Android 上展示用户引导,以有效提升用户参与度。" --- :::tip **从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 Android 视觉体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以开始使用。 ::: 在开始之前,请确保: 1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 如果您已使用 Onboarding Builder 自定义了用户引导,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示它。此类用户引导已包含展示内容和展示方式的完整配置。 要在设备屏幕上显示可视化的用户引导,首先需要进行配置。调用 `AdaptyUI.getOnboardingView()` 方法,或直接创建 `OnboardingView`: ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener); ``` ```xml ``` 视图成功创建后,您可以将其添加到视图层级中并在设备屏幕上显示。 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfig** | 必填 | 从 `AdaptyUI.getOnboardingConfiguration()` 获取的用户引导配置 | | **eventListener** | 必填 | `AdaptyOnboardingEventListener` 的实现,用于处理用户引导事件。详情请参考[处理用户引导事件](android-handle-onboarding-events)。 | ## 更改加载指示器颜色 \{#change-loading-indicator-color\} 您可以通过以下方式覆盖加载指示器的默认颜色: ```xml ``` ## 在启动页和用户引导之间添加流畅过渡 \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} 默认情况下,在启动页和用户引导之间,你会看到加载画面,直到用户引导完全加载完毕。但如果你想让过渡更流畅,可以自定义过渡效果——延长启动页的显示时间,或显示其他内容。 为此,在 `res/layout` 目录下创建 `adapty_onboarding_placeholder_view.xml`,并在其中定义一个占位视图(即用户引导加载期间显示的内容)。 如果您定义了版位,用户引导将在后台加载,并在准备就绪后自动显示。 ## 禁用安全区域内边距 \{#disable-safe-area-paddings\} 默认情况下,用户引导视图会自动应用安全区域内边距,以避免与状态栏、导航栏等系统 UI 元素重叠。如果你希望禁用此行为并完全控制布局,可以将 `safeAreaPaddings` 参数设置为 `false`。 ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener, safeAreaPaddings = false ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener, safeAreaPaddings = false ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener, false ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener, false); ``` 你也可以通过在应用中添加布尔资源来全局控制此行为: ```xml false ``` 当 `safeAreaPaddings` 设置为 `false` 时,用户引导将扩展至全屏,不会进行任何自动内边距调整,从而让你完全掌控布局,使用户引导内容得以占用整个屏幕空间。 ## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接的打开方式需要 Adapty SDK v3.15.1 及以上版本。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开。这种方式能为用户提供无缝体验,让用户无需切换应用即可在应用内查看网页。 如果你希望改为在外部浏览器中打开链接,可以将 `externalUrlsPresentation` 参数设置为 `AdaptyWebPresentation.ExternalBrowser` 来自定义此行为: ```kotlin val onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ) ``` ```java AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding, AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ); ``` --- # File: android-handle-onboarding-events --- --- title: "在 Android SDK 中处理用户引导事件" description: "在 Android 中使用 Adapty 处理用户引导相关事件。" --- :::tip **从 SDK v4 起**,你可以使用[流程](android-get-pb-paywalls)作为用户引导的更强大替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——动画更流畅、外观风格与 Android 保持一致、加载速度更快,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)开始使用。 ::: 开始之前,请确保: 1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 通过编辑工具配置的用户引导会生成各类事件,您的应用可以对这些事件作出响应。请参阅下方说明,了解如何处理这些事件。 如需在 Android 应用中控制或监听用户引导屏幕上发生的操作,请实现 `AdaptyOnboardingEventListener` 接口。 ## 自定义操作 \{#custom-actions\} 在编辑工具中,你可以为按钮添加**自定义**操作并为其分配一个 ID。然后,你可以在代码中使用该 ID,并将其作为自定义操作进行处理。 例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,委托方法 `onCustomAction` 将被触发,并携带来自编辑工具的操作 ID。你可以自定义 ID,例如 "allowNotifications"。 ```kotlin showLineNumbers class YourActivity : AppCompatActivity() { private val eventListener = object : AdaptyOnboardingEventListener { override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) { when (action.actionId) { "allowNotifications" -> { // Request notification permissions } } } override fun onError(error: AdaptyOnboardingError, context: Context) { // Handle errors } // ... other required delegate methods } } ```
事件示例(点击展开) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
## 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了 **Close** 操作的按钮时,用户引导即视为已关闭。你需要处理用户关闭用户引导后的逻辑。例如: :::important 你需要处理用户关闭用户引导后的逻辑,例如停止显示用户引导界面本身。 ::: 示例: ```kotlin override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) { // Dismiss the onboarding screen (context as? Activity)?.onBackPressed() } ```
事件示例(点击展开) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
## 打开付费墙 \{#opening-a-paywall\} :::tip 如果你想在用户引导内部打开付费墙,请处理此事件。如果你想在付费墙关闭后再打开另一个付费墙,有一种更直接的方式——处理 [`AdaptyOnboardingCloseAction`](#closing-onboarding) 并直接打开付费墙,无需依赖事件数据。 ::: 在用户引导中使用付费墙最流畅的方式,是将 action ID 设置为等同于付费墙的版位 ID。这样,在收到 `AdaptyOnboardingOpenPaywallAction` 事件后,你可以直接用版位 ID 获取并打开对应的付费墙: ```kotlin override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) { // Get the paywall using the placement ID from the action Adapty.getPaywall(placementId = action.actionId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Get the paywall configuration AdaptyUI.getViewConfiguration(paywall) { result -> when(result) { is AdaptyResult.Success -> { val paywallConfig = result.value // Create and present the paywall val paywallView = AdaptyUI.getPaywallView( activity = this, viewConfig = paywallConfig, products, eventListener = paywallEventListener ) // Add the paywall view to your layout binding.container.addView(paywallView) } is AdaptyResult.Error -> { val error = result.error // handle the error } } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } } ```
事件示例(点击展开) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
## 完成用户引导加载 \{#finishing-loading-onboarding\} 当用户引导完成加载时,将调用此方法: ```kotlin override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) { // Handle loading completion } ```
事件示例(点击展开) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
## 导航事件 \{#navigation-events\} `onAnalyticsEvent` 方法会在用户引导流程中发生各类分析事件时被调用。 `event` 对象可以是以下类型之一: |类型 | 描述 | |------------|-------------| | `OnboardingStarted` | 用户引导加载完成时触发 | | `ScreenPresented` | 任意屏幕显示时触发 | | `ScreenCompleted` | 屏幕完成时触发。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的响应)。用户执行任意操作退出屏幕时触发。 | | `SecondScreenPresented` | 第二个屏幕显示时触发 | | `UserEmailCollected` | 通过输入框收集到用户邮箱时触发 | | `OnboardingCompleted` | 用户到达 ID 为 `final` 的屏幕时触发。如需使用此事件,请将最后一个屏幕的 ID 设置为 `final`。 | | `Unknown` | 任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据) | 每个事件都包含 `meta` 信息,内容如下: | 字段 | 描述 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前屏幕的标识符 | | `screenIndex` | 当前屏幕在流程中的位置 | | `totalScreens` | 流程中的屏幕总数 | 以下是如何使用分析事件进行追踪的示例: ```kotlin override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) { when (event) { is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> { // 追踪用户引导开始 trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> { // 追踪屏幕展示 trackEvent("screen_presented", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> { // 追踪屏幕完成及用户响应 trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> { // 追踪用户引导成功完成 trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingAnalyticsEvent.Unknown -> { // 处理未知事件 trackEvent(event.name, event.meta) } // 按需处理其他情况 } } ```
事件示例(点击展开) ```javascript // OnboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // ScreenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // ScreenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // SecondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // UserEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // OnboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
--- # File: android-onboarding-input --- --- title: "在 Android SDK 中处理用户引导的数据" description: "使用 Adapty SDK 在 Android 应用中保存并使用用户引导的数据。" --- :::tip **从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与运行在 WebView 中的用户引导不同,流程在设备上原生渲染——带来更流畅的动画、一致的 Android 外观体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以快速上手。 ::: 当用户回答测验问题或在输入框中填写数据时,`onStateUpdatedAction` 方法会被调用。你可以在代码中保存或处理字段类型。 例如: ```kotlin override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle single selection } is AdaptyOnboardingStateUpdatedParams.MultiSelect -> { // Handle multiple selections } is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input } is AdaptyOnboardingStateUpdatedParams.DatePicker -> { // Handle date selection } } } ``` 请参阅[此处](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/)的操作格式说明。
已保存数据示例(格式可能因您的实现方式而有所不同) ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ```
## 使用场景 \{#use-cases\} ### 用户画像数据补充 \{#enrich-user-profiles-with-data\} 如果你希望立即将用户填写的信息与其用户画像关联,避免重复询问同样的内容,可以在处理操作时[更新用户画像](android-setting-user-attributes),将输入数据写入其中。 例如,你让用户在 ID 为 `name` 的文本框中输入姓名,并希望将该字段的值设为用户的名字;同时,你还让用户在 `email` 字段中输入邮箱。在应用代码中,实现方式如下: ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (action.elementId) { "name" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Text -> { builder.withFirstName(inputParams.value) } } } "email" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Email -> { builder.withEmail(inputParams.value) } } } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` ### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\} 通过在用户引导中加入问卷测验,你还可以根据用户完成引导后的答案来定制展示给他们的付费墙。 例如,你可以询问用户的运动经验,并向不同用户群展示不同的行动号召文案(CTA)和产品。 1. 在用户引导编辑工具中[添加问卷测验](onboarding-quizzes),并为各选项设置有意义的 ID。 2. 根据 ID 处理测验响应,并为用户[设置自定义属性](android-setting-user-attributes)。 ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Handle quiz responses and set custom attributes when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (action.elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.params.value) } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为每个已创建的市场细分添加[目标受众](audience)。 5. 在应用代码中为该版位[展示付费墙](android-paywalls)。如果你的用户引导中有一个按钮用于打开付费墙,请将付费墙代码实现为[该按钮操作的响应](android-handle-onboarding-events#opening-a-paywall)。 --- # File: android-sdk-call-order --- --- title: "Android SDK 调用顺序" description: "通过按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、归因数据缺失以及间歇性 ADAPTY_NOT_INITIALIZED 错误。" --- `Adapty.activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其完成之前,SDK 没有任何状态。在 `activate()` 之前或与之并行发出的任何调用都会以 [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling) 错误失败。 如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到后调用 `Adapty.identify()`。在 `identify` 的完成回调触发之前,不要调用任何用户操作相关的方法。与 `identify` 并发的调用要么在回调中返回错误,要么会落到激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属不一定会转移到已识别的用户画像。如果你的应用不需要用户认证,可以跳过 `identify`,直接使用匿名用户画像。 MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待 UID 回调后再调用 `Adapty.activate`。否则 MMP ID 会落到一个短暂的匿名用户画像上,并不总能转移到已识别的用户画像中。有关 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的路径取决于两件事:何时获取客户用户 ID,以及是否使用了 MMP 或数据分析 SDK。 - **步骤 2 和 5**:每个应用都必须执行。先激活 SDK,再调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或数据分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。 - **步骤 4**:仅在应用需要用户身份验证、且在启动后才能获取客户用户 ID 时需要。 如果在应用启动时已知用户的 customer user ID,可在调用 `activate()` 前将其传入 `AdaptyConfig.Builder`(步骤 2a)。这种方式不会创建匿名用户画像,因此无需执行步骤 4。 | 步骤 | 调用 | 时机 | 说明 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化你的 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动时,第一步 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | 应用启动时,步骤 1 之后,如果你已有 customer user ID | 推荐。不会创建任何匿名用户画像。 | | 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` 不传 `customerUserId` | 应用启动时,步骤 1 之后,如果你还没有 customer user ID(或从不收集) | Adapty 会创建一个匿名用户画像。 | | 3 | 针对每个 MMP 调用 `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,以确保 MMP ID 关联到正确的用户画像。 | | 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | 步骤 3 之后(若无 MMP 则步骤 2 之后),步骤 5 之前 — 仅适用于走路径 2b 且需要身份验证的情况 | 使用完成回调。在 `identify` 执行期间并发调用可能会落到匿名用户画像上。 | | 5 | `getPaywall`、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 如果调用了 `identify`,则在步骤 4 之后;否则在步骤 3 之后(若无 MMP 则步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回归用户失去高级访问权限、用户画像缺少 `appsflyer_id`,以及付费墙被错误的目标受众所匹配。 ::: ## Web2app 与网页漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在网页结账(Stripe、Paddle)完成购买后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与网页端的用户画像关联。如果你能在应用启动前(通过认证流程或安装来源)获取到 customer user ID,可以直接将其传入 `AdaptyConfig.Builder`。否则,在你调用 `identify("YOUR_USER_ID")` 并执行 `restorePurchases` 之前,设备上将看不到该网页购买记录。 关于每次网页结账需要传递的元数据,请参考: - [Stripe](stripe) - [Paddle](paddle) --- # File: android-optimize-paywall-fetching --- --- title: "优化 Android SDK 中的付费墙获取" description: "可靠地获取 Adapty 付费墙:Android 的时机、缓存与备用方案。" --- 在 Android 上可靠地获取付费墙需要做到三点:渲染速度快、返回针对目标受众的付费墙,以及在网络较慢时能优雅降级。以下规则涵盖了实现这些目标所需的时机、缓存和备用方案。 :::tip 以下规则假定 `Adapty.activate()` 和 `Adapty.identify()` 均已完成。详情请参阅 [Android SDK 调用顺序](android-sdk-call-order)。 ::: ## 注意事项与常见陷阱 \{#rules-and-pitfalls\} | 应该这样做 | 不要这样做 | 原因 | |---|---|---| | 仅在即将展示时获取对应的版位。 | 启动时并发预取所有版位。 | 批量预取会阻塞主线程,导致启动期间出现黑屏。 | | 在归因数据有机会解析后再调用 `getPaywall`,例如在 `activate` 之后等待 1–2 秒,或等待 `setOnProfileUpdatedListener` 触发后再调用。 | 在 `Application.onCreate()` 中调用 `getPaywall`。 | 此时归因数据尚未就绪,付费墙将按默认目标受众进行解析,静默绕过市场细分和 ASA 个性化配置。 | | 为每个版位设置 `loadTimeout` 并配置[备用付费墙](fallback-paywalls)。 | 无限等待 `getPaywall` 返回。 | 没有超时限制时,网络较差的用户会看到空白屏幕,直到网络恢复——或者直接关闭应用。 | 有关 `fetchPolicy` 和 `loadTimeout` 参数的说明,请参阅[获取付费墙和产品](fetch-paywalls-and-products-android);有关选择合适版位的说明,请参阅[版位](placements)。 ## 针对弱网环境进行调优 \{#tune-for-poor-connectivity\} 针对网络状况持续较差的市场(农村地区、交通途中、受路由问题影响的地区): - 除首次请求外,每次获取付费墙时将 `fetchPolicy` 设置为 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeout` 设置为 3–5 秒,并在超时触发时接受备用付费墙。 - 不要将付费墙的展示逻辑依赖于 `getProfile` 的返回结果。独立调用 `getPaywall`,避免因用户画像加载缓慢而阻塞界面。 --- # File: android-test --- --- title: "在 Android SDK 中测试与发布" description: "了解如何在 Android 应用中使用 Adapty 检查订阅状态。" --- 如果你已经在 Android 应用中集成了 Adapty SDK,接下来需要测试所有配置是否正确,以及购买流程是否按预期运行。这包括测试 SDK 集成以及使用 Google Play 沙盒环境验证实际的购买流程。 ## 测试你的应用 \{#test-your-app\} 关于应用内购买的全面测试指南,包括沙盒测试和封闭轨道验证,请参阅我们的[测试指南](testing-on-android)。 ## 发布前准备 \{#prepare-for-release\} 在将应用提交到应用商店之前,请按照[发布检查清单](release-checklist)确认以下事项: - 已配置应用商店连接和服务器通知 - 购买已完成并上报至 Adapty - 访问等级能够正确解锁和恢复 - 已满足隐私和审核要求 --- # File: android-sdk-error-handling --- --- title: "处理 Android SDK 错误" description: "借助 Adapty 的故障排查指南,有效处理 Android SDK 错误。" --- SDK 返回的所有错误均为 `AdaptyError` 类型。 :::tip **在调试前开启详细日志。** 大多数 `AdaptyError` 都封装了底层的 Play Billing、网络或后端错误。开启详细日志(`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — 参见[日志记录](sdk-installation-android#logging))后,封装的错误会打印到控制台,通常能直接告诉你真正的原因。 ::: :::important 如果以上方案未能解决你的问题,请查看[其他问题](#other-issues),了解联系支持前需要做哪些准备,以便我们更高效地为你提供帮助。 ::: | 错误 | 解决方案 | |----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | UNKNOWN | 发生了未知或意外错误。 | | [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | 此错误多发生在测试阶段,可能表示产品未发布至生产环境,或该用户不在 Google Play 的 Testers 分组中。 | | ADAPTY_NOT_INITIALIZED | Adapty SDK 未激活。
最常见的情况是启动页或早期 UI 钩子在 `Adapty.activate` 返回之前就调用了 Adapty 方法。此问题偶发,在模拟器上可能无法复现,因为真机的时序不同。请等待 `Adapty.activate` 完成后再发起其他 SDK 调用。完整调用顺序请参阅 [Android SDK 调用顺序](android-sdk-call-order)。同时需要通过 `Adapty.activate` 方法正确[配置 Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk)。 | | PROFILE_WAS_CHANGED | 操作期间用户画像发生了变更。
当 `Adapty.identify` 仍在执行时调用其他方法会触发此错误——进行中的调用落在即将被替换的用户画像上,SDK 会拒绝该请求。请等待 `Adapty.identify` 完成后再发起其他 SDK 调用。请参阅 [Android SDK 调用顺序](android-sdk-call-order)。 | | PRODUCT_NOT_FOUND | 请求购买的产品在商店中不可用。 | | INVALID_JSON |

本地备用付费墙 JSON 格式无效。

请先修复默认的英文付费墙,再替换无效的本地付费墙。如何修复付费墙,请参阅[使用远程配置自定义付费墙](customize-paywall-with-remote-config);如何替换本地付费墙,请参阅[定义本地备用付费墙](fallback-paywalls)。

| |

CURRENT_SUBSCRIPTION_TO_UPDATE

\_NOT_FOUND_IN_HISTORY

| 需要替换的原始订阅在活跃订阅中未找到。 | | [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | 请求在 Google Play 响应之前已达到最大超时时间。例如,Play Billing Library 调用所请求的操作执行延迟可能导致此错误。 | | [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | 当前设备的 Play Store 不支持所请求的功能。 | | [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | 客户端应用通过 `BillingClient` 与 Google Play Store 服务的连接已断开。 | | [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Google Play 计费服务当前不可用。大多数情况下,这意味着客户端设备与 Google Play 计费服务之间存在网络连接问题。 | | [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) |

购买过程中发生了计费问题,可能原因如下:

1. 用户设备上的 Play Store 应用缺失或版本过旧。

2. 用户所在国家/地区不受支持。

3. 用户属于企业账号,管理员已禁用购买功能。

4. Google Play 无法向用户的支付方式扣款(例如信用卡已过期)。

5. 用户未登录 Play Store 应用。

| | [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | API 使用方式不正确。 | | [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Google Play 内部出现问题。 | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | 该产品已购买。 | | [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | 该商品不属于当前用户,无法执行所请求的操作。 | | [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | 设备与 Play 系统之间的网络连接出现问题。 | | NO_PRODUCT_IDS_FOUND |

付费墙中没有任何产品在商店中可用。

如果遇到此错误,请按以下步骤排查:

  1. 检查所有产品是否已添加到 Adapty 看板。
  2. 确认应用的 **Package name** 与 Google Play Console 中的一致。
  3. 核实应用商店中的产品标识符与看板中添加的标识符一致。注意:除非商店本身已包含 Bundle ID,否则标识符中不应包含 Bundle ID。
  4. 确认您的 Google 税务设置中应用的付费状态为 **Active**,税务信息是最新的,且证书有效。
  5. 检查应用是否已绑定银行账号,以便参与变现。
  6. 检查产品是否在您所在的地区可用。
  7. 确保应用已加入某个测试轨道。**Internal testing** 轨道是最简便的选项,无需审核且对用户不可见。
| | NO_PURCHASES_TO_RESTORE | Google Play 未找到可恢复的购买记录。 | | AUTHENTICATION_ERROR | 请通过 `Adapty.activate` 方法正确[配置 Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk)。 | | BAD_REQUEST | 请求无效。
请确认已完成[与 Google Play 集成](google-play-store-connection-configuration)所需的所有步骤。 | | SERVER_ERROR | 服务器错误。 | | REQUEST_FAILED | 发生了无法明确定义的网络问题。 | | DECODING_FAILED | 无法解析响应。
请检查您的代码,确认发送的参数有效。例如,此错误可能表示您使用了无效的 API 密钥。 | | ANALYTICS_DISABLED | 由于您已[关闭该功能](analytics-integration#disabling-external-analytics-for-a-specific-customer),无法处理数据分析事件。 | | WRONG_PARAMETER | 部分参数不正确:不能为空的字段为空,或参数类型错误等。 | ## 其他问题 \{#other-issues\} 如果您尚未找到解决方案,可以采取以下后续步骤: - **将 SDK 升级到最新版本**:我们始终建议升级到最新的 SDK 版本,因为它们更稳定并包含已知问题的修复。 - **联系支持团队或在[支持论坛](https://adapty.featurebase.app/)中获得其他开发者的帮助**。 - **通过 [support@adapty.io](mailto:support@adapty.io) 或在线聊天联系支持团队**:如果您还不打算升级 SDK 或升级后问题仍未解决,请联系我们的支持团队。请注意,如果您[启用详细日志记录](sdk-installation-android#logging)并与团队共享日志,您的问题将得到更快解决。您也可以附上相关代码片段。 --- # File: migration-to-android-sdk-v4 --- --- title: "将 Adapty Android SDK 迁移至 v. 4.0" description: "通过将付费墙 API 替换为流程 API,迁移至 Adapty Android SDK v4.0,兼容流程编辑工具和付费墙编辑工具。" --- Adapty Android SDK 4.0 引入了流程功能,并相应地对付费墙 API 进行了重命名。新 API 同时兼容新版流程编辑工具和现有的付费墙编辑工具——无需在 Adapty 看板端进行任何配置变更。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `AdaptyUI.getViewConfiguration(paywall)` | `AdaptyUI.getFlowConfiguration(flow, locale)` | | `AdaptyUI.LocalizedViewConfiguration` | `AdaptyUI.FlowConfiguration` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.getPaywallView(...)` | `AdaptyUI.getFlowView(...)` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `AdaptyPaywallScreen` (Compose) | `AdaptyFlowScreen` | | `showPaywall(...)` | `showFlow(...)` | | `AdaptyPaywallInsets` | `AdaptyFlowInsets` | | `AdaptyUiEventListener` | `AdaptyFlowEventListener` | | `AdaptyUiDefaultEventListener` | `AdaptyFlowDefaultEventListener` | | `onPaywallShown` / `onPaywallClosed` | `onFlowShown` / `onFlowClosed` | | `onRenderingError` | `onError` | | `Adapty.updateAttribution(attribution, source)` (`source: String`) | `Adapty.updateAttribution(attribution, source)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key, value)` | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier)` | `AdaptyPaywallProduct` 保持原名不变——产品仍属于某个 flow,`getPaywallProducts` 现在接收 `AdaptyFlow` 参数。其他 `AdaptyFlowEventListener` 方法(`onProductSelected`、`onPurchaseStarted`、`onPurchaseFinished`、`onPurchaseFailure`、`onRestoreSuccess`、`onRestoreFailure`、`onActionPerformed`、`onAwaitingPurchaseParams`、`onLoadingProductsFailure` 等)保持原有名称和签名不变。 ## 安装 \{#installation\} 将 `adapty-bom` 版本设置为 `4.0.0`(或更高版本)并同步项目。BOM 会自动为你解析匹配的 `android-sdk` 和 `android-ui` 版本。依赖项声明请参见[安装 Adapty SDK](sdk-installation-android)。 ## 已移除和废弃的 API \{#removed-and-deprecated-apis\} - **`Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized, callback)`** — 已移除。此重载方法在 v3 中已废弃。请改用 `AdaptyPurchaseParameters` 传入相同的选项: ```diff showLineNumbers - Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> /* ... */ } + val params = AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(subscriptionUpdateParams) + .withOfferPersonalized(isOfferPersonalized) + .build() + Adapty.makePurchase(activity, product, params) { result -> /* ... */ } ``` - **用户引导已弃用。** `AdaptyUI.getOnboardingView` 和 `AdaptyUI.getOnboardingConfiguration` 在 4.0 中标记为 `@Deprecated` — 请将用户引导迁移到在[流程编辑工具](adapty-flow-builder)中构建的流程。 ## 获取流程 \{#fetching-flows\} ### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration 获取返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,配置加载器从 `AdaptyUI.getViewConfiguration` 重命名为 `AdaptyUI.getFlowConfiguration`(返回 `AdaptyUI.FlowConfiguration` 而非 `AdaptyUI.LocalizedViewConfiguration`)。`locale` 参数从获取调用中移出,改为传入 `getFlowConfiguration`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> + Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { - val paywall = result.value - if (!paywall.hasViewConfiguration) return@getPaywall - AdaptyUI.getViewConfiguration(paywall) { configResult -> + val flow = result.value + if (!flow.hasViewConfiguration) return@getFlow + AdaptyUI.getFlowConfiguration(flow, locale = "en") { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` 现在接受由 `Adapty.getFlow` 返回的 `AdaptyFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) { result -> /* products */ } + Adapty.getPaywallProducts(flow) { result -> /* products */ } ``` ## 跟踪流程浏览数据 \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` 已重命名为 `logShowFlow`,现在接受 `AdaptyFlow` 而非 `AdaptyPaywall`。事件仍会记录在同一个实验变体下,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。 ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` 与 v3 一样,当展示由[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些页面的浏览记录。 ## 显示流程 \{#displaying-flows\} ### getPaywallView / AdaptyPaywallView → getFlowView / AdaptyFlowView 重命名工厂方法和视图类型,并传入 `AdaptyUI.FlowConfiguration`: ```diff showLineNumbers - val paywallView = AdaptyUI.getPaywallView( - activity, - viewConfiguration, - products, - eventListener, - ) + val flowView = AdaptyUI.getFlowView( + activity, + flowConfiguration, + products, + eventListener, + ) ``` 如果直接创建视图,show 方法也需要重命名: ```diff showLineNumbers - val paywallView = AdaptyPaywallView(activity) - paywallView.showPaywall(viewConfiguration, products, eventListener) + val flowView = AdaptyFlowView(activity) + flowView.showFlow(flowConfiguration, products, eventListener) ``` 在 XML 布局中,更新视图标签: ```diff showLineNumbers - + ``` 可选参数 `personalizedOfferResolver` 已从 `getFlowView` / `showFlow` / `AdaptyFlowScreen` 中移除。如需标记个性化定价,请通过 `onAwaitingPurchaseParams` 为每个产品单独设置(`AdaptyPurchaseParameters.Builder().withOfferPersonalized(true)`)。新增的可选参数 `customAssets` 允许你在运行时覆盖图片和视频——详见[自定义资源](android-get-pb-paywalls#customize-assets)。 ### AdaptyPaywallScreen → AdaptyFlowScreen 在 Jetpack Compose 中,重命名 composable 并更新配置参数: ```diff showLineNumbers - AdaptyPaywallScreen( - viewConfiguration, + AdaptyFlowScreen( + flowConfiguration, products, eventListener, ) ``` ## 处理事件 \{#handling-events\} 事件监听器已从 `AdaptyUiEventListener` 重命名为 `AdaptyFlowEventListener`(`AdaptyUiDefaultEventListener` 也相应重命名为 `AdaptyFlowDefaultEventListener`)。大多数方法名保持不变;生命周期和渲染相关的回调已重命名: ```diff showLineNumbers - class YourListener : AdaptyUiDefaultEventListener() { + class YourListener : AdaptyFlowDefaultEventListener() { - override fun onPaywallShown(context: Context) {} - override fun onPaywallClosed() {} + override fun onFlowShown(context: Context) {} + override fun onFlowClosed() {} - override fun onRenderingError(error: AdaptyError, context: Context) {} + override fun onError(error: AdaptyError, context: Context) {} } ``` 现有的处理器主体无需修改代码——只需重命名类型和重写方法即可。`onError` 触发的场景与 `onRenderingError` 相同,还额外涵盖其他非购买类运行时错误。完整的回调列表请参阅[处理流程与付费墙事件](android-handling-events)。 v4 还新增了 `onBackPressed(context): Boolean` 回调,其默认行为也改变了系统返回键的处理方式。此前,返回键(或返回手势)会透传给 Activity 或 Fragment,通常会关闭付费墙。v4 中默认实现会消费该按键事件,因此**系统返回键不再自动关闭流程**——与 iOS 保持一致(iOS 上流程无法通过系统手势关闭)。请为用户提供明确的退出方式(添加 **Close** 按钮或 `on_device_back` 操作),或者重写 `onBackPressed` 并返回 `false` 以恢复旧有行为。详情请参阅[系统返回键](android-handling-events#system-back-button)。 默认购买处理器也不再自动关闭屏幕。在 v3 中,默认的 `onPurchaseFinished` 会在任何非用户主动取消的购买完成后(成功或待处理的购买)关闭付费墙。在 v4 中它是一个空操作,因此**流程在购买完成后会保持打开状态,直到你手动关闭它**——这与 iOS 的行为一致。如果你之前依赖自动关闭功能,请在购买完成后自行关闭屏幕。具体示例请参阅[购买成功、取消或待处理](android-handling-events#successful-canceled-or-pending-purchase)。 ## 归因与集成标识符 \{#attribution-and-integration-identifiers\} ### updateAttribution `source` 参数从 `String` 类型变更为新的 `AdaptyAttributionSource` 类型,`attribution` 现在是 `Map`(同时也提供 JSON `String` 重载)。请使用以下预定义来源之一: ```diff showLineNumbers - Adapty.updateAttribution(attribution, "appsflyer") { error -> /* handle the error */ } + Adapty.updateAttribution(attribution, AdaptyAttributionSource.APPSFLYER) { error -> /* handle the error */ } ``` 预定义来源:`AdaptyAttributionSource.APPLE_ADS`、`.ADJUST`、`.APPSFLYER`、`.BRANCH`、`.TENJIN`。如需使用其他来源,可通过字符串构建:`AdaptyAttributionSource("your_source")`。 ### setIntegrationIdentifier `setIntegrationIdentifier(key, value)` 已被替换为接受一个或多个 `AdaptyIntegrationIdentifier` 值的方法。请使用便捷方法构建每个标识符,而不是传入原始字符串键: ```diff showLineNumbers - Adapty.setIntegrationIdentifier("appsflyer_id", appsFlyerId) { error -> /* handle the error */ } + Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId)) { error -> /* handle the error */ } ``` 你可以在一次调用中设置多个标识符: ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( listOf( AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId), AdaptyIntegrationIdentifier.adjustDeviceId(adjustDeviceId), ) ) { error -> /* handle the error */ } ``` 将每个旧的键字符串替换为对应的便捷方法: | v3 键名 | v4 `AdaptyIntegrationIdentifier` 方法 | |---|---| | `"adjust_device_id"` | `adjustDeviceId(value)` | | `"airbridge_device_id"` | `airbridgeDeviceId(value)` | | `"amplitude_user_id"` | `amplitudeUserId(value)` | | `"amplitude_device_id"` | `amplitudeDeviceId(value)` | | `"appmetrica_device_id"` | `appmetricaDeviceId(value)` | | `"appmetrica_profile_id"` | `appmetricaProfileId(value)` | | `"appsflyer_id"` | `appsflyerId(value)` | | `"branch_id"` | `branchId(value)` | | `"facebook_anonymous_id"` | `facebookAnonymousId(value)` | | `"firebase_app_instance_id"` | `firebaseAppInstanceId(value)` | | `"mixpanel_user_id"` | `mixpanelUserId(value)` | | `"one_signal_subscription_id"` | `oneSignalSubscriptionId(value)` | | `"one_signal_player_id"` | `oneSignalPlayerId(value)` | | `"posthog_distinct_user_id"` | `posthogDistinctUserId(value)` | | `"pushwoosh_hwid"` | `pushwooshHWID(value)` | | `"tenjin_analytics_installation_id"` | `tenjinAnalyticsInstallationId(value)` | 对于不在此列表中的键,可以直接使用自定义 `Key` 来构建标识符:`AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("custom"), customValue)`。 --- # File: migration-to-android-312 --- --- title: "将 Adapty Android SDK 迁移至 v3.12" description: "迁移至 Adapty Android SDK v3.12,获得更好的性能与全新的变现功能。" --- 在 Adapty SDK 3.12.0 中,我们已从 SDK 中移除了 `logShowOnboarding` 方法。 如果你之前使用过该方法,升级至 SDK 3.12 或更高版本后将无法继续使用它。 作为替代,您可以[在 Adapty 无代码用户引导编辑工具中创建用户引导](onboardings)。这些用户引导的分析数据会自动追踪,并且您拥有丰富的自定义选项。 --- # File: migration-to-android-310 --- --- title: "Android Adapty SDK 3.10.0 迁移指南" description: "" --- Adapty SDK 3.10.0 是一个主要版本,带来了一些改进,但可能需要您执行一些迁移步骤: 1. `AdaptyUiPersonalizedOfferResolver` 已被移除。如果您正在使用它,请在 `onAwaitingPurchaseParams` 回调中传入。 2. 更新付费墙编辑工具付费墙的 `onAwaitingSubscriptionUpdateParams` 方法签名。 ## 更新购买参数回调 \{#update-purchase-parameters-callback\} `onAwaitingSubscriptionUpdateParams` 方法已重命名为 `onAwaitingPurchaseParams`,现在使用 `AdaptyPurchaseParameters` 代替 `AdaptySubscriptionUpdateParameters`。这使您可以指定订阅替换参数(跨级别升降级)并指示价格是否为个性化价格([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),以及其他购买参数。 ```diff showLineNumbers - override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, - ) { - onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) - } + override fun onAwaitingPurchaseParams( + product: AdaptyPaywallProduct, + context: Context, + onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, + ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { + onPurchaseParamsReceived( + AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) + .withOfferPersonalized(true) + .build() + ) + return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked + } ``` 如果不需要额外的参数,您可以直接使用: ```kotlin showLineNumbers + override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` --- # File: migration-to-android-sdk-34 --- --- title: "迁移 Adapty Android SDK 至 v3.4" description: "迁移至 Adapty Android SDK v3.4,享受更好的性能和全新的变现功能。" --- Adapty SDK 3.4.0 是一个主要版本,引入了需要你进行迁移操作的改进内容。 ## 更新备用付费墙文件 \{#update-fallback-paywall-files\} 更新您的备用付费墙文件以确保与新 SDK 版本的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 用新文件[替换移动应用中现有的备用付费墙](android-use-fallback-paywalls)。 ## 更新观察者模式的实现 \{#update-implementation-of-observer-mode\} 如果你正在使用观察者模式,请确保更新其实现方式。 在之前的版本中,你需要手动恢复购买,Adapty 才能识别通过自有基础设施完成的交易——因为在观察者模式下,Adapty 无法直接访问这些交易。如果你使用了付费墙,还需要手动将每笔交易与触发它的付费墙关联起来。 在新版本中,您必须明确上报每笔交易,Adapty 才能识别它。如果您使用付费墙,还需要传入 variation ID,以便将交易与所用付费墙关联起来。 :::warning **不要跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在数据分析中,也不会发送到各集成渠道。 ::: ```diff showLineNumbers - Adapty.restorePurchases { result -> - if (result is AdaptyResult.Success) { - // success - } - } - - Adapty.setVariationId(transactionId, variationId) { error -> - if (error == null) { - // success - } - } + val transactionInfo = TransactionInfo.fromPurchase(purchase) + + Adapty.reportTransaction(transactionInfo, variationId) { result -> + if (result is AdaptyResult.Success) { + // success + } + } ``` ```diff showLineNumbers - Adapty.restorePurchases(result -> { - if (result instanceof AdaptyResult.Success) { - // success - } - }); - - Adapty.setVariationId(transactionId, variationId, error -> { - if (error == null) { - // success - } - }); + TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); + + Adapty.reportTransaction(transactionInfo, variationId, result -> { + if (result instanceof AdaptyResult.Success) { + // success + } + }); ``` --- # File: migration-to-android330 --- --- title: "迁移 Adapty Android SDK 至 v3.3" description: "迁移至 Adapty Android SDK v3.3,获得更好的性能和新的变现功能。" --- Adapty SDK 3.3.0 是一个重大版本更新,带来了若干改进,但可能需要你执行一些迁移步骤。 1. 更新在非付费墙编辑工具创建的付费墙中处理购买的方式。停止处理 `USER_CANCELED` 和 `PENDING_PURCHASE` 错误码。取消购买不再被视为错误,现在将出现在非错误的购买结果中。 2. 对于使用付费墙编辑工具创建的付费墙,将 `onPurchaseCanceled` 和 `onPurchaseSuccess` 事件替换为新的 `onPurchaseFinished` 事件。此更改的原因相同:取消购买不再被视为错误,将包含在非错误的购买结果中。 3. 更改付费墙编辑工具付费墙的 `onAwaitingSubscriptionUpdateParams` 方法签名。 4. 如果直接传递文件 URI,请更新用于提供备用付费墙的方法。 5. 更新 Adjust、AirBridge、Amplitude、AppMetrica、Appsflyer、Branch、Facebook Ads、Firebase 和 Google Analytics、Mixpanel、OneSignal、Pushwoosh 的集成配置。 ## 更新购买流程 \{#update-making-purchase\} 之前,已取消和待处理的购买会被视为错误,分别返回 `USER_CANCELED` 和 `PENDING_PURCHASE` 错误码。 现在引入了新的 `AdaptyPurchaseResult` 类,用于表示已取消、成功和待处理的购买状态。请按以下方式更新购买相关代码: ~~~diff Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { - val info = result.value - val profile = info?.profile - - if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) { - // Grant access to the paid features - } + when (val purchaseResult = result.value) { + is AdaptyPurchaseResult.Success -> { + val profile = purchaseResult.profile + if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { + // Grant access to the paid features + } + } + + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash + } + } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ~~~ 如需完整代码示例,请参阅[在移动应用中进行购买](android-making-purchases#make-purchase)页面。 ## 修改付费墙编辑工具的购买事件 \{#modify-paywall-builder-purchase-events\} 1. 添加 `onPurchaseFinished` 事件: ```diff showLineNumbers + public override fun onPurchaseFinished( + purchaseResult: AdaptyPurchaseResult, + product: AdaptyPaywallProduct, + context: Context, + ) { + when (purchaseResult) { + is AdaptyPurchaseResult.Success -> { + // Grant access to the paid features + } + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash) + } + } + } ``` 有关完整代码示例,请查看[成功、取消或待处理的购买](android-handling-events#successful-canceled-or-pending-purchase)及事件说明。 2. 移除 `onPurchaseCancelled` 事件的处理: ```diff showLineNumbers - public override fun onPurchaseCanceled( - product: AdaptyPaywallProduct, - context: Context, - ) {} ``` 3. 移除 `onPurchaseSuccess`: ```diff showLineNumbers - public override fun onPurchaseSuccess( - profile: AdaptyProfile?, - product: AdaptyPaywallProduct, - context: Context, - ) { - // Your logic on successful purchase - } ``` ## 修改 onAwaitingSubscriptionUpdateParams 方法的签名 \{#change-the-signature-of--onawaitingsubscriptionupdateparams-method\} 现在,如果在已有活跃订阅的情况下购买新订阅,请在以下情况下调用相应方法:若新订阅应替换当前活跃订阅,调用 `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))`;若活跃订阅应保持不变、新订阅单独新增,则调用 `onSubscriptionUpdateParamsReceived(null)`: ```diff showLineNumbers - public override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - ): AdaptySubscriptionUpdateParameters? { - return AdaptySubscriptionUpdateParameters(...) - } + public override fun onAwaitingSubscriptionUpdateParams( + product: AdaptyPaywallProduct, + context: Context, + onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, + ) { + onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) + } ``` 请参阅[升级订阅](android-handling-events#upgrade-subscription)文档章节以查看完整代码示例。 ## 更新备用付费墙的提供方式 \{#update-providing-fallback-paywalls\} 如果你通过文件 URI 来提供备用付费墙,请按以下方式更新相关代码: ```diff showLineNumbers val fileUri: Uri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback) + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback) ``` ```diff showLineNumbers Uri fileUri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback); + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback); ``` ## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\} 为确保集成能与 Adapty Android SDK 3.3.0 及更高版本正常工作,请按照以下各节所述更新以下集成的 SDK 配置。 ### Adjust 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。 ```diff showLineNumbers - Adjust.getAttribution { attribution -> - if (attribution == null) return@getAttribution - - Adjust.getAdid { adid -> - if (adid == null) return@getAdid - - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error -> - // Handle the error - } - } - } + Adjust.getAdid { adid -> + if (adid == null) return@getAdid + + Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> + if (error != null) { + // Handle the error + } + } + } + + Adjust.getAttribution { attribution -> + if (attribution == null) return@getAttribution + + Adapty.updateAttribution(attribution, "adjust") { error -> + if (error != null) { + // Handle the error + } + } + } ``` ```diff showLineNumbers val config = AdjustConfig(context, adjustAppToken, environment) config.setOnAttributionChangedListener { attribution -> attribution?.let { attribution -> - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error -> + Adapty.updateAttribution(attribution, "adjust") { error -> if (error != null) { // Handle the error } } } } Adjust.onCreate(config) ``` ### AirBridge 按以下方式更新您的移动应用代码。完整代码示例请参阅 [AirBridge 集成的 SDK 配置](airbridge#connect-your-app-to-airbridge)。 ```diff showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() { override fun onSuccess(result: String) { - val params = AdaptyProfileParameters.Builder() - .withAirbridgeDeviceId(result) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> + if (error != null) { + // Handle the error + } + } } override fun onFailure(throwable: Throwable) { } }) ``` ### Amplitude 按照以下方式更新您的移动应用代码。完整的代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。 ```diff showLineNumbers // For Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId val amplitudeUserId = amplitude.store.userId // - val params = AdaptyProfileParameters.Builder() - .withAmplitudeDeviceId(amplitudeDeviceId) - .withAmplitudeUserId(amplitudeUserId) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### AppMetrica 按如下方式更新您的移动应用代码。完整代码示例,请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。 ```diff showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceId = result?.deviceId ?: return - val params = AdaptyProfileParameters.Builder() - .withAppmetricaDeviceId(deviceId) - .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID") - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error -> + if (error != null) { + // Handle the error + } + } + + Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> + if (error != null) { + // Handle the error + } + } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { // Handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID)) ``` ### AppsFlyer 按照以下步骤更新您的移动应用代码。完整的代码示例,请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。 ```diff showLineNumbers val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(conversionData: Map) { - Adapty.updateAttribution( - conversionData, - AdaptyAttributionSource.APPSFLYER, - AppsFlyerLib.getInstance().getAppsFlyerUID(context) - ) { error -> - if (error != null) { - // Handle the error - } - } + val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context) + Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.updateAttribution(conversionData, "appsflyer") { error -> + if (error != null) { + // Handle the error + } + } } } ``` ### Branch 按照以下方式更新您的移动应用代码。完整的代码示例,请查看 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。 ```diff showLineNumbers // Login and update attribution Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> - Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.updateAttribution(data, "branch") { error -> + if (error != null) { + // Handle the error + } + } } } // Logout Branch.getAutoInstance(context).logout() ``` ### Facebook Ads 按如下方式更新您的移动应用代码。完整代码示例请参考 [Facebook Ads 集成的 SDK 配置](facebook-ads#connect-your-app-to-facebook-ads)。 ```diff showLineNumbers - val builder = AdaptyProfileParameters.Builder() - .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context)) - - Adapty.updateProfile(builder.build()) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier( + "facebook_anonymous_id", + AppEventsLogger.getAnonymousAppDeviceGUID(context) + ) { error -> + if (error != null) { + // Handle the error + } + } ``` ### Firebase 与 Google Analytics \{#firebase-and-google-analytics\} 按照以下示例更新您的移动应用代码。完整代码示例请参阅 [Firebase 与 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。 ```diff showLineNumbers // After Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> - Adapty.updateProfile( - AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build() - ) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> + if (error != null) { + // Handle the error + } + } } ``` ```diff showLineNumbers // After Adapty.activate() - FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build(); - - Adapty.updateProfile(params, error -> { - if (error != null) { - // Handle the error - } - }); - }); + FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { + if (error != null) { + // Handle the error + } + }); + }); ``` ### Mixpanel \{#mixpanel\} 按如下所示更新您的移动应用代码。完整代码示例,请查看 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。 ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withMixpanelUserId(mixpanelAPI.distinctId) - .build() - - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### OneSignal 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。 ```diff showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { - val params = AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.current.id) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // Handle the error } } } } ``` ```diff showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.getCurrent().getId()) - .build(); - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // Handle the error } }); }; ``` ```diff showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> - val params = AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // Handle the error } - } } } ``` ```diff showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { - AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build(); - - Adapty.updateProfile(params1, error -> { + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // Handle the error } - }); } }; ``` ### Pushwoosh 按以下方式更新您的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。 ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().hwid) - .build() - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // Handle the error } } ``` ```diff showLineNumbers - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().getHwid()) - .build(); - - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // Handle the error } }); ``` --- # File: migration-to-android-sdk-v3 --- --- title: "迁移 Adapty Android SDK 至 v3.0" description: "迁移至 Adapty Android SDK v3.0,享受更优性能与全新变现功能。" --- Adapty SDK v3.0 带来了重大改进,在迁移之前,请务必了解以下关键变更。 ## 更换弃用方法 \{#replace-deprecated-methods\} ### activateAdapty → Adapty.activate \{#activateadapty--adaptyadapty\} `activateAdapty` 函数已被弃用,请改用 `Adapty.activate`。 ```kotlin override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(false) .withCustomerUserId(customerUserId) .withIpAddressCollectionDisabled(false) .build() ) } ``` ```kotlin override fun onCreate() { super.onCreate() Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", observerMode = false, customerUserId = "YOUR_USER_ID") } ``` ### getPaywalls → getPaywall \{#getpaywalls--getpaywall\} `getPaywalls` 函数已被弃用,请改用 `getPaywall`。 ```kotlin Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // 使用付费墙 } is AdaptyResult.Error -> { val error = result.error // 处理错误 } } } ``` ```kotlin Adapty.getPaywalls(forceUpdate = false) { result -> when (result) { is AdaptyResult.Success -> { val paywalls = result.value.paywalls // 使用付费墙 } is AdaptyResult.Error -> { val error = result.error // 处理错误 } } } ``` ### getPaywallProducts → getPaywallProducts \{#getpaywallproducts--getpaywallproducts\} `getPaywallProducts` 函数签名已更新。 ```kotlin Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // 使用产品 } is AdaptyResult.Error -> { val error = result.error // 处理错误 } } } ``` ```kotlin Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // 使用产品 } is AdaptyResult.Error -> { val error = result.error // 处理错误 } } } ``` ## 更新后的模型 \{#updated-models\} ### AdaptyPaywall \{#adaptypaywall\} `AdaptyPaywall` 模型已更新,包含以下变更: | 属性 | v2.x | v3.0 | |------|------|------| | `name` | 已弃用 | 已移除 | | `variationId` | 保留 | 保留 | | `revision` | 保留 | 保留 | | `isPromo` | 已弃用 | 已移除 | ### AdaptyProduct \{#adaptyprodcut\} `AdaptyProduct` 模型已更新,包含以下变更: | 属性 | v2.x | v3.0 | |------|------|------| | `variationId` | 保留 | 已移除(移至 `AdaptyPaywall`)| | `paywallName` | 保留 | 已移除 | | `paywallABTestName` | 保留 | 已移除 | ## 移除的功能 \{#removed-functionality\} 以下功能已在 v3.0 中移除: - **Promo campaigns**:不再支持促销活动功能 - **getPromo**:`getPromo` 方法已移除 - **setExternalAnalyticsEnabled**:此方法已移除,请参阅[分析集成文档](analytics-integration)了解替代方案 ## 迁移步骤 \{#migration-steps\} 1. 将 `build.gradle` 中的 SDK 版本更新至 `3.0.0` 2. 将所有 `activateAdapty` 调用替换为 `Adapty.activate`,并使用新的 `AdaptyConfig.Builder` 3. 将所有 `getPaywalls` 调用替换为 `getPaywall`,并传入版位 ID 4. 移除所有对已弃用属性的引用 5. 测试所有购买流程,确保功能正常 如需获取完整的迁移支持,请联系 [Adapty 支持团队](https://adapty.io/contact)。 Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder),这是用于创建付费墙的全新无代码工具,操作简单、功能强大。凭借其极高的灵活性和丰富的设计能力,你的付费墙将变得更加高效、更具盈利潜力。 Adapty SDK 以 BoM(Bill of Materials)方式分发,确保应用中 Adapty SDK 与 AdaptyUI SDK 的版本始终保持一致。 如需迁移至 v3.0,请按以下步骤更新代码: ```diff showLineNumbers dependencies { ... - implementation 'io.adapty:android-sdk:2.11.5' - implementation 'io.adapty:android-ui:2.11.3' + implementation platform('io.adapty:adapty-bom:3.0.4') + implementation 'io.adapty:android-sdk' + implementation 'io.adapty:android-ui' } ``` ```diff showLineNumbers dependencies { ... - implementation("io.adapty:android-sdk:2.11.5") - implementation("io.adapty:android-ui:2.11.3") + implementation(platform("io.adapty:adapty-bom:3.0.4")) + implementation("io.adapty:android-sdk") + implementation("io.adapty:android-ui") } ``` ```diff showLineNumbers //libs.versions.toml [versions] .. - adapty = "2.11.5" - adaptyUi = "2.11.3" + adaptyBom = "3.0.4" [libraries] .. - adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" } - adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" } + adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } + adapty = { module = "io.adapty:android-sdk" } + adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... + implementation(libs.adapty.bom) implementation(libs.adapty) implementation(libs.adapty.ui) } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:53.291Z_ _Successfully processed: 41/41 files_ # API - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.293Z Total files: 18 --- # File: developer-cli --- --- title: "开发者 CLI" description: "Adapty 开发者 CLI 概览。" --- **Adapty 开发者 CLI** 是一款命令行工具,无需打开看板即可管理您的 Adapty 账户。它提供了主要的配置功能,可从您的终端或自动化环境中访问。 **使用 CLI 可以完成以下操作:** - 在您的 Adapty 账户中创建和配置 iOS 及 Android 应用 - 定义访问等级——应用在运行时检查的订阅层级 - 设置产品并将其映射到 App Store 和 Google Play 商店 ID - 创建付费墙并为其分配产品 - 配置版位以通过 SDK 获取付费墙 :::link 正在使用 AI 助手或 MCP 客户端?[Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) 可帮助 LLM 使用 CLI。 ::: --- # File: developer-cli-quickstart --- --- title: "Adapty 开发者 CLI 快速入门指南" description: "使用开发者 CLI 端到端配置您的 Adapty 账户——从创建应用到上线版位,只需几条命令。" --- :::link 正在使用 AI 助手?我们提供了 [Adapty CLI 技能包](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli),可帮助 LLM 更好地使用 CLI。 ::: Adapty CLI 让你完全通过命令行完成应用配置。如果你偏好终端工具或 [MCP 客户端](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli),可以用它替代[看板快速入门](integrate-payments)。 :::note 将 Adapty 连接到 App Store Connect 和 Google Play 仍需在看板中进行一次性配置——详见第 3 步。 ::: 完成后,你的应用、访问等级、产品、付费墙和版位都会显示在 [Adapty 看板](https://app.adapty.io) 中。 ## 1. 安装 CLI \{#1-install-the-cli\} 需要 [Node.js](https://nodejs.org/en/download) 18 或更高版本。 运行以下命令安装 CLI: ```bash npm install -g adapty ``` 或者,直接运行: ```bash npx adapty auth login ``` ## 2. 身份验证 \{#2-authenticate\} 运行登录命令,将 CLI 连接到您的 Adapty 账户。 ```bash adapty auth login ``` CLI 将打开一个浏览器标签页。将终端中显示的代码与浏览器中显示的代码进行匹配,然后点击 **Authorize**。身份验证完成后,终端将显示确认信息。 ## 3. 创建您的应用 \{#3-create-your-app\} Adapty 中的应用代表您的移动应用程序。一个 Adapty 应用可同时连接 App Store 和 Google Play——无论您在多少个商店上架,只需创建一个即可。 ```bash adapty apps create --title "My App" --platform ios --platform android --apple-bundle-id com.example.app --google-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform android --google-bundle-id com.example.app ``` 该命令会返回一个 ``。后续所有命令都需要用到这个 ID。 :::important 在继续之前,请先在 Adapty 看板中将你的应用连接到 App Store Connect 和 Google Play。第 5 步需要用到两个平台的产品 ID。 - [连接 App Store Connect](app-store-connection-configuration) - [连接 Google Play](google-play-store-connection-configuration) ::: ## 4. 创建访问等级(可选)\{#4-create-an-access-level-optional\} [访问等级](access-level)用于控制用户购买后可以访问的内容。与其在应用中检查用户是否购买了某个特定产品,不如检查用户是否拥有某个访问等级。这样可以将应用逻辑与具体的产品 ID 解耦。 每个新应用都会自动创建一个 `premium` 访问等级。**对于大多数应用,你可以跳过这一步。** 在第 5 步中直接使用 `premium` 作为访问等级 ID 即可。 仅当不同产品为不同用户组解锁不同功能时才运行此命令——例如,"Basic" 订阅用户和 "Pro" 订阅用户可以访问应用的不同部分。 ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` - `--sdk-id` 是你在应用代码中用于检查某功能是否对用户开放的标识符(例如 `if user.hasAccessLevel("pro")`)。如果跳过此步骤并使用默认访问等级,其 `--sdk-id` 为 `premium`。 - `--title` 是在 Adapty 看板中供你参考的显示标签。 该命令返回一个 ``。 ## 5. 创建产品 \{#create-a-product\} 在 Adapty 中,[产品](product)代表应用内销售的任何内容——订阅或一次性购买。来自 App Store Connect 和 Google Play 的商品可以归并为一个 Adapty 产品,统一管理。 你需要从各应用商店获取产品 ID:从 App Store Connect 获取 Apple 产品 ID,从 Google Play Console 获取 Android 产品 ID 和基础方案 ID。详情请参阅[产品](quickstart-products)。 如果你跳过了第 4 步,请使用第 3 步 `apps create` 命令返回的 `default_access_level.id` 作为你的 ``。 :::important 此处关联的商店产品 ID(`--ios-product-id`、`--android-product-id`)在创建后无法更改。如需使用不同的商店产品 ID,请创建新产品。 ::: ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id --android-product-id --android-base-plan-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --android-product-id --android-base-plan-id ``` 该命令返回一个 ``。 ## 6. 创建付费墙 \{#6-create-a-paywall\} [付费墙](paywalls) 是承载产品的容器。在 Adapty 中,付费墙是向用户呈现产品的唯一方式。每个产品都必须先放入付费墙,才能在应用中展示。 :::important 付费墙与版位关联后,其中的产品将无法修改。如需使用不同的产品,请创建新的付费墙,并将版位指向该付费墙。 ::: ```bash adapty paywalls create --app --title "My Paywall" --product-id ``` ```bash adapty paywalls create --app --title "My Paywall" --product-id --product-id ``` 该命令会返回一个 ``。 ## 7. 创建版位 \{#create-a-placement\} [版位](placements) 是应用中展示付费墙的位置。你在代码里唯一需要硬编码的就是版位 ID,其余所有内容——向哪些用户展示哪个付费墙——都可以在看板中管理,无需发布新版本。 `--developer-id` 是你后续在应用代码中向 Adapty 查询该位置应展示哪个付费墙时所引用的字符串。建议取一个能描述该位置的名称,例如 `"main"`、`"onboarding"` 或 `"settings"`。 ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` `--audiences` 标志控制向哪些用户显示哪个付费墙。上面的示例设置了一个默认目标受众——所有用户在该版位看到的都是同一个付费墙。 ## 下一步 \{#whats-next\} 所有实体现在都可以在 [Adapty 看板](https://app.adapty.io) 中看到。接下来: - [设计你的付费墙](adapty-paywall-builder) — 使用无代码的付费墙编辑工具为刚创建的付费墙添加视觉元素、布局和文案。 - [集成 Adapty SDK](quickstart-sdk) — 将 SDK 添加到你的应用中,以获取并展示版位。 - 将不同的用户[市场细分](segments)路由到不同的付费墙 — 请参阅完整参考文档中的 [`placements update`](developer-cli-reference#adapty-placements-update) 和 [`segments list`](developer-cli-reference#adapty-segments-list)。 --- # File: developer-cli-authentication --- --- title: "Adapty 开发者 CLI 中的身份验证" description: "如何使用 Adapty 开发者 CLI 进行身份验证。" --- :::link 使用 AI 助手?可使用 [Adapty CLI 技能](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) 来帮助 LLM 使用 CLI。 ::: CLI 需要通过身份验证才能调用 Adapty API。 ## 登录 \{#log-in\} 登录步骤: 1. 在终端中运行: ```bash adapty auth login ``` 2. CLI 以 `XXXX-XXXX` 格式打印验证码,并在浏览器中打开 Adapty 看板。 3. 在授权页面上,确认验证码与终端输出一致。 4. 点击 **Authorize**。浏览器显示"CLI authorized! You can close this tab." 5. 返回终端,CLI 确认您已通过身份验证。 如果验证码在您授权之前过期,或者您点击了 **Deny**,请再次运行以下命令以重新开始流程: ```bash adapty auth login ``` ## 管理身份验证 \{#manage-authentication\} ### 检查身份验证状态 \{#check-authentication-status\} 要查看当前身份验证状态,请运行: ```bash adapty auth status ``` 已通过身份验证时,输出会显示您的电子邮件、经过掩码处理的令牌前缀以及本地配置文件的路径: ``` Email: you@example.com Token: abcd1234**** Config: ~/.config/adapty/config.json ``` 未通过身份验证时: ``` Not authenticated. Run `adapty auth login`. ``` ### 验证您的令牌 \{#verify-your-token\} 要确认令牌有效并查看您的账户详情,请运行: ```bash adapty auth whoami ``` 与 `adapty auth status` 不同,此命令会向服务器发起实时请求以验证令牌。 ### 退出登录 \{#log-out\} 要在本地清除已存储的凭据,请运行: ```bash adapty auth logout ``` 这将清除 `~/.config/adapty/config.json`。令牌在服务器端仍然有效,直到其过期为止——如果您需要立即使其失效,请改用 `adapty auth revoke`。 ### 撤销您的令牌 \{#revoke-your-token\} 要在服务器上使令牌失效并在本地将其清除,请运行: ```bash adapty auth revoke ``` 当您希望完全使令牌失效时(例如您的凭据可能已泄露),请使用此命令。撤销后,请运行 `adapty auth login` 重新进行身份验证。 ## 令牌错误 \{#token-errors\} 如果令牌被撤销或变为无效,CLI 命令将返回 401 错误。要重新进行身份验证,请运行: ```bash adapty auth login ``` --- # File: developer-cli-reference --- --- title: "Adapty 开发者 CLI 完整参考" description: "所有 Adapty 开发者 CLI 命令的完整参考文档。" --- :::link 正在使用 AI 助手?可以使用 [Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) 帮助大语言模型操作 CLI。 ::: 本文列出了所有 Adapty CLI 命令及其参数、标志和可接受的值。 :::link 有关身份验证设置和令牌管理,请参阅[身份验证](developer-cli-authentication)。 ::: ## 全局标志 \{#global-flags\} 这些标志适用于所有命令。 | 标志 | 描述 | |---|---| | `--json` | 以 JSON 格式输出,而非格式化文本 | | `--help` | 显示命令帮助 | 所有 `list` 命令还接受分页标志: | 标志 | 默认值 | 描述 | |---|---|---| | `--page` | `1` | 页码 | | `--page-size` | `20` | 每页条目数(最大:100) | ## 应用 \{#apps\} 管理 Adapty 账户中的应用。有关基于看板的配置,请参阅 [App settings](general)。 ### adapty apps list \{#adapty-apps-list\} 列出 Adapty 账户中的所有应用。 ```bash adapty apps list ``` 接受[分页标志](#global-flags)。 ### adapty apps get \{#adapty-apps-get\} 获取特定应用的详细信息。 ```bash adapty apps get ``` | 参数 | 描述 | |---|---| | `app-id` | 应用 ID(UUID) | ### adapty apps create \{#adapty-apps-create\} 创建新应用。 ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--title` | 是 | 应用标题 | | `--platform` | 是 | 平台:`ios` 或 `android`。重复使用以支持两者:`--platform ios --platform android` | | `--apple-bundle-id` | 与 `--platform ios` 一起使用时必填 | Apple bundle ID | | `--google-bundle-id` | 与 `--platform android` 一起使用时必填 | Google bundle ID | ### adapty apps update \{#adapty-apps-update\} 更新现有应用。 ```bash adapty apps update --title "New Name" ``` | 参数 | 描述 | |---|---| | `app-id` | 应用 ID(UUID) | | 标志 | 描述 | |---|---| | `--title` | 新的应用标题 | | `--apple-bundle-id` | 新的 Apple bundle ID | | `--google-bundle-id` | 新的 Google bundle ID | 至少需要一个标志。`--platform` 在创建后无法更改。 ## 访问等级 \{#access-levels\} ### adapty access-levels list \{#adapty-access-levels-list\} 列出应用的所有[访问等级](access-level)。 ```bash adapty access-levels list --app ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 接受[分页标志](#global-flags)。 ### adapty access-levels get \{#adapty-access-levels-get\} 获取特定[访问等级](access-level)的详细信息。 ```bash adapty access-levels get --app ``` | 参数 | 描述 | |---|---| | `access-level-id` | 访问等级 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | ### adapty access-levels create \{#adapty-access-levels-create\} 创建新的[访问等级](access-level)。 ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--sdk-id` | 是 | 应用代码中用于检查访问权限的标识符(例如 `"pro"` 或 `"premium"`) | | `--title` | 是 | Adapty 看板中的显示标签 | ### adapty access-levels update \{#adapty-access-levels-update\} 更新现有[访问等级](access-level)。 ```bash adapty access-levels update --app --title "Pro Access" ``` | 参数 | 描述 | |---|---| | `access-level-id` | 访问等级 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--title` | 是 | 新的显示标签 | `--sdk-id` 在创建后无法更改。 ## 产品 \{#products\} ### adapty products list \{#adapty-products-list\} 列出应用的所有[产品](product)。 ```bash adapty products list --app ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 接受[分页标志](#global-flags)。 ### adapty products get \{#adapty-products-get\} 获取特定[产品](product)的详细信息。 ```bash adapty products get --app ``` | 参数 | 描述 | |---|---| | `product-id` | 产品 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | ### adapty products create 创建新[产品](product)。 :::important 商店产品 ID(`--ios-product-id`、`--android-product-id`、`--android-base-plan-id`)在创建后不可更改。如需使用不同的商店产品 ID,请创建新产品。 ::: ```bash adapty products create --app --title "Monthly" --access-level-id --period monthly --ios-product-id com.example.monthly ``` | 参数 | 是否必填 | 说明 | |---|---|---| | `--app` | 是 | App ID(UUID) | | `--title` | 是 | 产品标题 | | `--access-level-id` | 是 | 该产品解锁的[访问等级](access-level) ID(UUID) | | `--period` | 是 | 订阅周期:`weekly`、`monthly`、`2_months`、`3_months`、`6_months`、`yearly`、`lifetime` | | `--ios-product-id` | 至少需要填写一个平台 | App Store Connect 中的产品 ID | | `--android-product-id` | 至少需要填写一个平台 | Google Play Console 中的产品 ID | | `--android-base-plan-id` | 使用 `--android-product-id` 时必填,除非 `--period lifetime` | Google Play Console 中的基础方案 ID | ### adapty products update 更新现有[产品](product)。 商店产品 ID(`--ios-product-id`、`--android-product-id`)在创建后不可更改,因此此命令不支持修改这些字段。如需使用不同的商店产品 ID,请创建新产品。 ```bash adapty products update --app --title "Monthly" --access-level-id ``` | 参数 | 描述 | |---|---| | `product-id` | 产品 ID(UUID) | | 参数 | 是否必填 | 说明 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--title` | 否 | 产品标题 | | `--access-level-id` | 否 | 该产品解锁的[访问等级](access-level) ID(UUID) | ## 付费墙 \{#paywalls\} ### adapty paywalls list \{#adapty-paywalls-list\} 列出应用的所有[付费墙](paywalls)。 ```bash adapty paywalls list --app ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 接受[分页标志](#global-flags)。 ### adapty paywalls get \{#adapty-paywalls-get\} 获取特定[付费墙](paywalls)的详细信息。 ```bash adapty paywalls get --app ``` | 参数 | 描述 | |---|---| | `paywall-id` | 付费墙 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | ### adapty paywalls create \{#adapty-paywalls-create\} 创建新[付费墙](paywalls)。 ```bash adapty paywalls create --app --title "Default Paywall" --product-id ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--title` | 是 | 付费墙标题 | | `--product-id` | 是 | [产品](product) ID(UUID)。重复使用以支持多个产品:`--product-id --product-id ` | ### adapty paywalls update 替换现有[付费墙](paywalls)的所有字段。 :::important 付费墙一旦与版位关联,其产品便无法更改。如需在已上线的付费墙中使用不同的产品,请创建新的付费墙并更新版位指向该付费墙。 ::: ```bash adapty paywalls update --app --title "Default Paywall" --product-id ``` 该命令会替换付费墙的所有字段,包括完整的产品列表。 | 参数 | 说明 | |---|---| | `paywall-id` | 付费墙 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--title` | 是 | 付费墙标题 | | `--product-id` | 是 | [产品](product) ID(UUID)。多个产品时重复使用:`--product-id --product-id ` | ### adapty paywalls placements 列出当前使用指定[付费墙](paywalls)的所有[版位](placements)。 ```bash adapty paywalls placements --app ``` | 参数 | 说明 | |---|---| | `paywall-id` | 付费墙 ID(UUID) | | 标志 | 是否必填 | 说明 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 在替换付费墙之前,可以使用此命令查看哪些版位会受到影响。 ## 版位 \{#placements\} ### adapty placements list \{#adapty-placements-list\} 列出应用的所有[版位](placements)。 ```bash adapty placements list --app ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 接受[分页标志](#global-flags)。 ### adapty placements get 获取特定[版位](placements)的详细信息。 ```bash adapty placements get --app ``` | 参数 | 描述 | |---|---| | `placement-id` | 版位 ID(UUID) | | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 响应包含一个 `audiences` 数组。每条记录的格式为 `{segment_ids, paywall_id, priority}`。默认目标受众的 `segment_ids: []`,优先级值最高(最后评估)。格式化的人类可读输出还会在顶层显示一个 `Paywall ID`,该值来自默认目标受众,仅供参考。`--json` 返回原始 API 数据,不做任何更改。 ### adapty placements create 创建新的[版位](placements)。 ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | App ID(UUID) | | `--title` | 是 | 版位标题 | | `--developer-id` | 是 | 用于在应用代码中请求该[版位](placements)的字符串标识符 | | `--audiences` | 二选一 | 由 `{segment_ids, paywall_id, priority}` 条目组成的 JSON 数组。参见[目标受众格式](#audiences-shape) | | `--paywall-id` | 二选一 | **已废弃。** [付费墙](paywalls) ID(UUID)。在客户端被包装为单个默认目标受众 | `--audiences` 和 `--paywall-id` 只能传其中一个。两个都传或都不传会报错。 :::warning `--paywall-id` 已弃用,将在未来版本中移除。传入该参数时,CLI 会在 stderr 打印警告,并将该值转换为默认目标受众。新的自动化流程请使用 `--audiences`。 ::: ### adapty placements update 替换现有[版位](placements)的所有字段。 ```bash adapty placements update --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` 此命令会替换版位的所有字段,包括完整的目标受众列表。 | 参数 | 说明 | |---|---| | `placement-id` | 版位 ID(UUID) | | 参数 | 是否必填 | 说明 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | | `--title` | 是 | 版位标题 | | `--developer-id` | 是 | 在应用代码中用于请求该[版位](placements)的字符串标识符 | | `--audiences` | 二选一 | 由 `{segment_ids, paywall_id, priority}` 条目组成的 JSON 数组。详见[目标受众格式](#audiences-shape) | | `--paywall-id` | 二选一 | **已废弃。** [付费墙](paywalls) ID(UUID)。将所有目标受众替换为单个默认受众 | :::warning 传入 `--paywall-id` 会覆盖版位上的所有目标受众,特定市场细分的目标受众将被删除。若要保留它们,请使用 `--audiences` 并将所有需要保留的条目一并传入。 ::: #### 目标受众结构 \{#audiences-shape\} `--audiences` 标志接受一个 JSON 数组,每个条目包含: | 字段 | 类型 | 描述 | |---|---|---| | `segment_ids` | `string[]` | 此目标受众所针对的[市场细分](segments) ID,长度为 0 或 1。空数组表示**默认目标受众**——作为不匹配任何其他市场细分的用户的兜底选项 | | `paywall_id` | `string` | 向此目标受众中的用户展示的[付费墙](paywalls) ID(UUID) | | `priority` | `number` | 从 0 开始,在版位内唯一。目标受众按从低到高的顺序依次评估;默认目标受众的优先级值必须最高 | 每个版位必须有且仅有一个默认目标受众。 示例包含一个定向目标受众和一个默认目标受众: ```bash adapty placements update --app --title "Main" --developer-id "main" \ --audiences '[{"segment_ids":[""],"paywall_id":"","priority":0},{"segment_ids":[],"paywall_id":"","priority":1}]' ``` 要在多个版位之间替换付费墙而不丢失细分受众路由配置: 1. 找到受影响的版位: ```bash adapty paywalls placements --app ``` 2. 对每个版位,读取完整的 `audiences` 数组: ```bash adapty placements get --app --json ``` 3. 在客户端替换匹配的 `paywall_id` 值。 4. 将修改后的内容写回: ```bash adapty placements update --app --title "" --developer-id "<developer-id>" --audiences '<modified-payload>' ``` ## 市场细分 \{#segments\} [市场细分](segments)通过 CLI 只读访问。请在 [Adapty 看板](https://app.adapty.io)中创建和编辑它们。使用以下命令在配置版位目标受众时查找市场细分 ID。 ### adapty 市场细分列表 \{#adapty-segments-list\} 列出某个应用的所有[市场细分](segments)。 ```bash adapty segments list --app <app-id> ``` | 标志 | 是否必填 | 描述 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 支持[分页标志](#global-flags)。 ### adapty segments get 获取指定[市场细分](segments)的详细信息。 ```bash adapty segments get --app <app-id> <segment-id> ``` | 参数 | 说明 | |---|---| | `segment-id` | 市场细分 ID(UUID) | | 标志 | 必填 | 说明 | |---|---|---| | `--app` | 是 | 应用 ID(UUID) | 响应包含 `id`、`title` 和 `description`。过滤规则不通过此 API 暴露。 ## 身份验证 \{#auth\} | 命令 | 描述 | |---|---| | `adapty auth login` | 通过浏览器使用设备流进行身份验证 | | `adapty auth logout` | 清除本地存储的凭据 | | `adapty auth whoami` | 向服务器验证令牌并显示用户信息 | | `adapty auth status` | 不发起服务器调用,显示本地身份验证状态 | | `adapty auth revoke` | 在服务器端撤销令牌并在本地清除 | 有关每个命令的完整详情,请参阅[身份验证](developer-cli-authentication)。 --- # File: getting-started-with-server-side-api --- --- title: "服务端 API" description: "快速上手 Adapty 服务端 API,管理订阅。" --- :::tip 在使用 AI 编程助手?请查看[从后端检查并授予订阅访问权限](server-side-api-with-ai),一页搞定完整流程。 ::: 通过该 API,你可以: 1. 查看用户的订阅状态。 2. 通过访问等级激活用户的订阅。 3. 获取用户属性。 4. 设置用户属性。 5. 获取并更新付费墙配置。 <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note 如需追踪订阅事件,请在 Adapty 中使用 [Webhook](webhook) 集成,或直接与您现有的服务进行集成。 ::: ## 案例一:同步网页端与移动端的订阅用户 \{#case-1-sync-subscribers-between-web-and-mobile\} 如果你使用 Stripe、ChargeBee 或其他网页支付服务商,可以轻松同步订阅用户。操作步骤如下: 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)</InlineTooltip>。 2. 通过 API [查询用户订阅状态](api-adapty/operations/getProfile)。 3. 如果用户处于免费增值计划,在你的网站上展示付费墙。 4. 支付成功后,通过 API 在 Adapty 中[更新订阅状态](api-adapty/operations/setTransaction)。 5. 订阅用户的状态将自动与移动端 App 保持同步。 ## 案例 2:授予订阅 \{#case-2-grant-a-subscription\} :::note 出于安全原因,您无法通过移动端 SDK 授予订阅。 ::: 如果您通过自己的线上商店、Amazon Appstore、Microsoft Store 或 Google Play 和 App Store 以外的其他平台进行销售,则需要将这些交易同步到 Adapty,以便授予用户访问权限并在分析中追踪该交易。 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. [在 Adapty 看板中为你的产品设置自定义商店](custom-store)。 3. 使用 [Set transaction](api-adapty/operations/setTransaction) API 请求将交易同步到 Adapty。 ## 情境 3:授予访问等级 \{#case-3-grant-an-access-level\} 假设你正在运行一个提供 7 天免费试用的促销活动,并希望跨平台保持一致的用户体验。要与移动应用同步,请按以下步骤操作: 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. 使用 API [授予高级访问等级](api-adapty/operations/grantAccessLevel),有效期 7 天。 7 天后,未订阅的用户将被降级至免费套餐。 ## 情形 4:同步用户属性和自定义特性 \{#case-4-sync-users-properties-and-custom-attributes\} 如果你的用户有自定义特性(例如语言学习应用中用户已学习的单词数量),同样可以进行同步。 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. 通过 API 或 SDK [更新属性](api-adapty/operations/updateProfile)。 这些自定义属性可用于创建市场细分和运行 A/B 测试。 ## 案例 5:管理付费墙配置 \{#case-5-manage-paywall-configurations\} 你可以[更新付费墙中的远程配置](api-adapty/operations/updatePaywall),无需重新发布应用即可动态调整付费墙的外观和行为。 --- **下一步:** - 继续了解[服务端 API 授权](ss-authorization) - 请求: - [获取用户画像](api-adapty/operations/getProfile) - [创建用户画像](api-adapty/operations/createProfile) - [更新用户画像](api-adapty/operations/updateProfile) - [删除用户画像](api-adapty/operations/deleteProfile) - [授予访问等级](api-adapty/operations/grantAccessLevel) - [撤销访问等级](api-adapty/operations/revokeAccessLevel) - [设置交易](api-adapty/operations/setTransaction) - [验证购买、向用户授予访问等级并导入其交易历史](api-adapty/operations/validateStripePurchase) - [添加集成标识符](api-adapty/operations/setIntegrationIdentifiers) - [获取付费墙](api-adapty/operations/getPaywall) - [列出付费墙](api-adapty/operations/listPaywalls) - [更新付费墙](api-adapty/operations/updatePaywall) --- # File: ss-authorization --- --- title: "服务器端 API 授权与请求格式" description: "" --- ## 授权 \{#authorization\} API 请求必须通过 Authorization 请求头进行身份验证,可使用您的私密或公开 API 密钥。您可以在 [**App Settings**](https://app.adapty.io/settings/general) 中找到这些密钥。值的格式为 `Api-Key {your-secret-api-key}`,例如 `Api-Key secret_live_...`。 :::important API 密钥与应用绑定。如果您有多个应用,请确保为每个应用使用不同的密钥。 ::: ## 请求格式 \{#request-format\} **请求头** 服务端 API 请求需要特定的请求头和 JSON 请求体。请参考以下信息构建你的请求。 | **Header** | **Description** | | --------------------------- | ------------------------------------------------------------ | | **adapty-profile-id** | <p>用户的 Adapty 用户画像 ID。可在 [Adapty 看板 -> **Profiles**](https://app.adapty.io/profiles/users) -> 具体用户画像页面的 **Adapty ID** 字段中查看。</p><p>与 **adapty-customer-user-id** 可互换使用,任选其一即可。</p> | | **adapty-customer-user-id** | <p>用户在您系统中的 ID。可在 [Adapty 看板 -> **Profiles**](https://app.adapty.io/profiles/users) -> 具体用户画像页面的 **Customer user ID** 字段中查看。</p><p>与 **adapty-profile-id** 可互换使用,任选其一即可。</p><p>⚠️ 仅当您在应用代码中通过 Adapty SDK <InlineTooltip tooltip="在应用中识别用户">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 及 [Unity](unity-identifying-users)</InlineTooltip> 识别用户时才有效。</p> | | **adapty-platform** | (可选)指定安装应用的设备平台。建议在 [Create profile](api-adapty/operations/createProfile) 和 [Update profile](api-adapty/operations/updateProfile) 请求中修改 [Installation Meta](server-side-api-objects#installation-meta) 对象时设置此参数,因为该参数取决于用户所使用的设备,而同一用户可能拥有多台设备。可选值:`iOS`、`macOS`、`iPadOS`、`visionOS`、`Android` 或 `web`。 | | **Content-Type** | 设置为 `application/json`,以便 API 正确处理请求。 | **请求体** API 需要一个 JSON 格式的请求体,其中包含请求所需的数据。 ## 请求频率限制 \{#rate-limits\} 为避免请求被限速,请确保每个应用的请求数量保持在每分钟 40,000 次以下。 如果超出此限制,系统可能会降速或暂时拦截后续请求,以确保所有用户都能获得最佳体验。 ## 轮换 API 密钥 \{#rotate-api-keys\} 如需轮换私密 API 密钥: 1. 在 **Settings → General** 中,点击 **Generate new key**,然后点击旧密钥旁边的垃圾桶图标。 2. 更新应用中使用的密钥。 --- **下一步:请求:** - [获取用户画像](api-adapty/operations/getProfile) - [创建用户画像](api-adapty/operations/createProfile) - [更新用户画像](api-adapty/operations/updateProfile) - [删除用户画像](api-adapty/operations/deleteProfile) - [授予访问等级](api-adapty/operations/grantAccessLevel) - [撤销访问等级](api-adapty/operations/revokeAccessLevel) - [设置交易](api-adapty/operations/setTransaction) - [验证购买、为客户提供访问等级并导入其交易历史](api-adapty/operations/validateStripePurchase) - [获取付费墙](api-adapty/operations/getPaywall) - [列出付费墙](api-adapty/operations/listPaywalls) - [更新付费墙](api-adapty/operations/updatePaywall) --- # File: server-side-api-specs --- --- title: "服务端 API 请求" description: "探索 Adapty 的服务端 API 规范,实现高级集成。" --- Adapty 的服务端 API 使您能够以编程方式访问和管理订阅数据,从而与现有服务和基础设施实现无缝集成。无论是跨平台同步数据、授予访问等级,还是在 Stripe 中验证购买,此 API 都提供了保持系统同步并提升用户参与度所需的工具。 ## Postman 集合与环境 \{#postman-collection-and-environment\} 为了简化服务端 API 的使用,我们准备了 Postman 集合和环境文件,你可以下载并导入到 Postman 中。 - **Request Collection(请求集合)**:包含 Adapty 服务端 API 中所有可用的请求。请注意,其中使用了变量,你可以在环境中定义这些变量的值。 - **Environment(环境)**:包含一组变量,你只需定义一次其值即可。我们为服务端 API、Web API 和分析导出 API 准备了统一的环境配置,方便你使用。将该环境设为活跃状态后,Postman 将自动在请求中替换已定义的变量值。 :::tip [下载集合与环境](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_server_side_API_postman_collection.zip) ::: 有关如何将集合和环境导入 Postman 的详细说明,请参阅 [Postman 文档](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/)。 ### 使用的变量 \{#variables-used\} 我们为服务端 API、Web API 和分析导出 API 创建了统一的环境,以简化您的工作流程。以下是服务端 API 的专用变量: | 变量 | 描述 | 示例值 | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | secret_api_key | 可在 [**App settings**](https://app.adapty.io/settings/general) 的 **Secret key** 字段中找到。 | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | 您在系统中使用的用户 ID。在 Adapty 看板中,可在用户画像的 **Customer user ID** 字段中找到。 | `john.doe@example.com` | | adapty-profile-id | Adapty 分配的用户 ID。在 Adapty 看板中,可在用户画像的 **Adapty ID** 字段中找到。 | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | | Adapty-platform | 用户使用您应用的平台。可选值:`iOS`、`macOS`、`iPadOS`、`visionOS`、`Android`、`web`。 | `iOS` | | stripe_token | 代表唯一购买行为的 Stripe 对象 Token,例如订阅(`sub_XXX`)或付款意图(`pi_XXX`)。 | `sub_1JY8xLLy6P12345a` | **下一步:请求:** - [获取用户画像](api-adapty/operations/getProfile) - [创建用户画像](api-adapty/operations/createProfile) - [更新用户画像](api-adapty/operations/updateProfile) - [删除用户画像](api-adapty/operations/deleteProfile) - [授予访问等级](api-adapty/operations/grantAccessLevel) - [撤销访问等级](api-adapty/operations/revokeAccessLevel) - [设置交易](api-adapty/operations/setTransaction) - [验证购买、为客户提供访问等级并导入其交易历史](api-adapty/operations/validateStripePurchase) - [添加集成标识符](api-adapty/operations/setIntegrationIdentifiers) - [获取付费墙](api-adapty/operations/getPaywall) - [列出付费墙](api-adapty/operations/listPaywalls) - [更新付费墙](api-adapty/operations/updatePaywall) - [创建虚拟货币交易](api-adapty/operations/createVirtualCurrencyTransaction) - [列出虚拟货币交易](api-adapty/operations/listVirtualCurrencyTransactions) - [列出虚拟货币余额](api-adapty/operations/listVirtualCurrencyBalances) --- # File: api-guides --- --- title: "API 使用指南" description: "了解如何使用服务端 API 执行特定任务。" --- 本节包含多个使用指南,涵盖不同的使用场景,帮助你通过服务端 API 和 Adapty SDK 完成具体任务。 <CustomDocCardList /> --- # File: sync-subscribers-from-web --- --- title: "同步 Web 与移动端的购买记录" description: "在 Web 和移动端之间同步订阅者信息。" --- 如果你的用户可以在**网站**上购买产品,你可以让他们的访问等级在**移动应用**中自动保持同步。 本指南将介绍如何通过 Adapty API 和 SDK 实现这一功能。 #### 使用场景示例 \{#sample-use-case\} 假设在你的应用中,用户可以在移动端和网页端注册免费版计划。你允许他们在网站上通过 Stripe 或 Chargebee 升级到高级计划。 用户在网页端完成订阅后,你希望他们立即在移动应用中获得高级访问权限——无需等待,也无需重新登录。 这正是 Adapty 帮你自动化实现的功能。 ## 第一步:识别用户 \{#step-1-identify-users\} Adapty 使用 `customer_user_id` 来跨平台识别用户。 你需要创建一个 ID,并同时传递给移动端 SDK 和 Web 后端。 ### 从网页注册 \{#sign-up-from-web\} 当您的用户在网站上注册时,您需要使用服务端 API 在 Adapty 中为他们创建用户画像。 请参阅[此处](api-adapty/operations/createProfile)的方法参考文档。 ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' ``` ### 从应用内注册 \{#sign-up-from-app\} 当用户首次从应用内注册时,你可以在 SDK 激活时传入其 customer user ID;如果在注册阶段之前已经激活了 Adapty SDK,则可以使用 `identify` 方法创建新的用户画像并为其分配 customer user ID。 :::important 如果在 SDK 激活之后才识别新用户,SDK 会先创建一个匿名用户画像,因为它必须依赖某个用户画像才能正常运行。随后,当你识别该用户并为其分配新的 customer user ID 时,系统将创建一个新的用户画像。 这是完全正常的行为,不会影响数据分析的准确性。详情请参阅[此处](ios-quickstart-identify)。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## 步骤 2:通过 API 检查订阅状态 \{#step-2-check-subscription-status-via-api\} 当用户在您的网站上登录时,使用 API 获取其 Adapty 用户画像。 如果用户没有活跃的订阅,您可以展示付费墙。 请参阅[此处](api-adapty/operations/getProfile)的方法参考文档。 ```bash curl --request GET \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ ``` ## 步骤 3:在您的网站上展示付费墙 \{#step-3-display-a-paywall-on-your-website\} 在您的网站上,为免费增值用户展示付费墙。 您可以使用任何支付服务商(Stripe、Chargebee、LemonSqueezy 等)。 ## 第四步:在 Adapty 中更新订阅状态 \{#step-4-update-subscription-status-in-adapty\} 用户在您的网站完成支付后,调用 Adapty API 根据所购产品更新该用户的访问等级。 方法参考文档请见[这里](api-adapty/operations/grantAccessLevel)。 ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## 第五步:在应用中同步状态 \{#step-5-sync-status-in-the-app\} 当用户打开你的移动应用时,拉取最新的用户画像并解锁付费功能。 你需要获取用户画像,或让其自动同步。然后从中读取访问等级。 下面展示了如何获取用户画像并检查其状态。更多详情请参阅[这里](ios-check-subscription-status)。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // 检查访问权限 if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // 授予高级功能访问权限 } } catch (error) { // 处理错误 } ``` </TabItem> </Tabs> --- # File: sync-purchases-from-custom-stores --- --- title: "从自定义商店同步交易" description: "将自定义商店的交易同步到 Adapty,以提供访问权限并跟踪收入。" --- 如果您通过 **自定义商店**(例如 Amazon Appstore、Microsoft Store 或您自己的支付平台)销售订阅或应用内购买,您可以将这些交易与 Adapty 同步,以自动管理访问等级并在分析中跟踪收入。 在本指南中,您将了解如何使用 SDK 和 API 将自定义商店的购买与 Adapty 连接起来。 #### 示例用例 \{#sample-use-case\} 假设您在 Amazon Appstore 上分发应用,或者您已经建立了自己的网络商店用于直接购买。当用户通过这些平台完成购买时,您希望: - 自动授予他们在移动应用中访问高级功能的权限 - 在 Adapty 分析中与 App Store 和 Google Play 收入一起跟踪该交易 - 像其他任何订阅一样触发集成和 Webhook 这正是本集成所要实现的目标。 ## 步骤 1. 识别用户 \{#step-1-identify-users\} Adapty 使用 `customer_user_id` 跨平台识别用户。 您需要创建此 ID 一次,并将其传递给移动 SDK 和 Web 后端。当用户首次从应用注册时,您可以在 SDK 激活期间传递其 customer user ID;如果您在注册阶段之前已激活 Adapty SDK,则使用 `identify` 方法创建新的用户画像并为其分配 customer user ID。 :::important 如果您在 SDK 激活后识别新用户,SDK 将首先创建一个匿名用户画像(它无法在没有用户画像的情况下工作)。当您使用 customer user ID 调用 `identify` 时,将创建一个新的用户画像。 此行为是正常的,不会影响分析准确性。了解更多信息,请点击[此处](ios-quickstart-identify)。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## 步骤 2. 在 Adapty 看板中创建自定义商店的产品 \{#step-2-create-products-in-a-custom-store-in-adapty-dashboard\} 为了让 Adapty 将自定义商店的交易与您的产品匹配,您需要添加产品并为其设置自定义商店详情。 1. 在 Adapty 看板左侧菜单中进入 [**Products**](https://app.adapty.io/settings/general),然后点击 **Create product**。或者,点击现有产品进行编辑。 2. 确保您已选择希望授予购买该产品的用户的[访问等级](access-level)。 3. 点击 **+** 并选择 **Add a custom store**。 4. 点击 **Create new custom store**。 5. 为您的商店命名(例如:"Amazon Appstore"、"Microsoft Store" 或 "Web Store")并设置 ID。点击 **Create custom store**。 6. 然后,点击 **Save changes** 将产品链接到自定义商店。 7. 输入产品的 **Store product ID**,以便将其映射到该商店中的某个产品。然后,点击 **Save**。 ## 步骤 3. 通过 API 同步交易 \{#step-3-sync-transactions-via-api\} 当自定义商店中完成购买时,您需要使用服务端 API 将其同步到 Adapty。 此 API 调用将: - 在 Adapty 中记录交易 - 向用户授予相应的访问等级 - 触发您已配置的任何集成和 Webhook - 使交易出现在您的分析中 完整方法参考请参见[此处](api-adapty/operations/setTransaction)。 ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/set/transaction/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' \ --data '{ "purchase_type": "PRODUCT_PERIOD", "store": "YOUR_CUSTOM_STORE", "environment": "production", "store_product_id": "YOUR_STORE_PRODUCT_ID", "store_transaction_id": "STORE_TRANSACTION_ID", "store_original_transaction_id": "ORIGINAL_TRANSACTION_ID", "price": { "country": "COUNTRY_CODE", "currency": "CURRENCY_CODE", "value": "YOUR_PRICE" }, "purchased_at": "2024-01-15T10:30:00Z" }' ``` :::important 重要参数: - **store**:步骤 2 中自定义商店的 ID - **store_product_id**:步骤 2 中的商店产品 ID - **store_transaction_id**:此交易的唯一标识符 - **purchased_at**:购买发生时的 ISO 8601 时间戳 - **price**:用户实际支付的金额 ::: ## 步骤 4. 在应用中验证访问权限 \{#step-4-verify-access-in-the-app\} 交易同步后,用户的用户画像将自动更新为新的访问等级。 当用户打开您的移动应用时,获取其用户画像以检查其订阅状态并解锁高级功能。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: grant-access-level --- --- title: "手动授予访问等级" description: "为特定用户或用户组手动解锁付费功能" --- 如果您需要为特定用户或用户组**手动解锁高级功能**,可以通过 Adapty API 实现。这适用于促销活动、投资人访问或特殊客户支持场景。 本指南将介绍如何识别用户并以编程方式为其授予访问等级。 #### 示例使用场景 \{#sample-use-cases\} - **优惠码**:当用户在应用中输入有效的优惠码时,自动为其授予高级功能访问权限。 - **投资人/测试用户访问**:通过检查自定义属性,为投资人或内测用户提供高级访问权限。 :::note **Google Play 促销代码**:通过兑换 Google Play 促销代码完成的购买,可能不包含 `orderId`。Adapty 的一次性(非订阅)购买验证需要 `orderId`,因此这类兑换不会被自动验证或授予权限。请按照以下步骤手动授予访问权限——服务端 API 不依赖 `orderId`。 ::: ## 步骤 1:识别用户 \{#step-1-identify-users\} Adapty 使用 `customer_user_id` 在各平台和设备之间识别用户。这对于确保用户在重新安装应用或切换设备后仍能保留其访问权限至关重要。 您只需创建一次此 ID。当用户首次从应用注册时,可以在 SDK 激活期间传入其 customer user ID,或者如果 SDK 在注册之前已激活,则使用 `identify` 方法。 :::important 如果您在 SDK 激活后识别新用户,SDK 将首先创建一个匿名用户画像(它必须有一个用户画像才能工作)。当您使用 customer user ID 调用 `identify` 时,将创建一个新的用户画像。 此行为是正常的,不会影响分析准确性。了解更多信息请点击[此处](ios-quickstart-identify)。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## 步骤 2:通过 API 授予访问等级 \{#step-2-grant-access-level-via-api\} 一旦用户通过 `customer_user_id` 识别后,您可以使用服务端 API 为其授予访问等级。此 API 调用将向用户授予访问等级,使其无需实际付费即可访问付费功能。 完整的方法参考请点击[此处](api-adapty/operations/grantAccessLevel)。 :::tip 您可以通过在 Adapty 看板中添加自定义属性(例如 Beta tester 或 Investor)来控制用户访问权限。 当应用启动时,[检查用户画像中的此属性](subscription-status)以自动授予访问权限。 如需更新访问权限,只需在看板中修改该属性即可。 ::: ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: CUSTOMER_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## 步骤 3:在应用中验证访问权限 \{#step-3-verify-access-in-the-app\} 通过 API 授予访问权限后,用户的用户画像将自动更新。获取其用户画像以检查订阅状态并解锁高级功能。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL_ID"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: web-api --- --- title: Adapty Web API description: "" --- Web API 是服务端 API 的扩展,专为 Web 应用设计。它允许你通过关联的版位 ID 获取正确的付费墙,并记录付费墙的展示以实现准确的转化追踪。这有助于你使用 Adapty 内置的 A/B 测试和付费墙个性化功能,以及追踪哪些付费墙的效果最佳。 ## 使用场景:记录来自 Web 应用的交易并将其关联到所使用的付费墙 \{#use-case-record-a-transaction-from-your-web-app-and-link-it-to-the-used-paywall\} 假设你在 Web 应用中销售产品。你需要向用户展示付费墙,让他们购买产品,然后将交易详情添加到 Adapty。将这些交易与用户完成购买时所使用的特定付费墙关联起来至关重要,这样你的分析数据才能反映准确的信息。使用 Adapty API 可以轻松实现这一点。 ### 前提条件 \{#prerequisites\} 1. 在 Adapty 看板中[创建产品](create-product),这些产品将用于付费墙。 2. 在 Adapty 看板中[创建付费墙](create-paywall)。[使用远程配置](customize-paywall-with-remote-config)来设计你的 Web 付费墙。 3. 在 Adapty 看板中[设置版位](create-placement),并将付费墙关联到该版位。 ### 使用 Adapty API 的步骤 \{#steps-with-adapty-api\} 1. **创建用户画像:** Adapty 在请求付费墙之前需要有一个用户画像,以便根据请求该付费墙的用户对结果进行个性化处理。使用[创建用户画像](api-adapty/operations/createProfile)请求来创建用户画像。 2. **获取并展示付费墙:** 当用户到达 Web 应用中需要展示付费墙的版位时,使用[获取付费墙](api-web/operations/getPaywall)请求通过[版位 ID](placements) 获取付费墙。返回结果将是与你的用户对应的[目标受众](audience)的付费墙。使用返回的产品以及(可选)该付费墙的[远程配置](customize-paywall-with-remote-config),通过你的代码展示付费墙。 3. **记录付费墙展示:** 使用[记录付费墙展示](api-web/operations/recordPaywallView)将付费墙展示事件记录到 Adapty,以确保你的分析数据准确反映该事件。这对于正确追踪转化至关重要。 4. **记录购买:** 如果用户完成购买,使用 Adapty API 将交易详情发送给 Adapty。在此请求中包含 **variation ID**,以便将交易与所展示的特定付费墙关联起来。有关指导,请参阅我们关于[在移动应用中将付费墙与交易关联](report-transactions-observer-mode)的页面——同样的方法适用于 Web 应用。 5. **添加营销归因数据(如适用):** 如果你有任何营销归因数据(例如推广活动或广告详情),使用[添加归因](api-web/operations/addAttribution)将其合并到用户画像中,以丰富分析数据并在 Adapty 中深入了解你的广告效果。 --- **后续步骤:** - 继续进行 [Web API 授权](web-api-authorization) - 请求接口: - [添加归因](api-web/operations/addAttribution) - [获取付费墙](api-web/operations/getPaywall) - [记录付费墙展示](api-web/operations/recordPaywallView) --- # File: web-api-authorization --- --- title: Web API 的授权与请求格式 description: "" --- ## 授权 \{#authorization\} API 请求必须通过你的公开 API 密钥进行身份验证,将其作为 **Authorization** 请求头,值的格式为 `Api-Key {your_public_api_key}`,例如 `Api-Key public_live_...`。可在 [Adapty 看板 -> **App Settings** -> **General** 标签页 -> **API keys** 部分](https://app.adapty.io/settings/general) 找到该密钥。 :::important API 密钥与应用绑定。如果你有多个应用,请确保为每个应用使用不同的密钥。 ::: ## 请求格式 \{#request-format\} - **Content-Type 请求头**: 将 **Content-Type** 请求头设置为 `application/json`,API 才能正常处理你的请求。 - **请求体**: API 期望请求体使用 JSON 格式。 --- # File: web-api-requests --- --- title: " Web API 请求" description: "" --- Adapty 的服务端 API 使您能够以编程方式访问和管理订阅数据,从而实现与现有服务和基础设施的无缝集成。无论是跨平台同步数据、授予访问等级,还是在 Stripe 中验证购买,此 API 都提供了保持系统同步并提升用户参与度所需的工具。 ## Postman 集合与环境 \{#postman-collection-and-environment\} 为了简化 Web API 的使用,我们准备了一个 Postman 集合和环境文件,您可以下载并导入到 Postman 中。 - **请求集合**:包含 Adapty Web API 中所有可用的请求。请注意,它使用的变量可以在环境中定义。 - **环境**:包含一组变量,您可以一次性定义值。我们为服务端 API、Web API 和分析导出 API 准备了统一的环境,以便于您使用。将此环境设为活动状态后,Postman 将自动在您的请求中替换已定义的变量值。 :::tip [下载集合与环境](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_Web_API_postman_collection.zip) ::: 有关如何将集合和环境导入 Postman 的信息,请参阅 [Postman 文档](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/)。 ## 使用的变量 \{#variables-used\} 我们创建了一个统一的环境,涵盖服务端 API、Web API 和分析导出 API,以简化您的工作流程。以下是 Web API 专用的变量: | 变量 | 描述 | 示例值 | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- | | public_api_key | 您可以在 [**App settings**](https://app.adapty.io/settings/general) 的 **Public SDK key** 字段中找到它。 | `public_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | 您系统中使用的用户 ID。在 Adapty 看板中,您可以在用户画像的 **Customer user ID** 字段中找到它。 | `john.doe@example.com` | | adapty-profile-id | Adapty 中分配的用户 ID。在 Adapty 看板中,您可以在用户画像的 **Adapty ID** 字段中找到它。 | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | **下一步:请求:** - [获取付费墙](api-web/operations/getPaywall) - [记录付费墙浏览](api-web/operations/recordPaywallView) - [添加归因](api-web/operations/addAttribution) --- # File: export-analytics-api --- --- title: 通过 API 导出分析数据 --- 将分析数据导出为 CSV,让你能够更深入地探索应用的性能指标、自定义报表,并分析长期趋势。借助 Adapty API,你可以轻松将详细分析数据提取为 CSV 格式,方便随时追踪、分享和优化数据洞察。 :::tip 正在使用 AI 智能体或大语言模型提取分析数据?请参阅[使用 AI 智能体导出分析数据](export-analytics-with-ai)。 ::: ## 开始使用数据分析导出 API \{#getting-started-with-the-api-for-analytics-export\} 通过数据分析导出 API,你可以实现以下目标: 1. **分析营销活动的 MRR**:衡量去年特定国家/地区营销活动的效果,了解哪些活动带来了最高收益,并按周进行跟踪。可使用 [获取分析数据](api-export-analytics/operations/retrieveAnalyticsData) 方法来实现。 2. **按同期群跟踪用户留存趋势**:通过同期群跟踪留存情况,找出用户流失节点,并对比不同时期的同期群,从而发现规律,找到可以通过互动策略提升留存的关键时机。支持按特定应用商店、特定国家和特定产品进行筛选。使用 [获取同期群数据](api-export-analytics/operations/retrieveCohortData) 方法来实现。 3. **评估各渠道转化率**:分析关键获客渠道的转化率,了解哪些渠道在推动首次购买方面最有效。这有助于将营销预算优先投入到高效渠道中。为此,请使用 [获取转化数据](api-export-analytics/operations/retrieveConversionData) 方法。 4. **查看流失率**:监控用户取消订阅的速度,以发现流失规律或评估留存效果,重点关注特定国家和特定产品。使用 [获取漏斗数据](api-export-analytics/operations/retrieveFunnelData) 方法来实现此目的。 5. **按用户细分评估 LTV**:识别不同用户细分的生命周期价值,了解哪些群体随时间推移带来最高收入。重点关注高价值细分(如长期订阅用户),并利用结果优化获客策略。使用 [获取 LTV 数据](api-export-analytics/operations/retrieveLTVData) 方法来实现这一目标。 6. **按国家查看留存率**:按地区查看留存率,找出高参与度市场,为本地化或区域策略提供参考。使用 [获取留存数据](api-export-analytics/operations/retrieveRetentionData) 方法。 --- **下一步**: - [授权与请求格式](export-analytics-api-authorization) - [导出分析 API 请求](export-analytics-api-requests) --- # File: export-analytics-api-authorization --- --- title: 导出分析 API 的授权与请求格式 --- ## 授权 \{#authorization\} 你需要使用私密 API 密钥作为 Authorization 请求头来验证 API 请求。你可以在 [App Settings](https://app.adapty.io/settings/general) 中找到它。格式为 `Api-Key {YOUR_SECRET_API_KEY}`,例如:`Api-Key secret_live_...`。 :::important API 密钥是按应用区分的。如果你有多个应用,请确保为每个应用使用不同的密钥。 ::: ## 请求格式 \{#request-format\} **Headers** 服务端 API 请求需要特定的请求头和 JSON 请求体。请参考以下说明构建请求: | 请求头 | 描述 | | ------------ | ------------------------------------------------------------ | | Content-Type | (必填)设置为 `application/json`,API 才能正常处理请求。 | | Adapty-Tz | (可选)设置时区,以定义数据的分组和显示方式。请使用 [IANA 时区数据库格式](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)(例如 `Europe/Berlin`)。 | ## 请求体 \{#body\} API 需要一个 JSON 格式的请求体,其中包含请求所需的数据。 ## 频率限制 \{#rate-limits\} 每个 API 密钥每秒最多发送 2 个请求。超出此限制将返回 `429 Too Many Requests` 错误。 ## 轮换 API 密钥 \{#rotate-api-keys\} 如需轮换私密 API 密钥: 1. 在 **Settings → General** 中,点击 **Generate new key**,然后点击旧密钥旁边的垃圾桶图标。 2. 更新应用中使用的密钥。 --- **下一步:请求:** - [获取分析数据](api-export-analytics/operations/retrieveAnalyticsData) - [获取同期群数据](api-export-analytics/operations/retrieveCohortData) - [获取转化数据](api-export-analytics/operations/retrieveConversionData) - [获取漏斗数据](api-export-analytics/operations/retrieveFunnelData) - [获取用户生命周期价值 (LTV) 数据](api-export-analytics/operations/retrieveLTVData) - [获取留存数据](api-export-analytics/operations/retrieveRetentionData) --- # File: export-analytics-api-requests --- --- title: 导出分析 API 请求 --- 将您的分析数据导出为 CSV 格式,可以让您更深入地研究应用的性能数据图表、自定义报告,并分析随时间变化的趋势。通过 Adapty API,您可以轻松地将详细分析数据提取为 CSV 格式,方便您根据需要跟踪、共享和优化数据洞察。 ## Postman 集合与环境 \{#postman-collection-and-environment\} 为了简化使用我们的 API 导出分析数据的操作,我们准备了一个 Postman 集合和环境文件,您可以下载并导入到 Postman 中。 - **请求集合**:包含 Adapty 分析导出 API 中的所有可用请求。请注意,它使用的变量可以在环境中定义。 - **环境**:包含一个变量列表,您可以在其中一次性定义值。我们为服务端 API、Web API 和分析导出 API 准备了统一的环境,以方便您的使用。将此环境设为激活状态后,Postman 将自动将已定义的变量值替换到您的请求中。 :::tip [下载集合与环境](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_export_analytics_API_postman_collection.zip) ::: 有关如何将集合和环境导入 Postman 的信息,请参阅 [Postman 文档](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/)。 ### 使用的变量 \{#variables-used\} 我们为服务端 API、Web API 和分析导出 API 创建了统一的环境,以简化您的工作流程。以下是分析导出 API 特有的变量: | 变量 | 描述 | 示例值 | | ----------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | | secret_api_key | 您可以在 [**App settings**](https://app.adapty.io/settings/general) 的 **Secret key** 字段中找到它。 | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | **请求:** - [获取分析数据](api-export-analytics/operations/retrieveAnalyticsData) - [获取同期群数据](api-export-analytics/operations/retrieveCohortData) - [获取转化数据](api-export-analytics/operations/retrieveConversionData) - [获取漏斗数据](api-export-analytics/operations/retrieveFunnelData) - [获取用户生命周期价值 (LTV) 数据](api-export-analytics/operations/retrieveLTVData) - [获取留存数据](api-export-analytics/operations/retrieveRetentionData) --- # End of Documentation _Generated on: 2026-07-24T13:01:53.301Z_ _Successfully processed: 17/18 files_ # CAPACITOR - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.302Z Total files: 45 --- # File: capacitor-sdk-overview --- --- title: "Capacitor SDK overview" description: "了解 Adapty Capacitor SDK 及其主要功能。" --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) 欢迎!我们致力于让应用内购买变得轻松愉快 🚀 我们构建了 [Adapty Capacitor SDK](https://github.com/adaptyteam/AdaptySDK-Capacitor/),帮助你从应用内购买的繁琐工作中解放出来,让你专注于最擅长的事——打造出色的应用。以下是我们为你处理的内容: - 开箱即用地处理购买、收据验证和订阅管理 - 无需更新应用即可创建和测试付费墙 - 零配置即可获取详细的购买分析数据——包含同期群、LTV、流失率和漏斗分析 - 跨会话和跨设备始终保持用户订阅状态最新 - 只需一行代码即可将应用与营销归因和分析服务集成 :::note 在深入代码之前,你需要先将 Adapty 与 App Store Connect 和 Google Play Console 集成,然后在看板中配置产品。请查看我们的[快速入门指南](quickstart),先完成所有配置。 ::: ## 快速开始 \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. 以下是集成指南中涵盖的内容: 1. [安装并配置 SDK](sdk-installation-capacitor):将 SDK 作为[依赖项](https://www.npmjs.com/package/@adapty/capacitor)添加到你的项目中,并在代码中激活它。 2. [通过付费墙启用购买](capacitor-quickstart-paywalls):设置购买流程,让用户可以购买产品。 3. [检查订阅状态](capacitor-check-subscription-status):自动检查用户的订阅状态并控制其对付费内容的访问权限。 4. [识别用户(可选)](capacitor-quickstart-identify):将用户与其 Adapty 用户画像关联,确保其数据在跨设备间一致存储。 ### 实际效果 \{#see-it-in-action\} 想看看这一切是如何协同运作的?我们为你准备好了: **示例应用**:查看我们展示完整配置的示例: - [React](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Vue.js](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Angular](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [高级开发工具](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) ## 核心概念 \{#main-concepts\} 在深入代码之前,先来了解 Adapty 运作的关键概念。 Adapty 方法的优势在于,只有版位是在应用中硬编码的。其他一切——产品、付费墙设计、定价和优惠——都可以在 Adapty 看板中灵活管理,无需更新应用: 1. **产品** - 应用中可供购买的任何内容——订阅、消耗型商品或永久授权。 2. **流程或付费墙** - 与配置捆绑并附加到版位的产品。有两种形式: - **[流程](adapty-flow-builder)** - 在 Flow Builder 中构建的可视化无代码界面。Adapty 为你渲染 UI 并处理购买。 - **[付费墙](paywalls)** - 无可视化配置;你需要在自己的代码中构建 UI,并自行调用 `makePurchase`。请参阅[手动实现付费墙](capacitor-quickstart-manual)。 在 SDK 代码中,两者均通过同一个 `getFlow` 方法获取。 3. **版位** - 用户旅程中你希望展示付费墙的关键节点。可以将版位理解为货币化策略的"在哪里"和"何时"。常见版位包括: - `main` - 你的主要付费墙位置 - `onboarding` - 在用户引导流程中展示 - `settings` - 可从应用设置中访问 首次集成时从 `main` 或 `onboarding` 等基础版位开始,然后思考应用中哪些地方的用户可能准备好购买。 4. **用户画像** - 当用户购买产品时,其用户画像会被分配一个**访问等级**,你可以用它来定义对付费功能的访问权限。 --- # File: sdk-installation-capacitor --- --- title: "Capacitor - Adapty SDK 安装与配置" description: "在 Capacitor 上安装 Adapty SDK 的分步指南,适用于订阅类应用。" --- Adapty SDK 包含两个核心模块,用于无缝集成到你的 Capacitor 应用中: - **Core Adapty**:此模块是 Adapty 在你的应用中正常运行所必需的。 - **AdaptyUI**:如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码即可轻松创建跨平台付费墙的可视化工具),则需要此模块。AdaptyUI 会与核心模块一同自动激活。 :::tip 想看看 Adapty SDK 如何集成到真实移动应用中?欢迎参考我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples),其中展示了完整的配置流程,包括显示付费墙、发起购买以及其他基础功能。 ::: ## 环境要求 \{#requirements\} [Adapty Capacitor SDK](https://github.com/adaptyteam/AdaptySDK-Capacitor/) 的版本要求如下: | Adapty SDK 版本 | Capacitor 版本 | iOS 版本 | |----------------|---------------|---------| | 3.16.0+ | 8 | 15.0+ | | 3.15 | 7 | 14.0+ | Capacitor 6 及以下版本不受支持。 使用 Adapty SDK v4 (beta) 构建 iOS 应用需要 **Xcode 26** 或更高版本——其底层 iOS 原生 SDK 基于 Swift tools 6.2 构建。iOS 15.0+、Capacitor 8 以及 Android minSdk 24 的要求与 SDK 3.16+ 保持一致。 :::info 从 SDK v3.17 起,Adapty SDK 默认使用 Google Play Billing Library v8.0.0。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} :::important 以下步骤安装的是 Adapty SDK 3.x。SDK v4(测试版)——[Flow Builder](adapty-flow-builder) 所需,也用于[快速入门](capacitor-quickstart-paywalls)——安装方式不同:请参阅下方的 [Adapty SDK 4.0(测试版)](#adapty-sdk-40-beta),或查看[迁移指南](migration-to-capacitor-sdk-v4)。 ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) 安装 Adapty SDK: ```sh npm install @adapty/capacitor npx cap sync ``` ### Adapty SDK 4.0 (beta) Capacitor SDK 4.0 — 新增 [Flow Builder](adapty-flow-builder) 支持 — 目前为预发布版本。请安装指定版本(npm 不会通过 caret/tilde 范围解析预发布版本),然后同步: ```sh npm install @adapty/capacitor@4.0.0-beta.2 ``` ```sh npx cap sync ``` 在 iOS 上,v4 仅通过 **Swift Package Manager** 拉取原生 Adapty SDK——CocoaPods podspec 已被移除([CocoaPods 的 spec 仓库将于 2026 年 12 月起只读](https://blog.cocoapods.org/CocoaPods-Specs-Repo/))。您的 iOS 项目必须使用 Capacitor 的 SPM 集成: - 对于新应用,使用 SPM 包管理器添加 iOS 平台: ```sh npx cap add ios --packagemanager SPM ``` - 对于已有应用,请按照 [Capacitor 在现有项目中使用 SPM 的指南](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project),将 iOS 项目从 CocoaPods 迁移到 SPM。 有关 v4 中完整的 API 变更列表,请参阅[将 Adapty Capacitor SDK 迁移至 v4](migration-to-capacitor-sdk-v4)。 ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} :::note Adapty SDK 在您的应用中只需激活一次。 ::: 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 将以下代码复制到任意应用文件中以激活 Adapty: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // verbose logging is recommended for the development purposes and for the first production release logLevel: 'verbose', // in the development environment, use this variable to avoid multiple activation errors. Set it to your development environment variable __ignoreActivationOnFastRefresh: true, } }); console.log('Adapty activated successfully!'); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `activate` 执行完毕。完整调用顺序请参阅 [Capacitor SDK 调用顺序](capacitor-sdk-call-order)。 ::: :::tip 若要避免开发环境中的激活错误,请参考[使用建议](#development-environment-tips)。 ::: 现在在你的应用中配置付费墙: - 如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请参阅[付费墙编辑工具快速入门](capacitor-quickstart-paywalls)。 - 如果你自行构建付费墙 UI,请参阅[自定义付费墙快速入门](capacitor-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果您计划使用[付费墙编辑工具](adapty-paywall-builder),则需要 AdaptyUI 模块。激活核心模块时会自动完成此操作,您无需进行任何额外操作。 ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解运行情况。可用的日志级别如下: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | 仅记录错误日志 | | `warn` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息 | | `info` | 记录错误、警告及各类信息消息 | | `verbose` | 记录调试时可能有用的所有附加信息,例如函数调用、API 请求等 | 您可以在配置 Adapty 之前或配置过程中设置日志级别: ```typescript showLineNumbers // Set log level before activation adapty.setLogLevel({ logLevel: 'verbose' }); // Or set it during configuration await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { logLevel: 'verbose', } }); ``` ### 数据策略 \{#data-policies\} Adapty 不会存储用户的个人数据,除非您明确发送,但您可以实施额外的数据安全策略,以符合应用商店或特定国家/地区的规定。 #### 禁用 IP 地址收集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 即可禁用用户 IP 地址的收集与共享。默认值为 `false`。 使用此参数可增强用户隐私保护、遵守区域性数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据收集。 ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ipAddressCollectionDisabled: true, } }); ``` #### 禁用广告 ID 的收集与共享 \{#disable-advertising-id-collection-and-sharing\} 激活 Adapty 模块时,将 `ios.idfaCollectionDisabled`(iOS)或 `android.adIdCollectionDisabled`(Android)设置为 `true` 可禁用广告标识符的收集,默认值为 `false`。 如需遵守 App Store/Play Store 政策、避免触发应用跟踪透明度(App Tracking Transparency)提示,或者您的应用不需要基于广告 ID 的广告归因或数据分析,请使用此参数。 ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, } }); ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} 默认情况下,AdaptyUI 会缓存媒体内容(如图片和视频),以提升性能并减少网络流量。你可以通过自定义配置来调整缓存设置。 使用 `mediaCache` 覆盖默认缓存配置: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, } }); ``` | 参数 | 是否必填 | 描述 | |-----------|----------|-------------| | memoryStorageTotalCostLimit | 可选 | 内存缓存的总大小,单位为字节。默认值因平台而异。 | | memoryStorageCountLimit | 可选 | 内存存储的最大条目数量。默认值因平台而异。 | | diskStorageSizeLimit | 可选 | 磁盘上的文件大小限制,单位为字节。默认值因平台而异。 | ### 启用本地访问等级(Android) \{#enable-local-access-levels-android\} 默认情况下,[本地访问等级](local-access-levels) 在 iOS 上已启用,在 Android 上已禁用。要在 Android 上也启用此功能,请将 `localAccessLevelAllowed` 设置为 `true`: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { localAccessLevelAllowed: true, }, } }); ``` ### 备份恢复时清除数据 \{#clear-data-on-backup\} 当 `clearDataOnBackup` 设置为 `true` 时,SDK 会检测到应用从 iCloud 备份中恢复,并删除所有本地存储的 SDK 数据,包括缓存的用户画像信息、产品详情和付费墙。SDK 随后将以全新状态重新初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录以及 Adapty 服务器上的用户数据不受影响。 ::: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { clearDataOnBackup: true, }, } }); ``` ## 开发环境使用技巧 \{#development-environment-tips\} #### 排查 Capacitor 热重载时的 SDK 激活报错 \{#troubleshoot-sdk-activation-errors-on-capacitors-live-reload\} 在 Capacitor 中使用 Adapty SDK 开发时,你可能会遇到以下报错:`Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` 这是因为 Capacitor 的热重载功能在开发过程中会多次触发激活调用。要避免此问题,请将 `__ignoreActivationOnFastRefresh` 选项设置为 Capacitor 的开发模式标志——具体取值取决于你所使用的打包工具。 ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Set your development environment variable __ignoreActivationOnFastRefresh: true, } }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## 故障排查 \{#troubleshooting\} #### iOS 最低版本错误 \{#minimum-ios-version-error\} :::note 此问题适用于使用 CocoaPods 的项目(**SDK 3.x**)。SDK 4.0 仅通过 Swift Package Manager 安装(无 `Podfile`),且要求 iOS 15.0 — 请在 Xcode 中将部署目标设置为 15.0。 ::: 如果在 SDK 3.x 上遇到 iOS 最低版本错误,请更新你的 Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Android 备份规则(自动备份配置) \{#android-backup-rules-auto-backup-configuration\} 部分 SDK(包括 Adapty)会自带 Android 自动备份配置。如果多个 SDK 都定义了备份规则,Android 清单合并工具可能会报错,提示 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup` 相关问题。 常见错误示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note 以下更改应在你的 Android 平台目录(通常位于项目的 `android/` 文件夹)中进行。 ::: 要解决此问题,你需要: - 告知清单合并工具使用应用自身的备份相关属性值。 - 创建备份规则文件,将 Adapty 的规则与其他 SDK 的规则合并。 #### 1. 在清单中添加 `tools` 命名空间 \{#1-add-the-tools-namespace-to-your-manifest\} 在 `AndroidManifest.xml` 文件中,确保根标签 `<manifest>` 包含 tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. 在 `<application>` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在同一个 `AndroidManifest.xml` 文件中,更新 `<application>` 标签,使应用提供最终属性值,并告知清单合并工具替换库中的值: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 Android 项目的 `res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并。Android 根据系统版本使用不同的备份规则格式,同时创建两个文件可确保应用所支持的所有 Android 版本都能正常兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请替换或添加你应用中实际使用的其他 SDK 的规则。 ::: **Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Android 11 及更低版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::tip 修改原生 Android 文件后,请运行 `npx cap sync android`,这样在重新生成平台时 Capacitor 可以获取到更新后的资源。 ::: #### 从其他应用返回后 Android 购买失败 \{#purchases-fail-after-returning-from-another-app-in-android\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会错误地重建或复用该 Activity。这会导致购买结果丢失或被视为已取消。 为确保购买流程正常运行,请仅对启动购买流程的 Activity 使用 `standard` 或 `singleTop` 启动模式,避免使用其他模式。 在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### 由 Podfile 中 SWIFT_VERSION 覆盖引发的 Swift 6 构建错误 \{#swift-6-build-errors-caused-by-podfile-swift-version-override\} :::note 此内容适用于基于 CocoaPods 的 **SDK 3.x** 项目。SDK 4.0 通过 Swift Package Manager 安装原生 SDK,因此无需调整 `Podfile`。 ::: 在为 iOS 构建 Capacitor 应用时,你可能会在 Adapty pod 目标上看到 Swift 6 编译错误。常见症状包括:`AdaptyUIBuilderLogic` 中的 `@Sendable` 不匹配、Adapty 类型缺少 `Sendable` 协议遵循,或 actor 隔离错误。 Adapty pods 声明了 `s.swift_version = '6.0'`,构建时需要 Swift 6。你自己的应用代码可以继续使用 Swift 5 —— 只有 Adapty 相关的 pod 目标(`Adapty`、`AdaptyUI`、`AdaptyUIBuilder`、`AdaptyLogger`、`AdaptyPlugin`)需要用 Swift 6 构建。 最常见的原因是 `ios/App/Podfile` 中存在 `post_install` 钩子,它会为所有 pod 目标重写 `SWIFT_VERSION`: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **修复方法**:将 Adapty pod 目标从覆盖范围中排除: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` 然后运行 `npx cap sync ios` 并重新构建。 如需验证,请打开 `ios/App/Pods/Pods.xcodeproj`,选择 `Adapty` pod target → **Build Settings** → **Swift Language Version**,确认显示的是 **Swift 6**。 --- # File: capacitor-quickstart-paywalls --- --- title: "在 Capacitor SDK 中使用付费墙编辑工具启用购买功能" description: "使用 Adapty 付费墙编辑工具启用应用内购买的快速入门指南。" --- 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 在无代码的 Flow Builder 中构建的屏幕序列,用于向用户展示产品,SDK 通过 `getFlow` 获取它们。如果你更倾向于用自己的代码构建 UI,请使用付费墙代替 — 参见[手动实现付费墙](capacitor-quickstart-manual)。 - [**版位**](placements) – 在应用中展示流程的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 请求它们。这样可以轻松运行 A/B 测试,并向不同用户展示不同的流程。 Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |---|---|---| | Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可直接购买的流程](quickstart-paywalls)。Adapty 自动渲染并在幕后处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 您在应用代码中自行实现付费墙 UI,但仍从 Adapty 获取流程对象,以保持产品方案的灵活性。请参阅[指南](capacitor-quickstart-manual)。 | | 观察者模式 | 🔴 复杂 | 您已有自己的购买处理基础设施并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。请参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤介绍如何在应用中实现通过 Adapty Flow Builder 创建的流程。** 如果您希望自行构建付费墙界面,请参阅[手动实现付费墙](capacitor-quickstart-manual)。 ::: 要在应用中展示通过 Adapty Flow Builder 创建的流程,您只需在代码中完成以下几步: 1. **获取流程**:从 Adapty 获取流程数据。 2. **展示流程,Adapty 将自动处理购买逻辑**:在应用中显示对应视图。 3. **处理按钮操作**:将用户的交互行为与应用的响应逻辑关联起来,例如打开链接或在用户点击按钮时关闭流程。 ## 开始之前 \{#before-you-start\} 在开始之前,请先完成以下步骤: 1. 在 Adapty 看板中将您的应用连接到 [App Store](initial_ios) 和/或 [Google Play](initial-android)。 2. 在 Adapty 中[创建您的产品](create-product)。 3. [创建流程并向其添加产品](create-paywall)。 4. [创建版位并将您的流程添加到其中](create-placement)。 5. 在您的应用代码中[安装并激活 Adapty SDK](sdk-installation-capacitor)。本指南使用 Adapty Capacitor SDK v4 API。 ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。通过版位,你可以针对不同的目标受众展示不同的流程,或运行 [A/B 测试](ab-tests)。 要获取在 Adapty 付费墙编辑工具中创建的流程,请通过 `getFlow` 方法,使用[版位](placements) ID 来获取 `flow` 对象。该流程对象包含展示所需的 UI 元素和样式信息。 ```typescript showLineNumbers title="Capacitor" try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow } catch (error) { // handle the error } ``` ## 2. 展示流程 \{#2-display-the-flow\} 准备好流程后,只需添加几行代码即可展示它。 使用 `createFlowView` 方法创建一个 `view`,设置其事件处理器,然后调用 `view.present()`。每个 `view` 只能使用一次。如果需要再次展示流程,请再次调用 `createFlowView` 创建一个新的 `view` 实例。 ```typescript showLineNumbers title="Capacitor" try { const view = await createFlowView(flow); await view.setEventHandlers({ onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle the error } ``` :::tip 有关如何展示流程的更多详情,请参阅我们的[指南](capacitor-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#3-handle-button-actions\} 当用户点击流程中的按钮时,Capacitor SDK 会自动处理购买、恢复、关闭流程以及打开 URL 等操作。 不过,其他按钮拥有自定义或预定义的 ID,需要在代码中处理相应操作。你也可以根据需要覆盖其默认行为。 例如,以下是关闭按钮的默认行为。你无需在代码中添加这段逻辑,但可以参考它了解具体实现方式。 ```typescript showLineNumbers title="Capacitor" const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` :::tip 阅读我们的指南,了解如何处理按钮[操作](capacitor-handle-paywall-actions)和[事件](capacitor-handling-events)。 ::: ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 你的流程已准备好在应用中展示。[测试购买](capacitor-test),确保你能从流程中完成测试购买。 接下来,你需要[检查用户的访问等级](capacitor-check-subscription-status),以确保向合适的用户展示流程或开放付费功能。 ## 完整示例 \{#full-example\} 以下是本指南中所有步骤在应用中的完整集成示例。 ```typescript showLineNumbers title="Capacitor" export async function showFlow() { try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); const view = await createFlowView(flow); await view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } } ``` --- # File: capacitor-check-subscription-status --- --- title: "在 Capacitor SDK 中检查订阅状态" description: "了解如何在 Capacitor 应用中通过 Adapty 检查订阅状态。" --- 要判断用户是否可以访问付费内容或是否需要显示付费墙,您需要检查用户画像中的[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种方式: - 如果需要立即获取最新的用户画像数据(例如应用启动时)或想强制更新,请调用 `getProfile`。 - 设置**自动用户画像更新**,以保留一份本地副本,该副本会在订阅状态发生变化时自动刷新。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方法是使用 `getProfile` 方法访问用户画像: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 在应用中自动接收用户画像更新: 1. 使用 `adapty.addListener('onLatestProfileLoad')` 监听用户画像变更——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。 2. 当该方法被调用时,保存最新的用户画像数据,这样在整个应用中都可以直接使用,无需额外发起网络请求。 ```typescript showLineNumbers class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addListener('onLatestProfileLoad', (data) => { this.currentProfile = data.profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive ?? false; } } ``` :::note 应用启动时,Adapty 会自动调用 `onLatestProfileLoad` 事件监听器,即使设备处于离线状态,也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当你需要立即决定是否显示付费墙或开放付费功能时,可以直接检查用户的用户画像。这种方式适用于以下场景:应用启动、进入高级内容区域,或在展示特定内容之前。 ```typescript showLineNumbers const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const getAccessLevel = (profile: AdaptyProfile) => { return profile.accessLevels?.['YOUR_ACCESS_LEVEL']; }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## 后续步骤 \{#next-steps\} 现在您已了解如何追踪订阅状态,接下来请学习如何[管理用户画像](capacitor-quickstart-identify),以确保用户可以访问其已付费的内容。 --- # File: capacitor-quickstart-identify --- --- title: "在 Capacitor SDK 中识别用户" description: "在 Capacitor 中设置 Adapty 以管理应用内订阅的快速入门指南。" --- 用户购买记录的管理方式取决于你的应用采用的身份验证模型: - 如果你的应用不使用后端身份验证,也不存储用户数据,请参阅[匿名用户章节](#anonymous-users)。 - 如果你的应用已有(或计划引入)后端身份验证,请参阅[已识别用户章节](#identified-users)。 :::tip **核心概念**: - **用户画像**是 SDK 正常运行所必需的实体,由 Adapty 自动创建。它可以是匿名的(不含 customer user ID),也可以是已识别的(含 customer user ID)。 - **Customer user ID** 是可选标识符,由**你**创建,用于将你的用户与其 Adapty 用户画像关联起来。 ::: 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|------------------------------------------------------|-------------------------------------------------------------------------| | **购买管理** | 通过商店层级恢复购买记录 | 通过客户用户 ID 跨设备维护购买历史 | | **用户画像管理** | 每次重新安装时创建新用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久化** | 匿名用户的数据与设备/安装绑定 | 已识别用户的数据可跨设备和会话持久保存 | ## 匿名用户 \{#anonymous-users\} 如果你没有后端身份验证,**无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时激活,Adapty 会**为该用户创建一个新的用户画像**。 2. 当用户在应用内购买任何内容时,该购买记录会**与其 Adapty 用户画像及商店账户关联**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建一个新的空用户画像**。 4. 如果用户之前已在你的应用中完成过购买,默认情况下,其购买记录会在 SDK 激活时自动从 App Store 同步。 :::note 备份恢复与重新安装的行为不同。默认情况下,当用户从备份恢复时,SDK 会保留缓存数据,不会创建新的用户画像。你可以通过 `clearDataOnBackup` 设置来调整此行为。[了解更多](sdk-installation-capacitor#clear-data-on-backup-restore)。 ::: ## 已识别用户 \{#identified-users\} - 如果某个用户画像还没有 customer user ID(即**用户尚未登录**),当你发送 customer user ID 时,它会与该用户画像关联。 - 如果是**重新安装、登录,或从新设备安装**,且你之前已经发送过该用户的 customer user ID,则不会创建新的用户画像,而是切换到与该 customer user ID 关联的已有用户画像。 你有两种方式在应用中识别用户: - [**在登录/注册时:**](#during-loginsignup) 如果用户在应用启动后登录,请在用户完成身份验证时调用 `identify()`,并传入 customer user ID。 - [**在 SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时已有存储的 customer user ID,请在调用 `activate()` 时一并传入。 :::important 默认情况下,当 Adapty 收到一笔来自某个 Customer User ID 的购买时,若该 Customer User ID 当前已关联到另一个 Customer User ID,访问等级将被共享,即两个用户画像均可获得付费访问权限。你可以将此设置更改为将付费访问权限从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[此文章](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 登录/注册时 \{#during-loginsignup\} 如果你在应用启动后才识别用户身份(例如,用户登录或注册之后),请使用 `identify` 方法设置其 customer user ID。 - 如果你**之前从未使用过这个 customer user ID**,Adapty 会自动将其关联到当前用户画像。 - 如果你**之前已经用这个 customer user ID 识别过用户**,Adapty 会切换到与该 customer user ID 关联的用户画像。 :::tip 创建 customer user ID 时,请将其与用户数据一起保存,这样当用户在新设备上登录或重新安装应用时,你可以发送相同的 ID。 ::: 在调用其他 SDK 方法之前,请始终 `await` `identify`。并发调用会产生 `#3006 profileWasChanged` 错误,或者落到匿名用户画像上。详见 [Capacitor SDK 的调用顺序](capacitor-sdk-call-order)。 ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` ### 在 SDK 激活期间 \{#during-the-sdk-activation\} 如果在激活 SDK 时你已经知道用户 ID,可以直接在 `activate` 方法中传入,无需单独调用 `identify`。 如果你知道用户 ID,但在激活之后才设置,那么 SDK 激活时 Adapty 会先创建一个新的空用户画像,等到你调用 `identify` 后才会切换到已有的用户画像。 您可以传入现有的 customer user ID(即您之前使用过的 ID),也可以传入一个新的。如果传入新 ID,激活时创建的新用户画像将自动与该 customer user ID 关联。 :::tip 如需将创建的空用户画像排除在看板分析之外,请前往 **App settings**,配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```typescript showLineNumbers await adapty.activate({ apiKey: "YOUR_PUBLIC_SDK_KEY", params: { customerUserId: "YOUR_USER_ID" } }); ``` ### 退出登录用户 \{#log-users-out\} 如果您有供用户退出登录的按钮,请使用 `logout` 方法。这将为用户创建一个新的匿名用户画像 ID。 ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录状态下购买 \{#allow-purchases-without-login\} 如果你的用户在登录前后均可进行购买,则无需额外配置: 工作原理如下: 1. 当未登录用户完成购买时,Adapty 会将其绑定到该用户的匿名用户画像 ID。 2. 当用户登录账户后,Adapty 会切换到使用其已识别的用户画像。 - 如果是已有的 customer user ID(该 customer user ID 已关联到某个用户画像),Adapty 会自动同步其交易记录。 - 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 会将该 customer user ID 分配给当前用户画像,从而保留完整的购买历史。 --- # File: adapty-sdk-integration-skill-capacitor --- --- title: "通过 SDK 集成技能将 Adapty 集成到 Capacitor 应用中" description: "使用 adapty-sdk-integration 技能,借助 AI 编程工具将 Adapty SDK 端到端集成到 Capacitor 应用中。" --- [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 :::important 该技能目前处于测试阶段。如果遇到卡顿或异常行为,请参考[分步集成指南](adapty-cursor-capacitor)——它会引导你的 AI 工具逐步完成每个阶段的正确文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-capacitor --- --- title: "借助 AI 将 Adapty 集成到 Capacitor 应用" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具将 Adapty 集成到 Capacitor 应用的分步指南。" --- 本指南将逐步带你用 AI 编程工具将 Adapty 集成到 Capacitor 应用中——你只需按正确顺序把合适的 Adapty 文档喂给它即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始之前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要先完成一些看板配置。您可以通过交互式 LLM 技能,或手动通过看板来完成配置。 ### 技能方式(推荐)\{#skill-approach-recommended\} Adapty CLI 技能允许您的 LLM 直接设置应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。您只需在看板中[连接您的应用商店](integrate-payments)即可。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在您的 agent 中运行 `/adapty-cli`。它将引导您完成每个步骤——包括何时打开看板来连接您的应用商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你更倾向于手动配置,以下是编写代码前需要准备的内容。LLM 无法自动从看板中获取这些值,需要你手动提供。 1. **连接应用商店**:在 Adapty 看板中,进入 **App settings → General**,将 App Store 和 Google Play 都连接上(如果你的 Capacitor 应用需要同时支持两个平台)。这是购买功能正常运行的必要前提。 [连接应用商店](integrate-payments) 2. **复制你的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这就是你传给 `adapty.activate()` 的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,前往 **Products** 页面。你不需要在代码中直接引用产品——Adapty 会通过付费墙来分发它们。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配到版位。在代码中,版位 ID 就是传给 `adapty.getFlow()` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板的 **Products** 页面中,按产品进行配置。在代码中,通过 `profile.accessLevels['premium']?.isActive` 检查对应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户会因购买的产品不同而获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编写代码之前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五项之后,就可以开始写代码了。告诉你的 LLM:"我的 Public SDK key 是 X,版位 ID 是 Y",它就能生成正确的初始化和获取流程的代码。 ::: ### 准备就绪后的配置 \{#set-up-when-ready\} 以下内容不是开始编码的必要条件,但随着集成的成熟,您会希望用到它们: - **A/B 测试**:在 **Placements** 页面配置。无需更改代码。 [A/B 测试](ab-tests) - **更多付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。 - **分析集成**:在 **Integrations** 页面配置。不同集成的设置方式各有不同。请参阅[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 向您的 LLM 提供 Adapty 文档 \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐)\{#use-context7-recommended\} [Context7](https://context7.com) 是一个 MCP 服务器,可让你的 LLM 直接访问最新的 Adapty 文档。它会根据你的提问自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 以及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the Capacitor SDK ``` :::warning 尽管 Context7 无需手动粘贴文档链接,实施顺序仍然重要。请按照下方的[实施流程](#implementation-walkthrough)逐步操作,确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 您可以以纯文本 Markdown 格式访问任何 Adapty 文档。在其 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-capacitor.md](https://adapty.io/docs/zh/adapty-cursor-capacitor.md)。 下方[实现演练](#implementation-walkthrough)中的每个阶段都包含一个"发送给您的 LLM"代码块,其中有可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下方的[索引文件和平台特定子集](#plain-text-doc-index-files)。 ## 实现演练 \{#implementation-walkthrough\} 本指南的其余部分按实现顺序介绍 Adapty 集成。每个阶段包含要发送给 LLM 的文档、完成后应看到的效果,以及常见问题。 ### 规划集成方案 \{#plan-your-integration\} 在开始写代码之前,先让你的 LLM 分析项目结构并制定实施计划。如果你使用的 AI 工具支持规划模式(例如 Cursor 或 Claude Code 的 plan 模式),建议先启用该模式,让 LLM 在生成代码前同时读取你的项目结构和 Adapty 文档。 告诉你的 LLM 你使用哪种购买方式——这会影响它需要参考的指南: - [**Adapty Flow Builder**](adapty-flow-builder):在 Adapty 的无代码编辑工具中创建流程,SDK 自动完成渲染。 - [**手动创建付费墙**](capacitor-making-purchases):用代码构建自己的付费墙界面,但仍使用 Adapty 获取产品并处理购买。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅将 Adapty 用于数据分析和集成。 不确定该选哪个?请查看[快速入门中的对比表格](capacitor-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 通过 npm 添加 Adapty SDK 依赖,并使用您的 Public SDK key 激活它。这是一切功能的基础——没有它,其他任何功能都无法正常运行。 **指南:** [安装并配置 Adapty SDK](sdk-installation-capacitor) :::info 本演示面向 Adapty Capacitor SDK v4(测试版)——即[快速入门](capacitor-quickstart-paywalls)所介绍的 API。v4 尚处于预发布阶段,请确保你的 LLM 固定使用精确版本(`npm install @adapty/capacitor@4.0.0-beta.2`),而非安装最新的稳定版 3.x。请参阅 [SDK 4.0 安装说明](sdk-installation-capacitor#adapty-sdk-40-beta)及[迁移指南](migration-to-capacitor-sdk-v4)。 ::: 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-capacitor.md ``` :::tip[Checkpoint] - **预期结果:** 应用在 iOS 和 Android 上均能构建并运行,控制台显示 Adapty 激活日志。 - **常见问题:** 提示"Public API key is missing" → 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 展示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙、展示付费墙并处理购买事件。具体需要参考哪些指南,取决于你的购买处理方式。 每完成一个购买功能就在沙盒中测试一次,不要等到最后再统一测试。沙盒环境的配置说明请参见[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **指南:** - [使用流程启用购买(快速入门)](capacitor-quickstart-paywalls) - [获取流程与付费墙](capacitor-get-pb-paywalls) - [展示流程与付费墙](capacitor-present-paywalls) - [处理事件](capacitor-handling-events) - [响应操作](capacitor-handle-paywall-actions) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/capacitor-quickstart-paywalls.md - https://adapty.io/docs/zh/capacitor-get-pb-paywalls.md - https://adapty.io/docs/zh/capacitor-present-paywalls.md - https://adapty.io/docs/zh/capacitor-handling-events.md - https://adapty.io/docs/zh/capacitor-handle-paywall-actions.md ``` :::tip[Checkpoint] - **预期结果:** 流程正常显示,并附带你配置的产品。点击某个产品后会触发沙盒购买对话框。 - **注意事项:** 流程为空或出现 `getFlow` 错误 → 请确认版位 ID 与看板中的完全一致,且该版位已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="Manual paywalls"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](capacitor-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-capacitor) - [渲染通过远程配置设计的付费墙](present-remote-config-paywalls-capacitor) - [发起购买](capacitor-making-purchases) - [恢复购买](capacitor-restore-purchase) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/capacitor-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-capacitor.md - https://adapty.io/docs/zh/present-remote-config-paywalls-capacitor.md - https://adapty.io/docs/zh/capacitor-making-purchases.md - https://adapty.io/docs/zh/capacitor-restore-purchase.md ``` :::tip[检查点] - **预期结果:** 自定义付费墙显示从 Adapty 获取的产品,点击产品触发沙盒购买对话框。 - **注意事项:** 产品数组为空 → 请确认付费墙已在看板中分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **指南:** - [Observer 模式概述](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-capacitor) - [在 Observer 模式中上报交易](report-transactions-observer-mode-capacitor) :::tip[Checkpoint] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易应出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认您已向 Adapty 上报交易,并且两个应用商店均已配置服务器通知。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买完成后,检查用户画像中是否存在有效的访问等级,以控制高级内容的访问权限。 **指南:** [检查订阅状态](capacitor-check-subscription-status) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/capacitor-check-subscription-status.md ``` :::tip[检查点] - **预期效果:** 沙盒购买完成后,`profile.accessLevels['premium']?.isActive` 返回 `true`。 - **注意事项:** 购买后 `accessLevels` 为空 → 检查该产品在看板中是否已分配访问等级。 ::: ### 识别用户 \{#identify-users\} 将你的应用用户账号与 Adapty 用户画像关联,确保购买记录在多设备间同步保留。 :::important 如果你的应用无需登录认证,请跳过此步骤。 ::: **指南:** [识别用户](capacitor-quickstart-identify) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/capacitor-quickstart-identify.md ``` :::tip[Checkpoint] - **预期结果:** 调用 `adapty.identify()` 后,看板的 **Profiles** 部分会显示你的自定义用户 ID。 - **注意事项:** 请在激活后、获取付费墙之前调用 `identify`,以避免匿名用户画像归因问题。 ::: ### 准备发布 \{#prepare-for-release\} 当你的集成在沙盒环境中运行正常后,请按照发布检查清单逐项确认,确保一切已准备好上线。 **指南:** [发布检查清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[Checkpoint] - **预期效果:** 所有检查项均已确认:商店连接、服务器通知、购买流程、访问等级检查及隐私合规要求。 - **常见问题:** 缺少服务器通知 → 请在 **App settings → iOS SDK** 中配置 App Store 服务器通知,并在 **App settings → Android SDK** 中配置 Google Play 实时开发者通知。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单页范围的更广上下文,我们提供了列出或汇总所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面的 `.md` 链接。这是一项[新兴标准](https://llmstxt.org/),旨在让 LLM 更方便地访问网站内容。请注意,某些 AI 智能体(如 ChatGPT)需要先下载 `llms.txt`,再以文件形式上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站合并为单一文件。体积较大——仅在需要完整内容时使用。 - Capacitor 专属版本 [`capacitor-llms.txt`](https://adapty.io/docs/zh/capacitor-llms.txt) 与 [`capacitor-llms-full.txt`](https://adapty.io/docs/zh/capacitor-llms-full.txt):平台专属子集,相比完整站点可节省 token 用量。 --- # File: capacitor-paywalls --- --- title: "流程与付费墙 - Capacitor" description: "在 Capacitor 应用中展示和管理使用 Adapty 流程编辑器或付费墙编辑工具构建的流程与付费墙。" --- ## 显示付费墙 \{#display-paywalls\} ### Adapty 流程编辑工具与付费墙编辑工具 \{#adapty-flow-builder--paywall-builder\} <CustomDocCardList ids={['capacitor-get-pb-paywalls', 'capacitor-present-paywalls', 'capacitor-handling-events', 'capacitor-handle-paywall-actions']} /> :::tip 若想快速上手 Adapty 流程和付费墙,请参阅我们的[快速入门指南](capacitor-quickstart-paywalls)。 ::: ### 手动实现付费墙 \{#implement-paywalls-manually\} <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases']} /> 有关手动实现付费墙和处理购买的更多指南,请参阅[分类](capacitor-implement-paywalls-manually)。 ## 实用功能 \{#useful-features\} <CustomDocCardList ids={['capacitor-use-fallback-paywalls', 'capacitor-web-paywall']} /> --- # File: capacitor-get-pb-paywalls --- --- title: "获取 flow 与付费墙 - Capacitor" description: "在 Capacitor 应用中从 Adapty 获取 flow 和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" /> 在[设计好流程或付费墙编辑工具中的付费墙](adapty-paywall-builder)之后,您可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体如下所述。 请注意,本主题适用于流程和付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙和产品](fetch-paywalls-and-products-capacitor)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在开始于移动应用中展示流程和付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程/付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程/付费墙添加到其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-capacitor)。 </details> ## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已经在 Flow Builder 或付费墙编辑工具中设计好了流程或付费墙,就不需要在移动端代码中手动处理渲染逻辑来将其展示给用户。这类流程或付费墙已经包含了展示内容和展示方式的完整配置。不过,你仍然需要通过版位获取其 ID 及视图配置,然后在移动应用中将其呈现出来。 尽早获取流程或付费墙并创建其[视图](capacitor-get-pb-paywalls#fetch-the-view-configuration)——最好在展示前提前完成。`createFlowView` 方法会加载视图配置,并在后台开始下载和缓存图片。调用时机越早,下载所需的时间就越充裕。当你真正展示流程或付费墙时,其配置和图片可能已经缓存完毕,可以直接显示。 要获取流程或付费墙,请使用 `getFlow` 方法: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow/paywall } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |-------------------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`'reload_revalidating_cache_data'` | <p>通过可选的 `params` 对象传入。默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时直接返回缓存数据。这样一来,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清除时才会被清空。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 来加快付费墙的获取速度,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>通过可选的 `params` 对象传入。该值限制此方法的超时时间。如果超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeoutMs` 中指定的值,因为该操作在底层可能包含多个不同的请求。</p> | **不要硬编码产品 ID。** 你唯一需要硬编码的 ID 是版位 ID。流程和付费墙均在远程配置,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果付费墙今天返回两个产品,明天返回三个,应无需修改代码即可全部展示。 响应参数: | 参数 | 描述 | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | 一个 `AdaptyFlow` 对象,包含流程的标识符(`id`、`variationId`)、名称、版位、付费墙变体(`paywalls`)以及远程配置(`remoteConfigs`)。 | ## 获取视图配置 \{#fetch-the-view-configuration\} :::important 请确保在编辑工具中启用了 **Show on device** 开关。如果未开启该选项,将无法获取视图配置。 ::: 如果版位是在 **Flow Builder** 或 **Paywall Builder** 中设计的,Adapty 会自动为您渲染 UI。使用 `createFlowView` 创建视图,然后[展示流程或付费墙](capacitor-present-paywalls)。如果版位是没有编辑工具 UI 的自定义付费墙,请改为[将其作为远程配置付费墙处理](present-remote-config-paywalls-capacitor)。 在 Capacitor SDK 中,直接调用 `createFlowView` 即可——无需提前获取视图配置。 :::warning `createFlowView` 方法的返回结果只能使用一次。如需再次使用,请重新调用 `createFlowView` 方法。不重新创建而重复调用可能会导致错误。 ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | 必填 | 一个 `AdaptyFlow` 对象,用于获取所需流程/付费墙的控制器。 | | **customTags** | 可选 | 定义自定义标签及其解析值的字典。自定义标签在内容中作为占位符使用,会动态替换为特定字符串,从而在流程/付费墙中实现个性化内容。详情请参阅付费墙编辑工具中的自定义标签主题。 | | **prefetchProducts** | 可选 | 启用后可优化产品在屏幕上的显示时机。设为 `true` 时,AdaptyUI 将自动预加载所需产品。默认值:`true`。 | | **android.enableSafeArea** | 可选 | 仅适用于 Android(在 iOS 上会被忽略)。嵌套在 `android` 键下。设为 `true` 时,流程视图会应用安全区域内边距。默认值:`true`。该默认值适用于大多数场景。 | :::note 如果您使用多种语言,请了解如何添加[流程本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何在[此处](capacitor-localizations-and-locale-codes)正确使用语言区域代码。 ::: 获取视图后,请[展示流程/付费墙](capacitor-present-paywalls)。 ## 为默认目标受众获取流程或付费墙以加快加载速度 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,流程和付费墙几乎可以瞬间完成加载,无需担心速度问题。但如果你配置了大量目标受众和版位,且用户的网络连接较差,加载流程或付费墙的时间可能会比预期更长。在这种情况下,你可能希望直接展示默认流程或付费墙,以确保良好的用户体验,而不是让用户看到空白页面。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程或付费墙。但请务必理解,推荐的方式是通过 `getFlow` 方法来获取流程或付费墙,详情请参阅上方的[获取流程/付费墙](#fetch-flowpaywall)部分。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显缺陷: - **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会面临挑战。你要么设计兼容当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些缺点以换取更快的 flow 或付费墙加载速度,可以按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。 ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow/paywall } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`'reload_revalidating_cache_data'` | <p>通过可选的 `params` 对象传入。默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `'return_cache_data_else_load'`——若缓存数据存在则直接返回。这种情况下用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在卸载重装应用或手动清除时才会被清空。</p> | ## 自定义资源 \{#customize-assets\} 要自定义流程/付费墙中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,通过这些 ID 定位相应元素并自定义其行为。 对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,您可以: - 为部分用户展示不同的图片或视频。 - 在远程主图加载时显示本地预览图。 - 在视频播放前先显示预览图。 以下是通过简单字典提供自定义资源的示例: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; const view = await createFlowView(flow, { customAssets }); ``` :::note 如果找不到资源文件,流程/付费墙将回退到其默认外观。 ::: </SDKv4> <SDKv3> 在 [Adapty 看板中使用付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,你可以在移动应用中展示它。整个流程的第一步是获取与版位关联的付费墙及其视图配置,具体如下所述。 请注意,本主题涉及使用付费墙编辑工具自定义的付费墙。如需了解如何获取远程配置付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙和产品](fetch-paywalls-and-products-capacitor)主题。 <details> <summary>在移动应用中展示付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在您的移动应用中安装 [Adapty SDK](sdk-installation-capacitor)。 </details> ## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动应用代码中手动编写渲染逻辑来向用户展示它。这类付费墙已经包含了展示内容和展示方式的完整配置。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动应用中将其呈现出来。 为确保最佳性能,请尽早获取付费墙及其[视图配置](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便图片有充足时间完成下载,再呈现给用户。 使用 `getPaywall` 方法获取付费墙: ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言区域代码及使用建议,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。</p> | | **params** | 可选 | 获取付费墙的附加参数。 | **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果今天某个付费墙返回两个产品,明天返回三个,则无需修改代码即可全部展示。 返回参数: | 参数 | 描述 | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | 一个 [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中开启 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 获取付费墙后,检查它是否包含 `ViewConfiguration`,这表明它是使用付费墙编辑工具创建的。这将指导你如何展示付费墙。如果存在 `ViewConfiguration`,则将其视为付费墙编辑工具付费墙;否则,[将其作为远程配置付费墙处理](present-remote-config-paywalls-capacitor)。 在 Capacitor SDK 中,直接调用 `createPaywallView` 方法,无需手动先获取视图配置。 :::warning `createPaywallView` 方法的返回结果只能使用一次。如需再次使用,请重新调用 `createPaywallView` 方法。 ::: ```typescript showLineNumbers if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { // use your custom logic } ``` 参数: | 参数 | 是否必填 | 描述 | | :------------------- | :------- | :----------------------------------------------------------- | | **paywall** | 必填 | 一个 `AdaptyPaywall` 对象,用于获取目标付费墙的控制器。 | | **customTags** | 选填 | 定义自定义标签及其对应值的字典。自定义标签作为付费墙内容中的占位符,在运行时动态替换为指定字符串,从而实现付费墙的个性化内容展示。详情请参阅付费墙编辑工具中的自定义标签相关文档。 | | **prefetchProducts** | 选填 | 启用后可优化产品在屏幕上的展示时机。设为 `true` 时,AdaptyUI 将自动预加载所需产品。默认值:`false`。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码([点击了解](capacitor-localizations-and-locale-codes))。 ::: 获取视图后,[展示付费墙](capacitor-present-paywalls)。 ## 为默认目标受众获取付费墙以加快加载速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙的获取速度非常快,无需额外优化。但如果你配置了大量目标受众和付费墙,且用户的网络状况较差,获取付费墙可能会花费较长时间。在这种情况下,你可能希望先展示一个默认付费墙,以确保用户体验流畅,而不是什么都不显示。 为解决此问题,你可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众对应的付费墙。但请务必注意,推荐的做法是通过 `getPaywall` 方法获取付费墙,详见上文[获取付费墙信息](#fetch-paywall-designed-with-paywall-builder)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显缺点: - **潜在的向后兼容性问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,你要么需要设计同时兼容当前(旧版)的付费墙,要么接受当前(旧版)用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-paywall-designed-with-paywall-builder)介绍的 `getPaywall`。 ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 说明 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](capacitor-localizations-and-locale-codes)。</p> | | **params** | 可选 | 获取付费墙时的附加参数。 | ## 自定义资源 \{#customize-assets\} 要自定义付费墙中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你可以通过这些 ID 定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 为部分用户展示不同的图片或视频。 - 在远程主图加载时显示本地预览图。 - 在播放视频前先展示预览图。 以下是通过简单字典提供自定义资源的示例: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }); ``` :::note 如果找不到某个资源,付费墙将回退到其默认外观。 ::: </SDKv3> --- # File: capacitor-present-paywalls --- --- title: "展示流程与付费墙 - Capacitor" description: "在您的 Capacitor 应用中向用户展示流程和付费墙。" --- <SDKv4> 如果你在付费墙编辑工具中创建了流程或付费墙,无需在移动端代码中手动渲染,即可将其展示给用户。这样的流程已包含展示内容和展示方式的完整配置。 在开始之前,请确保: 1. 你已[创建流程或付费墙](create-paywall)。 2. 你已将其添加到[版位](placements)。 3. 你已[获取流程并准备好视图](capacitor-get-pb-paywalls)。 :::warning 本指南仅适用于**流程和付费墙编辑工具付费墙**,需要 SDK v4.0 或更高版本。呈现流程的方式与远程配置付费墙有所不同。 - 如需呈现**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls-capacitor)。 ::: 要将流程或付费墙显示为独立屏幕,请在由 [`createFlowView`](capacitor-get-pb-paywalls#fetch-the-view-configuration) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示流程,请再次调用 `createFlowView` 以创建新的 `view` 实例。 :::warning 禁止在未重新创建的情况下复用同一个 `view`,否则将导致错误。 ::: ```typescript showLineNumbers const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // await view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important 多次调用 `setEventHandlers` 会覆盖你设置的处理器,替换掉对应事件之前设置的默认处理器和自定义处理器。 ::: ## 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置流程在 iOS 上的展示方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 两个值。在 Android 上,流程始终以全屏 Activity 的形式展示。 ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请使用 `timerId`,在本例中为 `CUSTOM_TIMER_NY`,即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。这可确保你的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(计算方式为计时器的结束时间(如元旦)减去当前时间)。 ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` 在此示例中,`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。计时器会确保你的应用动态更新并显示正确的倒计时值——例如 `13d 09h 03m 34s`(由计时器的结束时间(如元旦)减去当前时间计算得出)。 ## 显示对话框 \{#show-dialog\} 当 Android 上展示 flow 视图时,请使用此方法代替原生的 alert 对话框。在 Android 上,普通 alert 会出现在 flow 视图的后面,导致用户无法看到。此方法可确保在所有平台上,对话框都能正确显示在 flow 的上层。 ```typescript showLineNumbers try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## 用新订阅替换旧订阅 \{#replace-one-subscription-with-another\} 当 Android 用户在已有活跃订阅的情况下尝试购买新订阅时,你可以在创建流程视图时传入订阅更新参数,来控制新购买的处理方式。如需用新订阅替换当前订阅,请在 `createFlowView` 中使用 `productPurchaseParams`,并传入 `oldSubVendorProductId` 和 `prorationMode` 参数。 ```typescript showLineNumbers const productPurchaseParams = flow.paywalls .flatMap((paywall) => paywall.productIdentifiers) .map((productId) => { const params: MakePurchaseParamsInput = {}; if (Capacitor.getPlatform() === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> 如果你已经使用付费墙编辑工具自定义了付费墙,就不需要在移动端代码中手动处理渲染逻辑来向用户展示它。这类付费墙已经包含了展示内容和展示方式的完整配置。 :::warning 本指南仅适用于**付费墙编辑工具构建的付费墙**。远程配置付费墙的展示流程有所不同。如需了解**远程配置付费墙**的展示方法,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls)。 ::: 要显示付费墙,请在由 [`createPaywallView`](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示付费墙,请重新调用 `createPaywallView` 创建新的 `view` 实例。 :::warning 重复使用同一个 `view` 而不重新创建,可能会导致错误。 ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; }, }); try { await view.present(); } catch (error) { // handle the error } ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请使用 `timerId`,本示例中为 `CUSTOM_TIMER_NY`,即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。这样可以确保应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(计算方式为计时器的结束时间(如元旦)减去当前时间)。 ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` 在此示例中,`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。该计时器可确保你的应用动态更新计时器显示值,例如 `13d 09h 03m 34s`(计算方式为计时器的结束时间(如元旦)减去当前时间)。 ## 显示对话框 \{#show-dialog\} 在 Android 上展示付费墙视图时,请使用此方法代替原生的 alert 对话框。在 Android 上,普通的 alert 会出现在付费墙视图的后面,导致用户看不到它。此方法可确保对话框在所有平台上都能正确显示在付费墙上方。 ```typescript showLineNumbers title="Capacitor" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置付费墙在 iOS 上的展示方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 两个值。 ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` </SDKv3> --- # File: capacitor-handle-paywall-actions --- --- title: "响应流程操作 - Capacitor" description: "使用 Adapty 在 Capacitor 中处理来自流程和付费墙的按钮操作,提升应用变现效果。" --- <SDKv4> 如果你正在使用 Adapty 的[付费墙编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)构建流程或付费墙,正确设置按钮至关重要: 1. 在[编辑工具中添加按钮](paywall-buttons),并为其分配已有的操作,或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理你分配的每个操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **购买、恢复、流程和付费墙关闭,以及 URL 打开均已自动处理。** 你可以配置这些操作的默认行为,或为自定义操作实现响应逻辑。 ::: :::note 为某个事件设置处理器会完全替换其默认行为。未设置处理器的事件仍保持默认行为。 ::: ## 关闭流程和付费墙 \{#close-flows-and-paywalls\} 要添加一个用于关闭流程或付费墙的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,实现一个处理 `close` 操作的回调,用于关闭流程或付费墙。 :::info 在 Capacitor SDK 中,`close` 操作默认会触发关闭流程或付费墙。不过,如有需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。 ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` 在 Android 上,系统的**返回**按钮和返回手势会触发独立的 `onAndroidSystemBack` 事件。在 SDK v4 中,该事件默认不再关闭 flow。如果希望**返回**按钮关闭 flow,请在处理函数中返回 `true`: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onAndroidSystemBack() { return true; // close the flow when the Back button is pressed }, }); ``` ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加 **Link** 元素,并以与 **Open URL** 动作按钮相同的方式处理它。 ::: 要在流程或付费墙中添加一个可打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在编辑工具中,添加一个按钮,为其分配 **Open URL** 动作,并输入你想打开的 URL。 2. 如有需要,在你的应用代码中实现 `openUrl` 动作的处理程序,以自定义方式打开接收到的 URL。 :::info 在 Capacitor SDK 中,点击 URL 默认会在原生浏览器中打开:SDK 会调用 `adapty.openWebUrl({ url, openIn })`,遵循你在编辑工具中设置的 **Open in** 选项,并保持流程继续运行。不过,如有需要,你可以在代码中覆盖此行为。 ::: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onUrlPress(url) { // Open the URL your own way, e.g. with the Capacitor Browser plugin Browser.open({ url }); return false; // keep the flow open }, }); ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在编辑工具中,添加一个按钮,为其指定 **Custom** 操作,并分配一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现对应的处理逻辑。 例如,如果你有其他订阅套餐或一次性购买商品,可以添加一个按钮来展示另一个流程或付费墙: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </SDKv4> <SDKv3> 如果您正在使用 Adapty 付费墙编辑工具构建付费墙,正确设置按钮至关重要: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配现有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理您分配的每个操作。 本指南介绍如何在代码中处理自定义操作和现有操作。 ## 关闭付费墙 \{#close-paywalls\} 要添加一个关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 动作。 2. 在应用代码中,实现一个处理 `close` 动作的处理器,用于关闭付费墙。 :::info 在 Capacitor SDK 中,`close` 动作默认会触发关闭付费墙。不过,如果需要,你也可以在代码中覆盖此行为。例如,关闭一个付费墙可能会触发打开另一个付费墙。 ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close } }); ``` ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果需要添加一组链接(例如使用条款和购买恢复),可在付费墙编辑工具中添加 **Link** 元素,并以与 **Open URL** 操作的按钮相同的方式处理它。 ::: 要添加一个从付费墙打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入要打开的 URL。 2. 在应用代码中,为 `openUrl` 操作实现一个处理器,用于在浏览器中打开接收到的 URL。 :::info 在 Capacitor SDK 中,`window.open` 操作默认会触发打开 URL。不过,如果需要,你可以在代码中覆盖此行为。 ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 操作。 2. 在应用代码中,实现 `login` 操作的处理程序以识别您的用户。 ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { // Navigate to login screen console.log('User requested login'); } } }); ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并为其指定一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现一个处理程序。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮来显示另一个付费墙: ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </SDKv3> --- # File: capacitor-handling-events --- --- title: "处理 flow 与付费墙事件 - Capacitor" description: "使用 Adapty SDK 在 Capacitor 应用中处理 flow 与付费墙事件。" --- <SDKv4> :::important 本指南介绍购买、恢复、产品选择及流程渲染的事件处理。你也可以设置按钮处理(关闭流程、打开链接、自定义操作等)。详情请参阅[按钮操作处理指南](capacitor-handle-paywall-actions)。 ::: [流程编辑工具](adapty-flow-builder)构建的流程和付费墙无需额外代码即可完成购买和恢复购买。但它们会生成一些你的应用可以响应的事件,包括按钮点击(关闭按钮、URL、产品选择等)以及与流程中购买相关操作的通知。请参阅下文了解如何响应这些事件。 要在移动应用中控制或监听流程界面上发生的事件,请实现 `view.setEventHandlers` 方法: :::important 每个事件只能设置一个处理器:多次调用 `setEventHandlers` 会覆盖你提供的处理器,同时替换掉那些特定事件的默认处理器和之前设置的处理器。未设置的处理器保持默认行为。`setEventHandlers` 返回一个取消订阅的函数,`view.dismiss()` 会清除所有处理器。 ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // close the flow (default behavior) }, onAndroidSystemBack() { return true; // close the flow; by default, it stays open }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, onPurchaseStarted(product) { /***/ }, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/ }, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl({ url, openIn }).catch(console.warn); // same as the SDK default return false; // keep the flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` <Details> <summary>事件示例(点击展开)</summary> 以下示例展示了每个处理程序中可用的属性,注释中提供了说明性的值。 ```typescript // onUrlPress url; // 'https://example.com/terms' openIn; // 'browser_in_app' or 'browser_out_app' // onCustomAction actionId; // 'login' // onProductSelected productId; // 'premium_monthly' // onPurchaseStarted, onPurchaseCompleted, onPurchaseFailed product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price?.amount; // 9.99 product.price?.currencyCode; // 'USD' product.price?.localizedString; // '$9.99' // onPurchaseCompleted purchaseResult.type; // 'success', 'pending', or 'user_cancelled' if (purchaseResult.type === 'success') { purchaseResult.profile.accessLevels['premium']?.isActive; // true } // onRestoreCompleted profile.accessLevels['premium']?.isActive; // true // onPurchaseFailed, onRestoreFailed, onError, onLoadingProductsFailed error.message; // 'Purchase failed due to insufficient funds' ``` </Details> 您可以只注册需要的事件处理程序,忽略不需要的。这样就不会创建多余的事件监听器。所有事件处理程序均为可选。 事件处理程序返回一个布尔值。如果返回 `true`,则认为展示流程已完成,流程屏幕随即关闭,并移除该视图的所有事件监听器。 某些事件处理器具有默认行为,您可以根据需要进行覆盖: - `onCloseButtonPress`:当用户按下关闭按钮时,关闭流程。 - `onUrlPress`:通过 `adapty.openWebUrl` 在原生浏览器中打开被点击的 URL,遵循编辑工具中设置的 **Open in** 选项,并保持流程开启。 - `onAndroidSystemBack`:当用户按下 **Back** 按钮时,保持流程开启。返回 `true` 可关闭流程。 - `onPurchaseCompleted`:购买完成后保持流程开启。返回 `true` 可关闭流程。 - `onRestoreCompleted`:成功恢复购买后保持流程开启。返回 `true` 可关闭流程。 - `onError`:如果流程渲染失败,则关闭流程。 ### 事件处理程序 \{#event-handlers\} | 事件处理器 | 描述 | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | 当用户执行自定义操作时触发,例如点击[自定义按钮](paywall-buttons)。 | | **onUrlPress** | 当用户点击流程中的 URL 时触发。 | | **onAndroidSystemBack** | 当用户点击 Android 系统的 **Back** 按钮时触发。默认情况下流程保持打开;返回 `true` 可关闭它。 | | **onCloseButtonPress** | 当关闭按钮可见且用户点击它时触发。建议在此处理器中关闭流程页面。 | | **onPurchaseCompleted** | 购买完成时触发,无论结果是成功、用户取消还是待审批。购买成功时会提供更新后的 `AdaptyProfile`。用户取消及待处理付款(如需家长审批)会触发此事件,而非 `onPurchaseFailed`。 | | **onPurchaseStarted** | 当用户点击"购买"操作按钮以开始购买流程时触发。 | | **onPurchaseFailed** | 因错误导致购买失败时触发(如付款限制、无效产品、网络故障、交易验证失败等)。用户取消或待处理付款不会触发此事件,这些情况会触发 `onPurchaseCompleted`。 | | **onRestoreStarted** | 当用户开始恢复购买流程时触发。 | | **onRestoreCompleted** | 购买恢复成功时触发,并提供更新后的 `AdaptyProfile`。如果用户已具备所需的 `accessLevel`,建议关闭页面。请参阅[订阅状态](capacitor-listen-subscription-changes)了解如何检查。 | | **onRestoreFailed** | 恢复流程失败时触发,并提供 `AdaptyError`。 | | **onProductSelected** | 当用户在流程视图中选择任意产品时触发,可用于监控用户在购买前的选择。 | | **onError** | 视图渲染过程中发生错误时触发,并提供 `AdaptyError`。此类错误本不应出现,如果遇到,请告知我们。 | | **onLoadingProductsFailed** | 产品加载失败时触发,并提供 `AdaptyError`。如果在创建视图时未设置 `prefetchProducts: true`,AdaptyUI 会自行从服务器获取所需对象。 | | **onAppeared** | 当流程展示给用户时触发。在 iOS 上,当用户点击流程中的 [Web 付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button) 且 Web 付费墙在应用内浏览器中打开时,也会触发此事件。 | | **onDisappeared** | 当用户关闭流程时触发。在 iOS 上,当从流程中在应用内浏览器打开的 [Web 付费墙](web-paywall#step-2a-add-a-web-purchase-button) 从屏幕消失时,也会触发此事件。 | | **onWebPaymentNavigationFinished** | 尝试打开 [Web 付费墙](web-paywall) 进行购买后触发,无论成功与否。 | | **onRequestAppReview** | 为流程中的应用评价请求预留。目前流程尚不会触发应用评价请求,无需实现。 | | **onAnalytics** | 为流程中的自定义分析事件预留。目前流程尚不会向你的代码发送这些事件,无需实现。 | | **onRequestPermission** | 为流程中的系统权限请求(如推送通知或相机访问)预留。目前流程尚不会触发权限请求,无需实现。 | | **onObserverPurchaseInitiated** | 仅限观察者模式:当用户在流程中点击购买按钮时触发。Adapty 不会执行购买——请使用你自己的购买代码完成购买,然后将交易上报给 Adapty。详见下方[在观察者模式下处理购买](#handle-purchases-in-observer-mode)。 | | **onObserverRestoreInitiated** | 仅限观察者模式:当用户在流程中点击恢复按钮时触发。Adapty 不会执行恢复——请自行恢复,然后上报所有已恢复的交易。详见下方[在观察者模式下处理购买](#handle-purchases-in-observer-mode)。 | ### 在观察者模式下处理购买 \{#handle-purchases-in-observer-mode\} 如果你以[观察者模式](implement-observer-mode-capacitor)(`observerMode: true`)激活了 SDK 并展示 Adapty 渲染的流程,SDK 不会为你执行购买操作。当用户点击购买或恢复按钮时,SDK 会调用 `onObserverPurchaseInitiated` 或 `onObserverRestoreInitiated`,你可以用自己的代码来处理购买或恢复逻辑。完整设置请参阅[在观察者模式下展示流程](capacitor-present-flows-in-observer-mode)。 </SDKv4> <SDKv3> :::important 本指南涵盖购买、恢复、产品选择和付费墙渲染的事件处理。你还必须实现按钮处理(关闭付费墙、打开链接等)。详情请参阅我们的[按钮操作处理指南](capacitor-handle-paywall-actions)。 ::: 通过[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,供您的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等),以及付费墙上与购买相关操作的通知。请参阅下文了解如何响应这些事件。 如需在移动应用中控制或监控付费墙界面上发生的流程,请实现 `view.setEventHandlers` 方法: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close }, onAndroidSystemBack() { console.log('User pressed back button'); return true; // Allow the paywall to close }, onAppeared() { console.log('Paywall appeared'); return false; // Don't close the paywall }, onDisappeared() { console.log('Paywall disappeared'); }, onPurchaseCompleted(purchaseResult, product) { console.log('Purchase completed:', purchaseResult); return purchaseResult.type !== 'user_cancelled'; // Close if not cancelled }, onPurchaseStarted(product) { console.log('Purchase started:', product); return false; // Don't close the paywall }, onPurchaseFailed(error, product) { console.error('Purchase failed:', error); return false; // Don't close the paywall }, onRestoreCompleted(profile) { console.log('Restore completed:', profile); return true; // Close the paywall after successful restore }, onRestoreFailed(error) { console.error('Restore failed:', error); return false; // Don't close the paywall }, onProductSelected(productId) { console.log('Product selected:', productId); return false; // Don't close the paywall }, onRenderingFailed(error) { console.error('Rendering failed:', error); return false; // Don't close the paywall }, onLoadingProductsFailed(error) { console.error('Loading products failed:', error); return false; // Don't close the paywall }, onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` <Details> <summary>事件示例(点击展开)</summary> ```typescript // onCloseButtonPress { "event": "close_button_press" } // onAndroidSystemBack { "event": "android_system_back" } // onAppeared { "event": "paywall_shown" } // onDisappeared { "event": "paywall_closed" } // onUrlPress { "event": "url_press", "url": "https://example.com/terms" } // onCustomAction { "event": "custom_action", "actionId": "login" } // onProductSelected { "event": "product_selected", "productId": "premium_monthly" } // onPurchaseStarted { "event": "purchase_started", "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseCompleted - Success { "event": "purchase_completed", "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" } } // onPurchaseCompleted - Cancelled { "event": "purchase_completed", "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseFailed { "event": "purchase_failed", "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } // onRestoreCompleted { "event": "restore_completed", "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" } ] } } // onRestoreFailed { "event": "restore_failed", "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "event": "rendering_failed", "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "event": "loading_products_failed", "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> 您可以只注册需要的事件处理器,无需注册的可以忽略。这样就不会创建多余的事件监听器。所有事件处理器均为可选。 事件处理器返回一个布尔值。若返回 `true`,则视为展示流程已完成,付费墙页面将关闭,该视图的所有事件监听器也会随之移除。 某些事件处理程序具有默认行为,您可以根据需要进行覆盖: - `onCloseButtonPress`:点击关闭按钮时关闭付费墙。 - `onAndroidSystemBack`:按下 **Back** 按钮时关闭付费墙。 - `onRestoreCompleted`:恢复成功后关闭付费墙。 - `onPurchaseCompleted`:除非用户取消,否则关闭付费墙。 - `onRenderingFailed`:付费墙渲染失败时关闭付费墙。 - `onUrlPress`:在系统浏览器中打开 URL,并保持付费墙开启。 ### 事件处理器 \{#event-handlers\} | 事件处理程序 | 描述 | |:----------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | 当用户执行自定义操作时触发,例如点击[自定义按钮](paywall-buttons)。 | | **onUrlPress** | 当用户点击付费墙中的 URL 时触发。 | | **onAndroidSystemBack** | 当用户点击 Android 系统 **Back** 按钮时触发。 | | **onCloseButtonPress** | 当关闭按钮可见且用户点击时触发。建议在此处理程序中关闭付费墙屏幕。 | | **onPurchaseCompleted** | 购买完成时触发,无论成功、用户取消还是等待审批。购买成功时提供更新后的 `AdaptyProfile`。用户取消和待处理支付(如需要家长批准)会触发此事件,而非 `onPurchaseFailed`。 | | **onPurchaseStarted** | 当用户点击"购买"操作按钮开始购买流程时触发。 | | **onPurchaseCancelled** | 当用户发起购买流程后手动中断(取消支付对话框)时触发。 | | **onPurchaseFailed** | 购买因错误失败时触发(如支付限制、无效产品、网络故障、交易验证失败)。用户取消或待处理支付不会触发此事件,这些情况会触发 `onPurchaseCompleted`。 | | **onRestoreStarted** | 当用户开始购买恢复流程时触发。 | | **onRestoreCompleted** | 购买恢复成功时触发,并提供更新后的 `AdaptyProfile`。如果用户拥有所需的 `accessLevel`,建议关闭屏幕。请参阅[订阅状态](capacitor-listen-subscription-changes)主题了解如何进行检查。 | | **onRestoreFailed** | 恢复流程失败时触发,并提供 `AdaptyError`。 | | **onProductSelected** | 付费墙视图中任意产品被选择时触发,允许您监控用户在购买前的选择。 | | **onAppeared** | 付费墙视图出现在屏幕上时触发。在 iOS 上,当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button)且网页付费墙在应用内浏览器中打开时,也会触发此事件。 | | **onDisappeared** | 付费墙视图从屏幕消失时触发。在 iOS 上,当从付费墙在应用内浏览器中打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)从屏幕消失时,也会触发此事件。 | | **onRenderingFailed** | 视图渲染期间发生错误时触发,并提供 `AdaptyError`。此类错误不应出现,如遇到请告知我们。 | | **onLoadingProductsFailed** | 产品加载失败时触发,并提供 `AdaptyError`。如果您在创建视图时未设置 `prefetchProducts: true`,AdaptyUI 将自行从服务器获取所需对象。 | </SDKv3> --- # File: capacitor-use-fallback-paywalls --- --- title: "Capacitor - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况" --- 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} ### Android 1. 将备用配置文件添加到您的应用程序中。选择以下目录之一: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** :::note `res/raw` 文件夹有特殊的文件命名规范(必须以字母开头,不能有大写字母,不能有特殊字符(下划线除外),且文件名中不能有空格)。 ::: 2. 更新 `FileLocation` 常量的 `android` 属性: * 如果文件位于 `assets` 目录中,传入文件相对于该目录的路径。 * 如果文件位于 `res/raw` 目录中,传入不含扩展名的文件名。 ### iOS \{#ios\} 1. 将备用 JSON 文件添加到您的项目包中:在 XCode 中打开 **File** 菜单,然后选择 **Add Files to "YourProjectName"** 选项。 2. 将您的配置文件名传递给 `FileLocation` 常量的 `ios` 属性。 ## 示例 \{#example\} ```typescript showLineNumbers const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } }; await adapty.setFallback({ fileLocation }); ``` :::important `setFallback` 必须在 SDK 获取目标流程、付费墙或用户引导之前运行。 ::: 参数: | 参数 | 描述 | | :------------------- | :------------------------------------------------------- | | **fileLocation** | 表示备用配置文件位置的对象。 | --- # File: capacitor-localizations-and-locale-codes --- --- title: "在 Capacitor SDK 中使用本地化和区域代码" description: "了解如何使用 Adapty SDK 在 Capacitor 应用中对付费墙进行本地化。" --- <SDKv4> ## 为什么这很重要 \{#why-this-is-important\} 当 Adapty 为流程选择本地化版本,以及当你为自定义付费墙读取远程配置时,语言区域代码就会发挥作用。 语言区域代码较为复杂,不同平台之间可能存在差异,因此 Adapty 在其支持的所有平台上统一采用一套内部标准。了解这套标准,有助于你预测用户最终会获取哪个本地化版本。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} Adapty 的语言代码基于略作修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 查找与用户语言区域匹配的本地化内容时,会按以下步骤进行: 1. 将语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. Adapty 查找与完整语言区域代码完全匹配的本地化内容 3. 如果未找到匹配项,Adapty 取第一个连字符前的子字符串(例如 `pt-br` 对应 `pt`),并查找匹配的本地化内容 4. 如果仍未找到匹配项,Adapty 返回默认的 `en` 本地化内容 通过这种方式,`'pt_BR'`、`pt-BR` 和 `pt-br` 都会解析到同一个本地化内容。 ## 实现本地化 \{#implementing-localizations\} 在 SDK v4 中,获取 flow 时无需传入语言区域代码。 - **流程编辑工具和付费墙编辑工具付费墙**:Adapty 会根据设备设置及你在编辑工具中配置的本地化内容自动解析语言区域。使用 `createFlowView` 渲染 flow,无需传入语言区域代码。 - **自定义(远程配置)付费墙**:`getFlow` 会在 `flow.remoteConfigs` 中返回所有已配置的本地化内容。每个条目包含一个 `lang` 代码和一个 `data` 对象。请自行选择与用户匹配的条目,并实现相应的降级逻辑: ```typescript showLineNumbers const flow = await adapty.getFlow({ placementId: 'placement_id' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` 上述语言代码匹配规则描述了 Adapty 如何对每个远程配置中存储的 `lang` 代码进行规范化处理。 </SDKv4> <SDKv3> ## 为什么这很重要 \{#why-this-is-important\} 在某些场景下,区域设置代码会发挥关键作用——例如,当你需要根据应用的当前语言获取正确的付费墙时。 由于区域设置代码较为复杂,且在不同平台之间可能存在差异,我们为所有支持的平台制定了统一的内部标准。正因为这些代码较为复杂,了解你究竟向服务器发送了什么内容以获取正确的本地化版本,以及后续会发生什么,就显得尤为重要——这样你才能始终收到预期的结果。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} 在语言代码方面,Adapty 采用了略有改动的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 发送的语言区域代码请求,并开始查找对应的付费墙本地化版本时,会按以下步骤处理: 1. 传入的语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. 然后查找与完整语言区域代码完全匹配的本地化版本 3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 取 `pt`),并查找对应的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 这样,发送了 `'pt_BR'` 的 iOS 设备、发送了 `pt-BR` 的 Android 设备,以及发送了 `pt-br` 的其他设备,都会得到相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果你正在考虑本地化问题,很可能你的项目中已经有了本地化字符串文件。在这种情况下,我们建议在每个本地化文件中,以目标 Adapty 语言代码为值,添加一个键值对。然后在调用 SDK 时提取该键对应的值,示例如下: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` 这样,您就能完全掌控应用中每位用户所获取的本地化内容。 ## 另一种实现本地化的方式 \{#implementing-localizations-the-other-way\} 你也可以不为每个本地化单独指定语言区域代码,同样能达到类似(但不完全相同)的效果。这种方式是从平台提供的其他对象中提取语言区域代码,如下所示: ```javascript showLineNumbers const getLocaleCode = () => { if (Capacitor.getPlatform() === 'ios') { return navigator.language || 'en'; } else { return navigator.language || 'en'; } }; const fetchPaywall = async () => { const locale = getLocaleCode(); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` 请注意,我们不建议使用此方法,原因如下: 1. 在 iOS 上,首选语言与当前语言区域设置并不相同。如果希望正确匹配本地化内容,你要么依赖 Apple 的内置逻辑(如果使用推荐的本地化字符串文件方式,该逻辑开箱即用),要么自行实现相同的逻辑。 2. 很难预测 Adapty 服务器实际收到的内容。例如,在 iOS 上,设备可能会生成类似 `ar_OM@numbers='latn'` 这样的语言区域标识,并将其发送到我们的服务器。而对于这个请求,你得到的不会是你期望的 `ar-om` 本地化内容,而是 `ar`,这很可能出乎意料。 Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: capacitor-web-paywall --- --- title: "实现网页付费墙" description: "了解如何使用 Adapty SDK 在 Capacitor 应用中实现网页付费墙。" --- :::important 开始之前,请确保您已[在看板中配置了网页付费墙](web-paywall),并安装了 Adapty SDK 3.6.1 或更高版本。 ::: ## 打开网页付费墙 \{#open-web-paywalls\} 如果你使用的是自行开发的付费墙,需要通过 SDK 方法来处理网页付费墙。`.openWebPaywall` 方法: 1. 生成一个唯一 URL,让 Adapty 能够将展示给特定用户的付费墙与其被跳转到的网页关联起来。 2. 追踪用户何时返回应用,并以短时间间隔调用 `.getProfile`,以判断用户画像的访问权限是否已更新。 这样,如果付款成功且访问权限已更新,订阅几乎会立即在应用中激活。 ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall({ paywallOrProduct: product })` — 根据付费墙生成 URL,并将产品数据附加到 URL 中。 2. `openWebPaywall({ paywallOrProduct: paywall })` — 根据付费墙生成 URL,但不附加产品数据。当 Adapty 付费墙中的产品与网页付费墙中的产品不同时,请使用此版本。 在 SDK v4 中,`paywallOrProduct` 的付费墙部分接收一个 `AdaptyFlowPaywall`——即已获取流程的付费墙变体。在索引访问之前,请检查 `flow.paywalls` 不为空,例如 `flow.paywalls[0]`。 ::: #### 处理错误 \{#handle-errors\} | 错误 | 描述 | 建议操作 | |------|------|----------| | AdaptyError.paywallWithoutPurchaseUrl | 该付费墙未配置网页购买链接 | 检查付费墙是否已在 Adapty 看板中正确配置 | | AdaptyError.productWithoutPurchaseUrl | 该产品没有网页购买链接 | 在 Adapty 看板中验证产品配置 | | AdaptyError.failedOpeningWebPaywallUrl | 无法在浏览器中打开该链接 | 检查设备设置,或提供其他购买方式 | | AdaptyError.failedDecodingWebPaywallUrl | 无法正确编码链接中的参数 | 验证链接参数是否有效且格式正确 | ## 获取 Web 付费墙 URL 而不直接打开它 \{#get-the-web-paywall-url-without-opening-it\} 如果你想自己展示 Web 购买页面,而不是让 SDK 来打开它,可以使用 `createWebPaywallUrl`。它返回与 `openWebPaywall` 相同的唯一 URL,你可以在自己的 Web 视图中渲染,或按需处理跳转逻辑。该方法接受与 `openWebPaywall` 相同的 `paywallOrProduct` 参数——可以是已获取流程的付费墙变体(`AdaptyFlowPaywall`),也可以是 `AdaptyPaywallProduct`。 ```typescript showLineNumbers try { const url = await adapty.createWebPaywallUrl({ paywallOrProduct: product }); // open `url` in your own web view, or handle the redirect yourself } catch (error) { console.error('Failed to create web paywall URL:', error); } ``` :::note 如需通过原生浏览器打开任意 URL(而非网页付费墙)——例如从流程的按钮触发——请改用 [`adapty.openWebUrl`](capacitor-handle-paywall-actions#open-urls-from-flows-and-paywalls)。 ::: ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} :::important 从 Adapty SDK v3.15 起支持在应用内浏览器中打开网页付费墙。 ::: 默认情况下,网页付费墙会在外部浏览器中打开。 为了提供更流畅的用户体验,你可以在应用内浏览器中打开网页付费墙。这样用户无需切换应用,即可在应用内直接完成购买。 要启用此功能,请在 `openWebPaywall` 中将 `openIn` 设置为 `WebPresentation.BrowserInApp`: ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product, openIn: WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` --- # File: capacitor-present-flows-in-observer-mode --- --- title: "在 Capacitor SDK 中以 Observer 模式展示流程" description: "在 Capacitor 应用中以 Observer 模式展示流程和付费墙编辑工具付费墙,同时使用自己的代码处理购买。" --- 如果你已经使用编辑工具自定义了流程或付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示它。这类流程或付费墙本身已包含展示内容和展示方式的完整定义。 :::warning 本节仅适用于[观察者模式](observer-vs-full-mode)。如果你未使用观察者模式,请参阅[展示流程与付费墙](capacitor-present-paywalls)主题。 ::: :::info 此功能需要 Adapty Capacitor SDK 4.0 或更高版本——此前仅在原生 iOS 和 Android SDK 中可用。请参阅[迁移指南](migration-to-capacitor-sdk-v4)进行升级。 ::: <details> <summary>开始展示流程前的准备工作(点击展开)</summary> 1. 完成 Adapty [与 App Store 的初始集成](initial_ios)以及[与 Google Play 的初始集成](initial-android)。 2. 安装并配置 Adapty SDK,确保将 `observerMode` 参数设置为 `true`。请参阅 [Capacitor SDK 安装指南](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk)。 3. 在 Adapty 看板中[创建产品](create-product)。 4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。 5. [创建版位并将流程或付费墙分配给对应版位](create-placement)。 6. 在移动应用代码中[获取流程及其配置](capacitor-get-pb-paywalls)。 </details> 在 Observer 模式下,SDK 不会替你发起购买。当用户点击 Adapty 渲染的流程或付费墙中的购买或恢复按钮时,SDK 会调用你的 `onObserverPurchaseInitiated` 或 `onObserverRestoreInitiated` 事件处理器——请在其中用你自己的代码执行购买或恢复操作。 1. 在视图上设置 observer 模式事件处理器。与其他平台不同,这里没有单独的 resolver 对象——这些处理器是常规[事件处理器](capacitor-handling-events)的一部分,因此需要在每个创建的视图上进行设置: ```typescript showLineNumbers title="Capacitor" import { adapty, createFlowView } from '@adapty/capacitor'; const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // the view shows its loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction({ transactionId, variationId: flow.variationId }), ) .finally(() => onFinishPurchase()); // the view hides the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi().finally(() => onFinishRestore()); return false; }, }); ``` `onObserverPurchaseInitiated` 处理器会在用户发起购买时通知你,`onObserverRestoreInitiated` 则会在用户发起恢复购买时通知你。收到通知后,触发你自定义的购买或恢复流程。 此外,请记得调用以下回调,将购买或恢复的进度通知 AdaptyUI。这对于正确的流程行为(例如显示加载动画等)是必要的: | 回调函数 | 描述 | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | 应调用此回调函数以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 应调用此回调函数以通知 AdaptyUI 购买已完成。 | | onStartRestore() | 应调用此回调函数以通知 AdaptyUI 恢复购买已开始。 | | onFinishRestore() | 应调用此回调函数以通知 AdaptyUI 恢复购买已完成。 | 2. 像往常一样展示流程视图:[获取流程并创建其视图](capacitor-get-pb-paywalls),然后[展示它](capacitor-present-paywalls)。无需额外参数——只有在 SDK 以 `observerMode: true` 激活时,处理器才会触发。 :::warning 别忘了[上报交易并将其与付费墙关联](report-transactions-observer-mode-capacitor)。否则,Adapty 将无法识别该交易,也无法确定购买来源的付费墙。 ::: --- # File: capacitor-implement-paywalls-manually --- --- title: "手动实现付费墙" description: "了解如何在 Capacitor 应用中使用 Adapty SDK 手动实现付费墙。" --- ## 接受购买 \{#accept-purchases\} 如果您使用的是自行实现的付费墙,可以通过 `makePurchase` 方法将购买处理委托给 Adapty。这样,我们将处理所有用户场景,您只需处理购买结果即可。 :::important `makePurchase` 适用于在 Adapty 看板中创建的产品。请确保按照[快速入门指南](quickstart)在看板中配置产品及其获取方式。 ::: <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases', 'capacitor-restore-purchase']} /> ## 观察者模式 \{#observer-mode\} 如果您希望从头实现自己的购买处理逻辑,同时仍希望享受 Adapty 提供的高级分析功能,可以使用观察者模式。 :::important 请在[此处](observer-vs-full-mode)了解观察者模式的限制。 ::: <CustomDocCardList ids={['implement-observer-mode-capacitor', 'report-transactions-observer-mode-capacitor']} /> --- # File: capacitor-quickstart-manual --- --- title: "在 Capacitor SDK 中为自定义付费墙启用购买功能" description: "将 Adapty SDK 集成到你的自定义 Capacitor 付费墙中,以启用应用内购买。" --- 本指南介绍如何将 Adapty 集成到你的自定义付费墙中。你可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。本指南使用 Adapty Capacitor SDK v4 API——如果你使用的是 v3,请参阅[迁移指南](migration-to-capacitor-sdk-v4)了解对应的方法名称。 :::important **本指南面向实现自定义付费墙的开发者。** 如果你想以最简单的方式启用内购,请使用 [Adapty 付费墙编辑工具](capacitor-quickstart-paywalls)。借助付费墙编辑工具,你可以在无代码可视化编辑器中创建付费墙,Adapty 自动处理所有购买逻辑,无需重新发布应用即可测试不同设计。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,你需要了解以下三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户提供哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,但这种设计让你无需修改应用代码即可调整产品、价格和优惠。在 SDK v4 中,版位的付费墙变体由 **flow** 对象承载——你获取一个 flow 并从中查询产品。 - [**版位**](placements) – 应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求它们。这让 A/B 测试和向不同用户展示不同付费墙变得轻而易举。 请务必理解这些概念,即使你使用的是自定义付费墙。简单来说,它们只是你管理应用内销售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到一个**版位**。这样即可获取你的产品。如需了解在看板中应如何操作,请参阅[快速入门指南](quickstart)。 ### 管理用户 \{#manage-users\} 您可以选择在后端使用身份验证,也可以不使用。 但是,Adapty SDK 对匿名用户和已识别用户的处理方式不同。请阅读[用户身份识别快速入门指南](capacitor-quickstart-identify),以了解具体细节并确保您正确处理用户信息。 ## 第一步:获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,需要执行以下步骤: 1. 通过将[版位](placements) ID 传入 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该 flow 的产品数组。 ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts({ flow }); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## 步骤 2:处理购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,调用 `makePurchase` 方法并传入所选产品。该方法会处理购买流程并返回更新后的用户画像。 ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const result: AdaptyPurchaseResult = await adapty.makePurchase({ product }); if (result.type === 'success') { // Purchase successful, profile updated } else if (result.type === 'user_cancelled') { // User canceled the purchase } else if (result.type === 'pending') { // Purchase is pending (e.g., user will pay offline with cash) } } catch (error) { // Handle the error } } ``` ## 步骤 3. 恢复购买 \{#step-3-restore-purchases\} 应用商店要求所有包含订阅的应用提供让用户恢复购买的方式。 当用户点击恢复按钮时,调用 `restorePurchases` 方法。这会将用户的购买记录与 Adapty 同步,并返回更新后的用户画像。 ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## 步骤 4. 检查订阅状态 \{#step-4-check-the-subscription-status\} 购买或恢复后,检查用户的[访问等级](access-level),以决定是否显示付费墙或解锁付费功能。`makePurchase` 和 `restorePurchases` 方法已返回更新后的用户画像;如需在应用其他地方获取当前状态,请使用 `getProfile` 方法: ```typescript showLineNumbers async function hasPremiumAccess(): Promise<boolean> { try { const profile = await adapty.getProfile(); return profile.accessLevels?.['premium']?.isActive ?? false; } catch (error) { // Handle the error } return false; } ``` 如需了解更多检查和监控订阅状态的方式(包括实时更新监听),请参阅[检查订阅状态](capacitor-check-subscription-status)。 ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox) 或 [Google Play Store](testing-on-android) 中测试您的购买流程,确保能从付费墙完成测试购买。如需了解生产级实现示例,请参考我们示例应用中的 [App.tsx](https://github.com/adaptyteam/AdaptySDK-Capacitor/blob/master/examples/adapty-devtools/src/screens/app/App.tsx),其中演示了包含完善错误处理、加载状态管理和全面 SDK 集成的购买流程实现。 --- # File: fetch-paywalls-and-products-capacitor --- --- title: "在 Capacitor SDK 中获取远程配置付费墙的付费墙和产品" description: "在 Adapty Capacitor SDK 中获取付费墙和产品,以提升用户变现效果。" --- <SDKv4> 在展示远程配置和自定义付费墙之前,你需要先获取相关信息。请注意,本主题仅涉及远程配置和自定义付费墙。如需了解如何获取 **Flow Builder** 流程或 **Paywall Builder** 付费墙及其配置,请参阅[获取 Flow Builder 流程和 Paywall Builder 付费墙及其配置](capacitor-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中获取流程和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程或付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程或付费墙添加到版位中](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-capacitor)。 </details> ## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被整合到流程和付费墙中,让你能够在移动应用的特定版位中展示它们。 要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 你唯一需要硬编码的是版位 ID。流程是远程配置的,因此产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果今天流程返回两个产品,明天返回三个,则无需修改代码即可全部展示。 ::: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested flow } catch (error) { console.error('Failed to fetch flow:', error); } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络状况不稳定,可以考虑使用 `'return_cache_data_else_load'`——在缓存存在时优先返回缓存数据。这种方式下用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储流程和付费墙:上述会定期更新的缓存,以及[备用付费墙](capacitor-use-fallback-paywalls)。我们还使用 CDN 来加快流程和付费墙的加载速度,并在 CDN 不可用时启用独立的备用服务器。该系统旨在确保您始终能获取最新版本的流程,同时在网络条件受限时也能保证可靠性。</p> | | **params.loadTimeoutMs** | <p>可选</p><p>默认值:5000 ms</p> | <p>该值限制此方法的超时时间(毫秒)。若超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeoutMs` 中指定的值,因为该操作在底层可能包含多个不同的请求。</p> | :::note 在 v4 中,`getFlow` 不再接受 `locale` 参数。对于自定义付费墙,所有可用的语言设置都会通过流程的远程配置(`flow.remoteConfigs`)返回——请从中选取与用户设备或应用设置相匹配的语言。 ::: 不要在代码中硬编码产品 ID!由于流程是远程配置的,可用产品的数量以及特殊优惠(例如免费试用)都可能随时间变化。请确保你的代码能够处理这些情况。 例如,如果最初获取到 2 个产品,你的应用应显示这 2 个产品;但如果后来获取到 3 个产品,你的应用无需修改代码即可显示全部 3 个。唯一需要硬编码的是版位 ID。 返回参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、付费墙变体列表(`paywalls`)以及 `remoteConfigs` 数组(每个已配置的语言环境对应一条记录)。如需获取该流程的产品,请调用 `getPaywallProducts({ flow })`。 | ## 获取产品 \{#fetch-products\} 获取到流程后,你可以查询与其对应的产品数组: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ flow }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` 响应参数: | 参数 | 说明 | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) 对象中的这些属性。以下列出了最常用的属性,完整属性详情请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **标题** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户所选的应用商店国家/地区,而非设备本身的语言环境。 | | **价格** | 要显示本地化价格,请使用 `product.price?.localizedString`。该本地化基于设备的语言环境信息。你也可以通过 `product.price?.amount` 以数字形式获取价格,值以当地货币表示。要获取对应的货币符号,请使用 `product.price?.currencySymbol`。 | | **订阅周期** | 要显示订阅周期(如周、月、年等),请使用 `product.subscription?.localizedSubscriptionPeriod`,该本地化基于设备的语言环境。要以编程方式获取订阅周期,请使用 `product.subscription?.subscriptionPeriod`,从中可访问 `unit` 属性获取周期单位(即 `'day'`、`'week'`、`'month'`、`'year'` 或 `'unknown'`)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `'month'`,`numberOfUnits` 值为 `3`。 | | **新用户优惠** | 要显示表示订阅包含新用户优惠的标签或其他指示器,请查看 `product.subscription?.offer?.phases` 属性。这是一个列表,最多可包含两个折扣阶段:免费试用阶段和优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:字符串类型,取值为 `'free_trial'`、`'pay_as_you_go'`、`'pay_up_front'` 和 `'unknown'`。免费试用类型为 `'free_trial'`。<br/>• `price`:折扣价格(数字类型)。免费试用时该值为 `0`。<br/>• `localizedNumberOfPeriods`:使用设备语言环境本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `'3 days'`。<br/>• `subscriptionPeriod`:你也可以通过此属性获取优惠周期的详细信息,其使用方式与上一节中描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:按用户语言环境格式化的折扣订阅周期。 | ## 使用默认目标受众流程加速流程获取 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流程的获取几乎是即时完成的,无需为此担心。但如果你的版位和目标受众数量较多,且用户网络连接较弱,流程的获取时间可能会比预期长。在这种情况下,你可能希望展示一个默认流程,以确保良好的用户体验,而不是什么都不显示。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程。但需要特别注意的是,推荐的方式是通过 `getFlow` 方法来获取流程,详情请参阅上方的[获取流程信息](fetch-paywalls-and-products-capacitor#fetch-flow-information)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺陷: - **潜在的向后兼容性问题**:如果你需要针对不同的应用版本(当前版本和未来版本)展示不同的流程,可能会遇到挑战。你要么必须设计同时兼容当前(旧版)版本的流程,要么接受使用当前(旧版)的用户可能遇到流程无法渲染的问题。 - **失去精准定向**:所有用户都会看到针对 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些不足之处以换取更快的流程获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-capacitor#fetch-flow-information)介绍的 `getFlow`。 ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested flow } catch (error) { console.error('Failed to fetch default audience flow:', error); } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐这种方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络连接不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时直接返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但加载速度更快,不受网络状况影响。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清除时才会被清空。</p> | </SDKv4> <SDKv3> 在展示远程配置和自定义付费墙之前,你需要先获取相关信息。请注意,本节内容涉及远程配置和自定义付费墙。如需了解如何获取付费墙编辑工具定制的付费墙,请参阅[获取付费墙编辑工具的付费墙及其配置](capacitor-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位](create-placement)。 4. 在你的移动应用中[安装 Adapty SDK](sdk-installation-capacitor)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合。这些跨平台产品被集成到付费墙中,使您能够在特定的移动应用版位中展示它们。 要显示产品,您需要使用 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested paywall } catch (error) { console.error('Failed to fetch paywall:', error); } ``` | 参数 | 是否必填 | 说明 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。此参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及我们建议使用方式的更多信息,请参阅[本地化与语言区域代码](capacitor-localizations-and-locale-codes)。</p> | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时直接返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络连接质量如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。</p> | | **params.loadTimeoutMs** | <p>可选</p><p>默认值:5000 ms</p> | <p>此值限制该方法的超时时间(毫秒)。如果达到超时时间,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的超时时间可能略晚于 `loadTimeoutMs` 中指定的值,因为该操作在底层可能包含多个请求。</p> | **不要硬编码产品 ID。** 您唯一应该硬编码的 ID 是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。您的应用必须动态处理这些变化——如果付费墙今天返回两个产品,明天返回三个,则应在不修改代码的情况下全部显示。 响应参数: | 参数 | 说明 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取产品 \{#fetch-products\} 获得付费墙后,您可以查询与其对应的产品数组: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ paywall }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` 响应参数: | 参数 | 描述 | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 | 在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) 对象中的这些属性。以下列出了最常用的属性,完整属性列表请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 | | **Price** | 要显示本地化的价格,请使用 `product.price?.localizedString`。该本地化基于设备的语言区域信息。您也可以通过 `product.price?.amount` 以数字形式获取价格,该值以本地货币为单位。要获取对应的货币符号,请使用 `product.price?.currencySymbol`。 | | **Subscription Period** | 要显示订阅周期(例如周、月、年等),请使用 `product.subscription?.localizedSubscriptionPeriod`。该本地化基于设备的语言区域设置。要以编程方式获取订阅周期,请使用 `product.subscription?.subscriptionPeriod`。通过该属性可访问 `unit` 属性以获取时间单位(即 `'day'`、`'week'`、`'month'`、`'year'` 或 `'unknown'`)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `'month'`,`numberOfUnits` 属性值为 `3`。 | | **Introductory Offer** | 要显示订阅包含新用户优惠的标识或其他指示器,请查看 `product.subscription?.offer?.phases` 属性。这是一个最多包含两个折扣阶段的列表:免费试用阶段和优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:字符串类型,可能的值为 `'free_trial'`、`'pay_as_you_go'`、`'pay_up_front'` 和 `'unknown'`。免费试用对应 `'free_trial'` 类型。<br/>• `price`:以数字表示的折扣价格。免费试用时该值为 `0`。<br/>• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠的持续时长。例如,三天试用优惠在此字段中显示为 `'3 days'`。<br/>• `subscriptionPeriod`:您也可以通过此属性获取优惠周期的具体详情,其使用方式与上一节中订阅周期的描述相同。<br/>• `localizedSubscriptionPeriod`:以用户语言区域格式化的折扣订阅周期。 | ## 使用默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙几乎可以即时获取,因此您无需担心加速此过程。但是,如果您拥有大量目标受众和付费墙,且用户的网络连接较弱,获取付费墙可能比预期耗时更长。在这种情况下,您可能希望显示默认付费墙,以确保流畅的用户体验,而不是完全不显示付费墙。 为此,您可以使用 `getPaywallForDefaultAudience` 方法,该方法为 **All Users** 目标受众获取指定版位的付费墙。但请务必了解,推荐的方式仍是使用 `getPaywall` 方法获取付费墙,详见上方的[获取付费墙信息](fetch-paywalls-and-products-capacitor#fetch-paywall-information)部分。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在一些显著缺点: - **潜在的向后兼容性问题**:如果您需要为不同版本的应用(当前版本和未来版本)显示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)应用版本的付费墙,要么接受使用当前(旧版)应用版本的用户可能遇到付费墙无法渲染的问题。 - **定向能力丧失**:所有用户都将看到为 **All Users** 目标受众设计的同一付费墙,这意味着您将失去个性化定向能力(包括基于国家/地区、营销归因或您自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请坚持使用[上述](fetch-paywalls-and-products-capacitor#fetch-paywall-information) `getPaywall` 方法。 ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested paywall } catch (error) { console.error('Failed to fetch default audience paywall:', error); } ``` | 参数 | 是否必填 | 说明 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。此参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及我们建议使用方式的更多信息,请参阅[本地化与语言区域代码](capacitor-localizations-and-locale-codes)。</p> | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时直接返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络连接质量如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。</p> | </SDKv3> --- # File: present-remote-config-paywalls-capacitor --- --- title: "在 Capacitor SDK 中渲染远程配置设计的付费墙" description: "了解如何在 Adapty Capacitor SDK 中展示远程配置付费墙,以个性化用户体验。" --- <SDKv4> 如果你通过远程配置自定义了流程,则需要在移动端代码中实现渲染逻辑,才能将其展示给用户。由于远程配置提供了高度灵活性,你可以完全掌控其中包含的内容以及流程视图的呈现方式。我们提供了一个获取远程配置的方法,让你能够自主展示通过远程配置设置的自定义流程。 ## 获取流程远程配置并展示 \{#get-flow-remote-config-and-present-it\} 在 v4 中,每个流程在 `remoteConfigs` 数组中为每种已配置的语言包含一条 `AdaptyRemoteConfig` 条目。选择与用户偏好匹配的语言,然后从其 `data` 中读取所需的值。 ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.['header_text']; } catch (error) { console.error('Failed to fetch flow:', error); } ``` 此时,一旦您获取到所有必要的数据,就可以开始渲染并将其组装成视觉效果出色的页面。请确保设计能够适配各种移动设备的屏幕尺寸和方向,为不同设备上的用户提供流畅且友好的体验。 :::warning 请务必按照以下说明[记录付费墙查看事件](present-remote-config-paywalls-capacitor#track-paywall-view-events),以便 Adapty 分析模块能够为漏斗和 A/B 测试收集相关数据。 ::: 在完成流程展示后,继续设置购买流程。当用户发起购买时,只需调用 `.makePurchase()` 并传入来自流程的产品即可。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](capacitor-making-purchases)。 我们建议[创建一个备用付费墙(即备用付费墙)](capacitor-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,该备用付费墙会自动展示,确保用户在任何情况下都能获得流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 帮助您衡量流程的表现。购买数据会自动收集,但流程浏览事件需要您手动记录,因为只有您知道用户何时看到了流程。 要记录流程浏览事件,只需调用 `.logShowFlow({ flow })`,该事件即会反映在付费墙漏斗和 A/B 测试的数据指标中。 :::important 如果你通过 [Flow Builder](adapty-flow-builder) 或 [Paywall Builder](adapty-paywall-builder) 渲染流程或付费墙,无需调用 `.logShowFlow({ flow })`。Adapty 会在这些情况下自动追踪展示次数。 ::: ```typescript showLineNumbers await adapty.logShowFlow({ flow }); ``` 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:-------------------------------------------------------------------------------------| | **flow** | 必填 | 通过 `adapty.getFlow({ placementId })` 获取的 `AdaptyFlow` 对象。 | </SDKv4> <SDKv3> 如果你通过远程配置自定义了付费墙,需要在移动应用代码中自行实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,付费墙视图的内容和样式都取决于你的实现。我们提供了获取远程配置的方法,让你能够自主展示通过远程配置设置的自定义付费墙。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); const headerText = paywall.remoteConfig?.data?.['header_text']; } catch (error) { console.error('Failed to fetch paywall:', error); } ``` 此时,一旦获取到所有必要的数据,就可以开始渲染并将其组合成美观的页面。请确保设计能够适配不同尺寸的手机屏幕和屏幕方向,在各类设备上提供流畅、友好的用户体验。 :::warning 请务必按照下文说明[记录付费墙展示事件](present-remote-config-paywalls-capacitor#track-paywall-view-events-1),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 展示付费墙完成后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](capacitor-making-purchases)。 我们建议[创建一个名为备用付费墙的备份付费墙](capacitor-use-fallback-paywalls)。当用户没有网络连接或没有可用缓存时,将向其展示该备份付费墙,确保即使在这些情况下也能提供流畅的体验。 ## 记录付费墙查看事件 \{#track-paywall-view-events\} Adapty 可帮助您衡量付费墙的表现。虽然我们会自动收集购买数据,但记录付费墙查看事件需要您的配合,因为只有您才知道用户何时查看了付费墙。 要记录付费墙查看事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是在[付费墙编辑工具](adapty-paywall-builder)中创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```typescript showLineNumbers try { await adapty.logShowPaywall({ paywall }); } catch (error) { console.error('Failed to log paywall view:', error); } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- | :-------------------------------------------------------------------------------------------------------- | | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) 对象。 | </SDKv3> --- # File: capacitor-making-purchases --- --- title: "在 Capacitor SDK 中进行应用内购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙,是向用户提供高级内容或服务访问权限的关键步骤。不过,如果你使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,仅仅展示付费墙本身就足以支持购买流程。 如果你没有使用付费墙编辑工具,则必须调用 `.makePurchase()` 方法来完成购买并解锁目标内容。该方法是用户与付费墙交互并完成交易的入口。 如果付费墙针对用户想要购买的产品设置了有效的促销活动,Adapty 会在购买时自动应用该优惠。 请确保你已[完成初始配置](quickstart),不要跳过任何步骤。否则我们将无法验证购买。 ## 进行购买 \{#make-purchase\} :::note **正在使用[付费墙编辑工具](adapty-paywall-builder)?** 购买流程会自动处理——可以跳过此步骤。 **需要分步指引?** 请查看[快速入门指南](capacitor-implement-paywalls-manually),其中包含完整的端到端实现说明。 ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('User is now subscribed!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------| | **product** | 必填 | 通过 `getPaywallProducts` 从流程中获取的 [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) 对象。 | 响应参数: | 参数 | 描述 | |---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **result** | 一个 [`AdaptyPurchaseResult`](https://capacitor.adapty.io/types/adaptypurchaseresult) 对象,包含 `type` 字段(表示购买结果:`'success'`、`'user_cancelled'` 或 `'pending'`)以及 `profile` 字段(购买成功时包含更新后的 [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile))。 | ## 购买时更改订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而非续订当前订阅时,具体行为取决于所使用的应用商店: - 对于 App Store,订阅会在订阅组内自动更新。如果用户在已有某个订阅组的订阅的情况下,又购买了另一个订阅组的订阅,则两个订阅将同时处于激活状态。 - 对于 Google Play,订阅不会自动更新。您需要按照以下说明在移动应用代码中手动处理切换逻辑。 要在 Android 上将订阅替换为另一个订阅,请在调用 `.makePurchase()` 方法时传入额外参数: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product, params: { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('Subscription updated successfully!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` 额外的请求参数: | 参数 | 是否必填 | 描述 | | :--------- | :------- | :----------------------------------------------------------- | | **params** | 可选 | 一个 [`MakePurchaseParamsInput`](https://capacitor.adapty.io/types/makepurchaseparamsinput) 类型的对象,包含特定平台的购买参数。 | `MakePurchaseParamsInput` 结构包含: ```typescript { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } ``` 您可以在 Google 开发者文档中了解更多关于订阅和替换模式的内容: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 对替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更仅在当前订阅计费周期结束后才会生效。 ### 管理预付费方案(Android) \{#manage-prepaid-plans-android\} 如果您的应用用户可以购买[预付费方案](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月有效的非续期订阅),您可以为预付费方案启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。 ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { pendingPrepaidPlansEnabled: true, }, } }); ``` ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 在应用中展示优惠码兑换页面: ```typescript showLineNumbers try { await adapty.presentCodeRedemptionSheet(); } catch (error) { console.error('Failed to present code redemption sheet:', error); } ``` :::danger 根据我们的观察,部分应用中的优惠码兑换页面可能不够稳定。我们建议直接将用户跳转到 App Store。 为此,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: capacitor-restore-purchase --- --- title: "在移动应用中使用 Capacitor SDK 恢复购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 在 iOS 和 Android 中恢复购买是一项功能,允许用户重新获得对先前购买内容(例如订阅或应用内购买)的访问权限,而无需再次付费。此功能对于那些可能已卸载并重新安装应用,或切换到新设备并希望访问先前购买内容而无需再次付款的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买将自动恢复,无需您编写额外代码。如果您属于这种情况,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Restore access to paid features console.log('Access restored successfully!'); } else { console.log('No active subscriptions found'); } } catch (error) { console.error('Failed to restore purchases:', error); } ``` 响应参数: | 参数 | 描述 | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | 一个 [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile) 对象。该模型包含有关访问等级、订阅和非订阅购买的信息。请检查**访问等级状态**以确定用户是否有权访问应用。 | --- # File: implement-observer-mode-capacitor --- --- title: "在 Capacitor SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以跟踪 Capacitor SDK 中的用户订阅事件。" --- 如果你已有自己的购买基础设施,暂时不打算完全切换到 Adapty,可以考虑使用[观察者模式](observer-vs-full-mode)。在基础形态下,观察者模式提供高级分析功能,并可与归因和分析系统无缝集成。 如果这满足您的需求,您只需: 1. 在配置 Adapty SDK 时,将 `observerMode` 参数设置为 `true` 以启用该功能。请参阅 [Capacitor](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk) 的配置说明。 2. 将现有购买基础设施中的交易[上报至 Adapty](report-transactions-observer-mode-capacitor)。 :::tip 在 SDK v4 中,你也可以在观察者模式下展示 Adapty 渲染的流程和付费墙:当用户点击购买或恢复按钮时,SDK 会将该操作移交给你的代码,由你自行执行购买或恢复逻辑。详见[在观察者模式下展示流程](capacitor-present-flows-in-observer-mode)。 ::: ### 观察者模式设置 \{#observer-mode-setup\} 如果你自行处理购买和订阅状态,仅使用 Adapty 发送订阅事件和分析数据,请开启观察者模式。 :::important 在观察者模式下,Adapty SDK 不会关闭任何交易,请确保你自行处理这一逻辑。 ::: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { observerMode: true // Enable observer mode } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | **observerMode** | 一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在观察者模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还想使用 Adapty 的付费墙和 A/B 测试功能,也可以实现——但在观察者模式下需要一些额外设置。除上述步骤外,您还需要: 1. 按照常规方式展示[远程配置付费墙](present-remote-config-paywalls-capacitor)。 2. 将付费墙与购买交易[关联](report-transactions-observer-mode-capacitor)。 --- # File: report-transactions-observer-mode-capacitor --- --- title: "在 Capacitor SDK 中以观察者模式上报交易" description: "在 Capacitor SDK 的 Adapty 观察者模式中上报购买交易,以获取用户洞察并跟踪收入。" --- 在观察者模式下,Adapty SDK 无法自动跟踪通过您现有购买系统完成的购买行为。您需要手动从应用商店上报交易。在发布应用**之前**完成此设置至关重要,否则可能导致分析数据出错。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别它。 :::warning **请勿跳过交易上报!** 如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送至集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时包含 `variationId`。这会将购买行为与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction({ transactionId: 'your_transaction_id', variationId: variationId }); } catch (error) { console.error('Failed to report transaction:', error); } ``` 参数说明: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | **transactionId** | 必填 | <ul><li>iOS:交易的标识符。</li><li>Android:购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | **variationId** | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://capacitor.adapty.io/interfaces/adaptypaywall) 对象的 `variationId` 属性获取该值。 | --- # File: capacitor-user --- --- title: "用户与访问" description: "了解如何在 Capacitor 应用中通过 Adapty SDK 管理用户和访问等级。" --- <CustomDocCardList /> --- # File: capacitor-identifying-users --- --- title: "在 Capacitor SDK 中识别用户" description: "了解如何使用 Adapty SDK 在 Capacitor 应用中识别用户。" --- Adapty 会为每位用户创建一个内部用户画像 ID。但如果你有自己的认证系统,建议设置你自己的 Customer User ID。你可以在[用户画像](profiles-crm)页面通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 会同步到所有集成。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果您在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID' } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` ### 在配置后设置用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以在任意时刻通过 `.identify()` 方法进行设置。最常见的使用场景是在注册或登录之后,即用户从匿名状态切换为已认证状态时。 ```typescript showLineNumbers try { await adapty.identify({ customerUserId: 'YOUR_USER_ID' }); console.log('User identified successfully'); } catch (error) { console.error('Failed to identify user:', error); } ``` 请求参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **customerUserId** | 必填 | 字符串类型的用户标识符。 | :::warning 重新提交重要用户数据 在某些情况下,例如用户再次登录账户时,Adapty 服务器已经存储了该用户的信息。此时,Adapty SDK 会自动切换到新用户。如果你之前向匿名用户传入了任何数据(例如自定义属性或来自第三方网络的归因数据),需要为已识别用户重新提交这些数据。 同样需要注意,在识别用户身份后,应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 登出与登录 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法登出用户: ```typescript showLineNumbers try { await adapty.logout(); console.log('User logged out successfully'); } catch (error) { console.error('Failed to logout user:', error); } ``` 之后您可以使用 `.identify()` 方法重新登录用户。 ## 分配 `appAccountToken`(iOS)\{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,用于将 App Store 交易与你的内部用户身份关联起来。 StoreKit 会将此 token 与每笔交易绑定,这样你的后端就能将 App Store 数据与用户对应匹配。 请为每位用户生成一个稳定的 UUID,并在同一账号的不同设备上复用它。 这样可以确保购买记录和 App Store 通知始终正确关联。 您可以通过两种方式设置令牌——在 SDK 激活时,或在识别用户时。 :::important 您必须始终将 `appAccountToken` 与 `customerUserId` 一起传递。 如果仅传递令牌而不传递用户 ID,该令牌将不会包含在交易中。 ::: ```typescript showLineNumbers // 在配置时: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID', ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, } }); // 或在识别用户时 await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { ios: { appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN' }, } }); ``` ### 设置混淆账户 ID(Android)\{#set-obfuscated-account-ids-android\} Google Play 在某些场景下需要使用混淆账户 ID,以保护用户隐私和安全。这些 ID 可以帮助 Google Play 识别购买记录,同时保持用户信息匿名,在防欺诈和数据分析方面尤为重要。 如果你的应用处理敏感用户数据,或需要遵守特定的隐私法规,可能就需要设置这些 ID。混淆 ID 让 Google Play 能够追踪购买行为,而无需暴露真实的用户标识符。 ```typescript showLineNumbers // 在配置时: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); // 或在识别用户时 await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); ``` ## 跨设备用户识别 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: capacitor-setting-user-attributes --- --- title: "在 Capacitor SDK 中设置用户属性" description: "了解如何使用 Adapty SDK 更新 Capacitor 应用中的用户属性和用户画像数据。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。之后,您可以使用这些属性创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```typescript showLineNumbers const params = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); console.log('Profile updated successfully'); } catch (error) { console.error('Failed to update profile:', error); } ``` 请注意,您之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters` 允许的键及其值如下所示: | 键 | 值 | |---|-----| | **email** | 字符串 | | **phoneNumber** | 字符串 | | **firstName** | 字符串 | | **lastName** | 字符串 | | **gender** | 枚举,允许的值为:`'female'`、`'male'`、`'other'` | | **birthday** | ISO 格式的日期字符串 | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性。这些属性通常与您的应用使用情况相关。例如,对于健身应用,可能是每周锻炼次数;对于语言学习应用,可能是用户的知识水平等。您可以在市场细分中使用这些属性来创建有针对性的付费墙和优惠,也可以在分析中用它们来找出哪些产品指标对收益影响最大。 ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); console.log('Custom attributes updated successfully'); } catch (error) { console.error('Failed to update custom attributes:', error); } ``` 要删除已有的键,请将其值设置为 `null`: ```typescript showLineNumbers try { // to remove keys, pass null as their values await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); console.log('Custom attributes removed successfully'); } catch (error) { console.error('Failed to remove custom attributes:', error); } ``` 有时您需要了解之前已设置了哪些自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能并非最新,因为用户属性可以随时从不同设备发送,所以服务器上的属性可能在最后一次同步后已发生变化。 ::: ### 限制 \{#limits\} - 每位用户最多 30 个自定义属性 - 键名最长 30 个字符,键名可包含字母数字字符及以下任意字符:`_`、`-`、`.` - 值可以是字符串或浮点数,最多 50 个字符。 --- # File: capacitor-listen-subscription-changes --- --- title: "在 Capacitor SDK 中检查订阅状态" description: "在 Adapty 中追踪和管理用户订阅状态,提升 Capacitor 应用的用户留存率。" --- 使用 Adapty,订阅状态的追踪变得轻而易举。你无需在代码中手动填入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可快速确认其订阅状态。 <details> <summary>开始检查订阅状态前的准备工作(点击展开)</summary> - iOS 请配置 [App Store Server Notifications](enable-app-store-server-notifications) - Android 请配置 [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile) 对象的属性。我们建议在应用启动时获取用户画像,例如在[识别用户](capacitor-identifying-users#setting-customer-user-id-on-configuration)时,并在发生变更时及时更新。这样,你就可以直接使用已有的用户画像对象,而无需反复请求。 如需在用户画像更新时收到通知,请按照下方[监听用户画像更新(包括访问等级)](capacitor-listen-subscription-changes)章节的说明来监听用户画像变更。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 要从服务器获取访问等级,请使用 `.getProfile()` 方法: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); console.log('Profile retrieved successfully'); } catch (error) { console.error('Failed to get profile:', error); } ``` 响应参数: | 参数 | 描述 | | --------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | 一个 [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile) 对象。通常,你只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级权限。`.getProfile` 方法始终会尝试请求 API,因此返回的结果是最新的。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。同样需要注意的是,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的实时性。 | `.getProfile()` 方法可以获取用户画像,从中你可以得到访问等级的状态。一个应用可以设置多个访问等级。例如,如果你有一个新闻应用,并分别销售不同主题的订阅,你可以创建 "sports" 和 "science" 两个访问等级。但在大多数情况下,你只需要一个访问等级,这时直接使用默认的 "premium" 访问等级即可。 以下是检查默认 "premium" 访问等级的示例: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { // Grant access to premium features console.log('User has premium access'); } else { console.log('User does not have premium access'); } } catch (error) { console.error('Failed to check subscription status:', error); } ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅状态发生变化时,Adapty 就会触发一个事件。 要接收来自 Adapty 的消息,你需要进行一些额外配置: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addListener('onLatestProfileLoad', (data) => { const profile = data.profile; const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { console.log('Subscription status updated: User has premium access'); } else { console.log('Subscription status updated: User does not have premium access'); } }); ``` Adapty 也会在应用启动时触发一次事件,此时传递的是缓存的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 内置了缓存机制,用于存储用户画像的订阅状态。这样一来,即使服务器暂时不可用,也可以通过缓存数据获取用户的订阅状态信息。 但需要注意的是,无法直接从缓存中请求数据。SDK 每分钟会定期向服务器查询,检查用户画像是否有任何更新或变更。如有修改(例如新交易或其他更新),这些变更将同步到缓存数据中,以确保其与服务器保持一致。 --- # File: capacitor-deal-with-att --- --- title: "在 Capacitor SDK 中处理 ATT" description: "开始在 Capacitor 上使用 Adapty,以简化订阅设置和管理。" --- 如果您的应用程序使用了 AppTrackingTransparency 框架并向用户显示应用跟踪授权请求,则应将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 ```typescript showLineNumbers try { await adapty.updateProfile({ appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); console.log('ATT status updated successfully'); } catch (error) { console.error('Failed to update ATT status:', error); } ``` :::warning 我们强烈建议您在该值发生变化时尽早发送,只有这样,数据才能及时传送到您已配置的集成服务中。 ::: --- # File: capacitor-onboardings --- --- title: "用户引导" description: "了解如何在 Capacitor 应用中使用 Adapty SDK 处理用户引导。" --- :::warning **用户引导功能在 SDK v4 中已弃用,将在未来版本中移除。** 该功能不再接受修复或改进。请改用[流程](capacitor-get-pb-paywalls):与运行在 WebView 中的用户引导不同,流程在设备上原生渲染——带来更流畅的动画、一致的原生外观与体验、更快的加载速度,以及无 WebView 运行时依赖。请参阅[获取流程与付费墙](capacitor-get-pb-paywalls)和[展示流程与付费墙](capacitor-present-paywalls)以开始使用。 ::: <CustomDocCardList /> --- # File: capacitor-get-onboardings --- --- title: "在 Capacitor SDK 中获取用户引导" description: "了解如何在 Adapty 的 Capacitor 中检索用户引导。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](capacitor-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画、一致的原生外观体验、更快的加载速度,且无需 WebView 运行时依赖。请参阅 [获取 flows 与付费墙](capacitor-get-pb-paywalls) 和 [展示 flows 与付费墙](capacitor-present-paywalls) 以开始使用。 ::: 在 Adapty 看板中[使用编辑工具设计好用户引导的视觉部分](design-onboarding)之后,您可以在 Capacitor 应用中展示它。这一过程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。 在开始之前,请确保: 1. 您已[创建了用户引导](create-onboarding)。 2. 您已将用户引导添加到[版位](placements)中。 ## 获取用户引导 \{#fetch-onboarding\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器形式存储,其中包含您的应用需要获取并展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(如答题或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。 为了获得最佳性能,请尽早获取用户引导配置,以便在向用户展示之前有充足的时间下载图片。 要获取用户引导,请使用 `getOnboarding` 方法: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboarding({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); console.log('Onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch onboarding:', error); } ``` 然后,调用 `createOnboardingView` 方法来创建视图实例。 :::warning `createOnboardingView` 方法的返回结果只能使用一次。如果需要再次使用,请重新调用 `createOnboardingView` 方法。 ::: ```typescript showLineNumbers if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); console.log('Onboarding view created successfully'); } catch (error) { console.error('Failed to create onboarding view:', error); } } else { // Use your custom logic console.log('Onboarding does not have view configuration'); } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及推荐用法的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐此选项,因为它可确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户网络不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时优先返回缓存数据。这样用户获得的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清除时才会被清空。</p> | | **params.loadTimeoutMs** | <p>可选</p><p>默认值:5000 毫秒</p> | <p>该值限制此方法的超时时间(以毫秒为单位)。如果达到超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略长于 `loadTimeoutMs` 中指定的值,因为该操作在底层可能包含多个请求。</p> | 响应参数: | 参数 | 描述 | |:----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding** | 一个 [`AdaptyOnboarding`](https://capacitor.adapty.io/interfaces/adaptyonboarding) 对象,包含:用户引导标识符和配置、远程配置以及其他若干属性。 | ## 通过默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常情况下,用户引导几乎可以即时获取,因此您无需担心加速此过程。但是,如果您有大量目标受众和用户引导,且用户网络较差,获取用户引导可能需要比预期更长的时间。在这种情况下,您可能希望显示一个默认用户引导,以确保流畅的用户体验,而不是不显示任何内容。 为解决此问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法会获取指定版位中**所有用户**目标受众的用户引导。但请务必了解,推荐的做法是使用 `getOnboarding` 方法获取用户引导,详见上方[获取用户引导](#fetch-onboarding)章节。 :::warning 建议使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者有以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要设计向后兼容的界面,否则旧版本可能显示不正常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向投放。 如果更快的获取速度对您的使用场景而言优于上述缺点,请按下方示例使用 `getOnboardingForDefaultAudience`。否则,请按[上方](#fetch-onboarding)描述使用 `getOnboarding`。 ::: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboardingForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); console.log('Default audience onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch default audience onboarding:', error); } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及推荐用法的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **params.fetchPolicy** | <p>可选</p><p>默认值:`'reload_revalidating_cache_data'`</p> | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐此选项,因为它可确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户网络不稳定,可以考虑使用 `'return_cache_data_else_load'`,在缓存存在时优先返回缓存数据。这样用户获得的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清除时才会被清空。</p> | --- # File: capacitor-present-onboardings --- --- title: "在 Capacitor SDK 中展示用户引导" description: "了解如何在 Capacitor 上展示用户引导,以提升转化率和收入。" --- :::warning **用户引导功能已在 SDK v4 中废弃,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [流程](capacitor-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上进行原生渲染,带来更流畅的动画效果、一致的原生外观体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅 [获取流程与付费墙](capacitor-get-pb-paywalls) 和 [展示流程与付费墙](capacitor-present-paywalls) 以开始使用。 ::: 如果你已通过编辑工具自定义了用户引导,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示。这类用户引导已经同时包含了展示内容和展示方式。 在开始之前,请确认: 1. 你已[创建用户引导](create-onboarding)。 2. 你已将用户引导添加到[版位](placements)。 ## 展示用户引导 \{#present-onboarding\} 要展示用户引导,请在 `createOnboardingView` 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示用户引导,请重新调用 `createOnboardingView` 来创建新的 `view` 实例。 :::warning 重复使用同一个 `view` 而不重新创建,可能会导致报错。 ::: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onClose: (actionId, meta) => { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom: (actionId, meta) => { console.log('Custom action:', actionId); return false; // Don't close the onboarding } }); await view.present(); console.log('Onboarding presented successfully'); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置用户引导在 iOS 上的展示方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 值。 ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` ## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接的打开方式需要 Adapty SDK v3.15 及以上版本。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开。这种方式可以让用户无需切换应用即可浏览网页,从而提供流畅的使用体验。 如果你希望改用外部浏览器打开链接,可以将 `openIn` 参数设置为 `browser_out_app` 来自定义此行为: ```typescript showLineNumbers await view.present({ openIn: 'browser_out_app' }); // default — browser_in_app ``` ## 后续步骤 \{#next-steps\} 展示用户引导后,您需要[处理用户交互和事件](capacitor-handling-onboarding-events)。了解如何处理用户引导事件,以响应用户操作并跟踪分析数据。 --- # File: capacitor-handling-onboarding-events --- --- title: "在 Capacitor SDK 中处理用户引导事件" description: "使用 Adapty 处理 Capacitor 中与用户引导相关的事件。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [流程](capacitor-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染,带来更流畅的动画、一致的原生外观与体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取流程和付费墙](capacitor-get-pb-paywalls) 和 [展示流程和付费墙](capacitor-present-paywalls) 开始使用。 ::: 使用构建工具配置的用户引导会生成相应事件,供应用程序响应。使用 `setEventHandlers` 方法来处理独立屏幕展示的这些事件。 开始之前,请确认: 1. 您已[创建用户引导](create-onboarding)。 2. 您已将用户引导添加到[版位](placements)。 ## 设置事件处理器 \{#set-up-event-handlers\} 要处理用户引导的事件,请使用 `view.setEventHandlers` 方法: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event); }, onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom(actionId, meta) { console.log('Custom action:', actionId); return false; // Don't close the onboarding }, onPaywall(actionId, meta) { console.log('Paywall action:', actionId); view.dismiss().then(() => { openPaywall(actionId); }); }, onStateUpdated(action, meta) { console.log('State updated:', action); }, onFinishedLoading(meta) { console.log('Onboarding finished loading'); }, onError(error) { console.error('Onboarding error:', error); }, }); await view.present(); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## 事件类型 \{#event-types\} 以下部分描述了您可以处理的不同类型的事件。 ### 处理自定义操作 \{#handle-custom-actions\} 在编辑工具中,你可以为按钮添加**自定义**操作并为其指定一个 ID。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 之后,您可以在代码中使用这个 ID,并将其作为自定义动作来处理。例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,事件处理程序将被触发,并附带与编辑工具中 **Action ID** 对应的 `actionId` 参数。您可以自定义 ID,例如 "allowNotifications"。 ```typescript showLineNumbers view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': console.log('Login action triggered'); break; case 'allow_notifications': console.log('Allow notifications action triggered'); break; } return false; // Don't close the onboarding }, }); ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### 完成用户引导加载 \{#finishing-loading-onboarding\} 当用户引导完成加载时,将触发以下事件: ```typescript showLineNumbers view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了 **Close** 操作的按钮时,用户引导即被视为已关闭。 <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,当用户关闭用户引导时,你需要自行处理后续逻辑。例如,你需要停止显示用户引导界面本身。 ::: ```typescript showLineNumbers view.setEventHandlers({ onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, }); ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### 打开付费墙 \{#opening-a-paywall\} :::tip 如果你希望在用户引导内部打开付费墙,可以处理此事件。如果你希望在付费墙关闭后再打开一个付费墙,有一种更直接的方式——处理关闭动作,然后直接打开付费墙,无需依赖事件数据。 ::: 在用户引导中使用付费墙最流畅的方式,是将动作 ID 设置为与付费墙版位 ID 相同。 :::note 请注意,在 iOS 上,同一时间只能显示一个视图(付费墙或用户引导)。如果你在用户引导上方展示付费墙,将无法以编程方式控制后台的用户引导。尝试关闭用户引导时,实际上会关闭付费墙,导致用户引导依然可见。为避免此问题,请在展示付费墙之前,始终先关闭用户引导视图。 ::: ```typescript showLineNumbers view.setEventHandlers({ onPaywall(actionId, meta) { // 在显示付费墙之前关闭用户引导 view.dismiss().then(() => { openPaywall(actionId); }); }, }); async function openPaywall(placementId: string) { // 在此实现您的付费墙打开逻辑 } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### 跟踪导航 \{#tracking-navigation\} 在用户引导流程中,当各类与导航相关的事件发生时,你会收到相应的分析事件: ```typescript showLineNumbers view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event.type, meta.onboardingId); }, }); ``` `event` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `onboardingStarted` | 用户引导加载完成时触发 | | `screenPresented` | 任意屏幕显示时触发 | | `screenCompleted` | 屏幕完成时触发。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的响应)。当用户执行任何退出屏幕的操作时触发。 | | `secondScreenPresented` | 第二个屏幕显示时触发 | | `userEmailCollected` | 通过输入框收集到用户邮箱时触发 | | `onboardingCompleted` | 当用户到达 ID 为 `final` 的屏幕时触发。如需使用此事件,请[为最后一个屏幕分配 `final` ID](design-onboarding)。 | | `unknown` | 用于任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据)| 每个事件都包含 `meta` 信息,其中含有以下字段: | 字段 | 描述 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前屏幕的标识符 | | `screenIndex` | 当前屏幕在流程中的位置 | | `screensTotal` | 流程中的屏幕总数 | <Details> <summary>事件示例(点击展开)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: capacitor-onboarding-input --- --- title: "在 Capacitor SDK 中处理用户引导数据" description: "在 Capacitor 应用中通过 Adapty SDK 保存并使用用户引导数据。" --- :::warning **用户引导功能已在 SDK v4 中废弃,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](capacitor-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画、一致的原生外观体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取 flows 和付费墙](capacitor-get-pb-paywalls) 和 [展示 flows 和付费墙](capacitor-present-paywalls) 以开始使用。 ::: 当用户回答测验问题或在输入框中输入数据时,`onStateUpdated` 方法将被调用。您可以在代码中保存或处理字段类型。 例如: ```typescript view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); ``` 在[此处](https://capacitor.adapty.io/types/onboardingstateupdatedaction)查看 action 的格式。 <Details> <summary>已保存数据示例(实际格式可能因实现方式而有所不同)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用户画像数据补充 \{#enrich-user-profiles-with-data\} 如果你希望立即将用户输入的数据与其用户画像关联,避免重复询问相同信息,则需要在处理操作时,将输入数据[更新到用户画像](capacitor-setting-user-attributes)中。 例如,你让用户在 ID 为 `name` 的文本框中输入姓名,并希望将该字段的值设置为用户的名字;同时让用户在 `email` 字段中输入邮箱地址。在你的应用代码中,实现方式如下: ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams: any = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` ### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\} 在用户引导中使用问卷,你还可以根据用户完成用户引导后的答案,为其展示不同的付费墙。 例如,你可以询问用户的运动经验,然后向不同用户群体展示不同的 CTA 和产品。 1. 在用户引导编辑器中[添加问卷](onboarding-quizzes),并为各选项设置有意义的 ID。 2. 根据 ID 处理问卷响应,并为用户[设置自定义属性](capacitor-setting-user-attributes)。 ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams: any = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为每个已创建的市场细分添加[目标受众](audience)。 5. 在应用代码中为该版位[展示付费墙](capacitor-paywalls)。如果您的用户引导中有打开付费墙的按钮,请将付费墙代码实现为[该按钮操作的响应](capacitor-handling-onboarding-events#opening-a-paywall)。 --- # File: capacitor-best-practices --- --- title: "Capacitor SDK 最佳实践" description: "在 Capacitor 中集成 Adapty SDK 的参考模式——调用顺序、错误处理及其他生产就绪规则。" --- <CustomDocCardList /> --- # File: capacitor-sdk-call-order --- --- title: "Capacitor SDK 调用顺序" description: "按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、归因缺失以及偶发的 #2002 错误。" --- `adapty.activate()` 必须先完成,才能调用其他任何 Adapty SDK 方法。在其解析完成之前,SDK 没有任何状态。在 `activate()` 之前或与其并行发出的任何调用都会失败,并返回 [`#2002 notActivated`](capacitor-handle-errors#custom-network-codes)。 如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到 ID 时调用 `adapty.identify()`。在 `identify` 完成之前,不要调用任何用户操作相关的方法。与 `identify` 并发执行的调用要么以 [`#3006 profileWasChanged`](capacitor-handle-errors#custom-network-codes) 错误失败,要么落到激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属不一定会迁移到已认证的用户画像。如果你的应用不需要用户认证,跳过 `identify`,直接使用匿名用户画像即可。 MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循相同的规则。请先初始化它们,并等待其 UID 回调返回后,再调用 `adapty.activate`。否则,MMP ID 会落在一个短暂的匿名用户画像上,不一定能传递到已识别的用户画像。有关 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的操作路径取决于两件事:何时获取用户 ID,以及是否使用 MMP 或分析 SDK。 - **步骤 2 和 5**:所有应用必须执行。先激活 SDK,然后调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要执行。 - **步骤 4**:仅在应用需要用户认证、且在启动后才能获取用户 ID 时需要执行。 如果在应用启动时已知客户用户 ID,可直接将其传入 `activate()`(步骤 2a)。这种方式不会创建匿名用户画像,因此步骤 4 无需执行。 | 步骤 | 调用 | 时机 | 说明 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动,第一步 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `adapty.activate({ apiKey: '...', params: { customerUserId: '...' } })` | 应用启动,步骤 1 之后,若已有 customer user ID | 推荐方式。不会创建匿名用户画像。 | | 2b | `adapty.activate({ apiKey: '...' })` 不传 `customerUserId` | 应用启动,步骤 1 之后,若没有 customer user ID(或从不收集) | Adapty 会创建匿名用户画像。 | | 3 | 为每个 MMP 调用 `adapty.setIntegrationIdentifier({ key: '...', value: '...' })` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,确保 MMP ID 关联到正确的用户画像。 | | 4 | `await adapty.identify({ customerUserId: 'YOUR_USER_ID' })` | 步骤 3 之后(若无 MMP 则在步骤 2 之后),步骤 5 之前——仅适用于走路径 2b 且需要鉴权的情况 | 必须使用 `await`。在 `identify` 执行期间并发调用会产生 `#3006 profileWasChanged` 错误。 | | 5 | `getPaywall`(SDK v4 中为 `getFlow`)、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 若调用了 `identify` 则在步骤 4 之后;否则在步骤 3 之后(若无 MMP 则在步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回归用户丢失高级访问权限、用户画像中缺少 `appsflyer_id`,以及付费墙被错误的目标受众匹配返回。 ::: ## Web2app 与网页漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在网页端结账(Stripe、Paddle),之后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与网页端的用户画像关联。如果你能在应用启动前(通过授权流程或安装来源追踪)获取到 customer user ID,请直接将其传入 `activate()`。否则,在调用 `identify({ customerUserId: 'YOUR_USER_ID' })` 并执行 `restorePurchases` 之前,网页端的购买记录在设备上将不可见。 关于每次网页结账需要发送的元数据,请参阅: - [Stripe](stripe) - [Paddle](paddle) --- # File: capacitor-optimize-paywall-fetching --- --- title: "在 Capacitor SDK 中优化付费墙获取" description: "可靠地获取 Adapty 付费墙:Capacitor 的时机、缓存与备用方案。" --- 在 Capacitor 中可靠地获取付费墙需要做到三点:快速渲染、返回针对目标受众的付费墙,以及在网络较慢时优雅降级。以下规则涵盖了实现这些目标所需的时机、缓存和备用方案。 :::tip 以下规则要求 `adapty.activate()` 和 `adapty.identify()` 均已完成。详见 [Capacitor SDK 调用顺序](capacitor-sdk-call-order)。 ::: 以下建议使用 v3 方法名。在 SDK v4 中,`getPaywall` 已重命名为 `getFlow`(参见[迁移指南](migration-to-capacitor-sdk-v4))——所有规则同样适用。 ## 规则与注意事项 \{#rules-and-pitfalls\} | 建议做法 | 不建议做法 | 原因 | |---|---|---| | 获取即将展示的版位。 | 在启动时并发预取所有版位。 | 批量预取会阻塞主线程,导致启动时出现黑屏。 | | 在归因有机会解析后再调用 `getPaywall`,例如在 `activate` 之后等待 1–2 秒,或等 `onLatestProfileLoad` 监听器触发后再调用。 | 在 `App.tsx` 的应用启动阶段调用 `getPaywall`。 | 此时归因尚未到达,付费墙会基于默认目标受众进行解析,悄无声息地绕过市场细分和 ASA 个性化设置。 | | 设置 `loadTimeoutMs`,并为每个版位配置[备用付费墙](fallback-paywalls)。 | 无限等待 `getPaywall` 返回。 | 没有超时限制时,网络条件差的用户会看到空白屏幕直到网络恢复——或者直接关闭应用。 | 请参阅[获取付费墙和产品](fetch-paywalls-and-products-capacitor)了解 `fetchPolicy` 和 `loadTimeoutMs` 参数说明,以及[版位](placements)了解如何选择合适的版位。 ## 针对弱网环境的优化 \{#tune-for-poor-connectivity\} 对于网络状况持续较差的市场(如农村地区、交通途中、受路由影响的地区): - 除首次请求外,所有请求均设置 `fetchPolicy: 'return_cache_data_else_load'`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeoutMs` 设置为 3000–5000 毫秒,超时后接受备用付费墙。 - 不要让付费墙的展示依赖于 `adapty.getProfile()` 的结果。将 `getPaywall` 独立调用,避免因用户画像加载缓慢而阻塞界面。 --- # File: capacitor-show-aa-targeted-paywall --- --- title: "在 Capacitor SDK 中首次启动时展示 AA 定向付费墙" description: "在 Capacitor 中立即展示付费墙,并在应用 Apple Ads 归因后为 Apple Ads 用户升级付费墙,使用 AdaptyProfile.appliedAttributionSources。" --- Apple Ads (AA) 归因在 `adapty.activate()` 之后异步到达。首次启动时通常尚未落地,因此 `getFlow` 会基于默认目标受众进行解析,Apple Ads 用户会错过你为 AA 市场细分配置的付费墙。与其等待归因落地后再展示付费墙,不如先立即展示一个,待 AA 归因应用后再刷新——这样 Apple Ads 用户能看到精准定向的实验变体,其他用户也无需等待。`AdaptyProfile.appliedAttributionSources` 可告知你 AA 归因何时已应用。 ## 开始之前 \{#before-you-start\} 你需要准备以下内容: - Adapty Capacitor SDK **3.17.1** 或更高版本。 - 在 Adapty 中为应用配置 Apple Ads。请参阅 [Apple Ads](apple-search-ads)。 ## 工作原理 \{#how-it-works\} 调用 `adapty.activate()` 后,SDK 会在后台向 Apple 请求 Apple Ads 归因数据,并将结果转发给 Adapty 后端。当 AA 成为该用户画像的有效归因来源时,SDK 会向你的 `onLatestProfileLoad` 监听器推送更新后的 `AdaptyProfile`,其 `appliedAttributionSources` 数组中会包含 `'apple_search_ads'`。 因此,你可以分两步加载付费墙: 1. 立即调用 `getFlow`。由于尚未应用任何归因,Adapty 将根据默认目标受众解析请求,用户会立即看到一个付费墙。 2. 当 `'apple_search_ads'` 出现时,再次调用 `getFlow`。Adapty 此时会根据 Apple Ads 目标受众解析请求,返回定向付费墙,替换第一个付费墙。 `appliedAttributionSources` 可能为空或不存在,这意味着: - 该用户画像的 Apple Ads 归因尚未处理完成,或 - 根本没有收到任何归因数据。 无论如何,第一步都是安全的 —— Adapty 会根据当前用户画像状态匹配到对应的目标受众来处理请求,通常是默认受众。第二步仅在 `'apple_search_ads'` 出现后才会执行。 :::important 在后续每次启动时,缓存的用户画像已经在 `appliedAttributionSources` 中包含了 `'apple_search_ads'`,因此第一次 `getFlow` 就会直接返回针对 Apple Ads 市场细分的付费墙 —— 不会有第二次请求,也不会有任何可见的变化。两步流程仅在首次启动时有意义,因为此时归因数据仍在处理中。 ::: ## 实现 \{#implementation\} 立即显示付费墙,然后监听 `'apple_search_ads'` 事件,当其到达时刷新付费墙。 1. **激活 SDK。** 请参阅[安装并配置 Capacitor SDK](sdk-installation-capacitor)。 2. **使用 `getFlow` 正常加载并展示付费墙** — 无需等待归因数据。 3. **通过 `adapty.addListener('onLatestProfileLoad', …)` 订阅用户画像更新**,并监听 `'apple_search_ads'`。一旦该数据出现,重新获取付费墙并展示更新后的版本。如果尚未设置监听器,请参阅[监听订阅状态更新](capacitor-check-subscription-status#listen-to-subscription-updates): ```typescript const listener = await adapty.addListener('onLatestProfileLoad', async ({ profile }) => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getFlow({ placementId }); // present the targeted flow in place of the first one }); // Call listener.remove() after the upgrade, or after a timeout (see below). ``` 4. **超时后停止监听。** 大多数用户不会获得 Apple Ads 归因数据,因此请在一段时间后移除监听器,而不是在整个会话期间保持开启。为版位配置[备用付费墙](capacitor-use-fallback-paywalls),确保请求失败时用户始终能看到内容。 ## 完整示例 \{#complete-example\} `onAppleAdsAttribution` 会在苹果广告归因应用后 resolve,或在 `timeoutMs` 超时后 reject。下面的用法会立即加载付费墙,然后在归因数据到达时重新获取——苹果广告用户将看到定向付费墙,如果归因一直未到达,则继续使用第一次加载的付费墙: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let handle: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); handle?.remove(); }; adapty .addListener('onLatestProfileLoad', ({ profile }) => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }) .then(listener => { handle = listener; }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let flow = await adapty.getFlow({ placementId }); onAppleAdsAttribution(30_000) .then(() => adapty.getFlow({ placementId })) .then(updated => { flow = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` 首次启动时,Apple Ads 用户会短暂看到默认付费墙,随后才会被替换。如果你使用付费墙编辑工具展示付费墙,请决定是否可以接受重新展示,或者在付费墙显示之前再应用更新。请根据你愿意等待的时间来调整 `timeoutMs`——通常情况下,归因数据在启动后几秒内就会到达。 如果你的应用已经出于其他目的监听了 `onLatestProfileLoad`(例如[检查订阅状态](capacitor-check-subscription-status#listen-to-subscription-updates)),则无需做任何更改。`adapty.addListener` 支持多个独立监听器,因此新增的监听器不会影响已有的其他监听器。 --- # File: capacitor-test --- --- title: "在 Capacitor SDK 中测试与发布" description: "了解如何使用 Adapty SDK 测试和发布您的 Capacitor 应用。" --- 如果您已经在 Capacitor 应用中集成了 Adapty SDK,您需要测试所有内容是否正确配置,以及购买流程在 iOS 和 Android 平台上是否按预期运行。这包括测试 SDK 集成,以及通过 Apple 沙盒环境和 Google Play 测试环境测试实际的购买流程。 ## 测试你的应用 \{#test-your-app\} 如需全面测试应用内购买,请参阅我们针对各平台的测试指南:[iOS 测试指南](test-purchases-in-sandbox) 和 [Android 测试指南](testing-on-android)。 ## 发布前准备 \{#prepare-for-release\} 在将应用提交到商店之前,请按照[发布检查清单](release-checklist)确认以下事项: - 商店连接和服务器通知已配置完成 - 购买流程正常完成并已上报至 Adapty - 访问等级可正确解锁和恢复 - 隐私和审核要求已满足 --- # File: kids-mode-capacitor --- --- title: "Capacitor SDK 中的儿童模式" description: "轻松启用儿童模式以符合 Apple 和 Google 政策。Capacitor SDK 中不收集 IDFA、GAID 或广告数据。" --- 如果您的 Capacitor 应用面向儿童用户,您必须遵守 [Apple](https://developer.apple.com/kids/) 和 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单步骤即可完成配置,以满足这些政策要求并通过应用商店审核。 ## 需要做什么?\{#whats-required\} 你需要配置 Adapty SDK,禁止收集以下信息: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers)(iOS) - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)(Android) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用 customer user ID。以 `<FirstName.LastName>` 格式设置的用户 ID 与使用邮箱地址一样,都会被视为收集个人数据。在儿童模式下,最佳实践是使用随机或匿名的标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。为此,请前往 [App settings](https://app.adapty.io/settings/general),然后在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 在移动应用代码中进行更新 \{#updates-in-your-mobile-app-code\} 为遵守相关政策,请禁止收集用户的 IDFA、GAID 和 IP 地址: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true } } }); console.log('Adapty activated with Kids Mode enabled'); } catch (error) { console.error('Failed to activate Adapty with Kids Mode:', error); } ``` ### 平台特定配置 \{#platform-specific-configurations\} #### iOS \{#ios\} <SDKv4> 即使在代码中禁用了 IDFA 收集(如上所述),您的构建文件仍然包含 `AdSupport` 和 `AppTrackingTransparency` 框架。App Store 儿童类别不允许使用这些框架。由于 v4 SDK 通过 Swift Package Manager 安装原生 iOS SDK,因此无法通过 Podfile 步骤将其移除。 为符合 Apple 的要求,请将 SDK 自带的 `adapty-kids-mode` 命令添加到您应用的 `postinstall` 中。该命令会启用 SDK 的 `KidsMode` 特性,从而在编译时排除相关代码。每次执行 install 时该命令都会重新生效: ```json showLineNumbers title="package.json" { "scripts": { "postinstall": "adapty-kids-mode" } } ``` 然后重新安装并重新解析 iOS 包,并使用 **Xcode 26** 或更高版本进行构建: ```sh showLineNumbers title="Shell" npm install npx cap sync ios ``` 如需关闭 Kids Mode,运行 `adapty-kids-mode disable` 并再次同步。 </SDKv4> <SDKv3> 如果您在 iOS 上使用 CocoaPods,也可以在原生层面启用 Kids Mode: 1. 更新您的 Podfile: - 如果你**没有** `post_install` 部分,请添加下方的完整代码块。 - 如果你**已有** `post_install` 部分,请将高亮行合并进去。 ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. 运行以下命令以应用更改: ```sh showLineNumbers title="Shell" pod install ``` </SDKv3> #### Android:移除广告 ID 权限 \{#android-remove-the-advertising-id-permission\} 将 `adIdCollectionDisabled: true`(如上所示)设置后,Adapty 将停止收集广告 ID,但 SDK 仍会声明 `AD_ID` 权限。如果你的应用仅面向儿童,且编译目标为 Android 13(API 33)或更高版本,Google Play 将禁止你申请该权限。 在 `<manifest>` 元素中添加以下两项内容: 1. 声明 `tools` 命名空间(Capacitor 的默认清单文件中未包含此命名空间)。 2. 添加一个带有 `tools:node="remove"` 属性的 `<uses-permission>` 条目,用于移除 `AD_ID` 权限。 ```xml showLineNumbers title="android/app/src/main/AndroidManifest.xml" <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> </manifest> ``` ## 后续步骤 \{#next-steps\} 启用儿童模式后,请确保: 1. 全面测试您的应用,确保所有功能正常运行 2. 更新应用的隐私政策,反映已禁用数据收集的情况 3. 提交应用审核时,附上关于儿童模式合规性的清晰说明 有关各平台具体要求的更多信息,请参阅: - [iOS SDK 中的儿童模式](kids-mode):iOS 配置详情 - [Android SDK 中的儿童模式](kids-mode-android):Android 配置详情 --- # File: capacitor-reference --- --- title: "参考文档" description: "Adapty Capacitor SDK 的参考文档。" --- 本页包含 Adapty Capacitor SDK 的参考文档。请选择您需要的主题: - **[SDK 模型](https://capacitor.adapty.io/)** - SDK 使用的数据模型和数据结构 - **[处理错误](capacitor-handle-errors)** - 错误处理与故障排查 --- # File: capacitor-handle-errors --- --- title: "处理 Capacitor SDK 中的错误" description: "处理 Capacitor SDK 中的错误。" --- SDK 返回的每个错误都是一个 `AdaptyError` 实例。示例如下: :::tip **在调试前开启详细日志。** 大多数 `AdaptyError` 都封装了底层的 StoreKit、Play Billing、网络或后端错误。开启详细日志(`adapty.setLogLevel({ logLevel: 'verbose' })`,参见[日志记录](sdk-installation-capacitor#logging))后,这些封装的错误会打印到控制台,通常能直接告诉你真正的原因。无论日志级别如何,`AdaptyError` 上的 `detail` 属性都会被填充——详细日志只是让它显示在控制台中。 ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); // 处理购买结果 if (result.type === 'success') { console.log('购买成功:', result.profile); } else if (result.type === 'user_cancelled') { console.log('用户取消了购买'); } else if (result.type === 'pending') { console.log('购买待处理中'); } } catch (error) { if (error instanceof AdaptyError) { console.error('Adapty 错误:', error.adaptyCode, error.localizedDescription); // 处理特定错误码 switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('此设备不允许应用内购买'); break; case ErrorCodeName.notActivated: console.log('Adapty SDK 未激活'); break; case ErrorCodeName.productPurchaseFailed: console.log('购买失败:', error.detail); break; default: console.log('发生其他错误:', error.detail); } } else { console.error('非 Adapty 错误:', error); } } ``` ## 错误属性 \{#error-properties\} `AdaptyError` 类提供以下属性: | 属性 | 类型 | 描述 | |----------|------|-------------| | `adaptyCode` | `number` | 数字错误代码(例如,`1003` 对应 cantMakePayments) | | `localizedDescription` | `string` | 用户友好的错误消息 | | `detail` | `string \| undefined` | 附加错误详情(可选) | | `message` | `string` | 包含代码和描述的完整错误消息 | ## 错误代码 \{#error-codes\} SDK 导出用于处理错误代码的常量和工具: ### ErrorCodeName 常量 \{#errorcodename-constant\} 将字符串标识符映射到数字代码: ```typescript ErrorCodeName.cantMakePayments // 1003 ErrorCodeName.notActivated // 2002 ErrorCodeName.networkFailed // 2005 ``` ### ErrorCode 常量 \{#errorcode-constant\} 将数字代码映射到字符串标识符: ```typescript ErrorCode[1003] // 'cantMakePayments' ErrorCode[2002] // 'notActivated' ErrorCode[2005] // 'networkFailed' ``` ### 辅助函数 \{#helper-functions\} ```typescript // Get numeric code from string name: getErrorCode('cantMakePayments') // 1003 // Get string name from numeric code: getErrorPrompt(1003) // 'cantMakePayments' ``` ### 比较错误代码 \{#comparing-error-codes\} **重要提示:** `error.adaptyCode` 是一个 **数字**,因此应直接与数字代码进行比较: ```typescript // Option 1: Use ErrorCodeName constant (recommended) ✅ if (error.adaptyCode === ErrorCodeName.cantMakePayments) { console.log('Cannot make payments'); } // Option 2: Compare with numeric literal ✅ if (error.adaptyCode === 1003) { console.log('Cannot make payments'); } // NOT like this ❌ - compares number to string and will never match if (error.adaptyCode === ErrorCode[1003]) { } ``` ## 全局错误处理器 \{#global-error-handler\} 你可以设置一个全局错误处理器来捕获所有 Adapty 错误: ```typescript showLineNumbers // 设置全局错误处理器 AdaptyError.onError = (error: AdaptyError) => { console.error('Global Adapty error:', { code: error.adaptyCode, message: error.localizedDescription, detail: error.detail }); // 全局处理特定错误类型 if (error.adaptyCode === ErrorCodeName.notActivated) { // SDK 未激活 - 可以尝试重新激活 console.log('SDK not activated, attempting to reactivate...'); } }; ``` ## 常见错误处理模式 \{#common-error-handling-patterns\} ### 处理购买错误 \{#handle-purchase-errors\} ```typescript showLineNumbers async function handlePurchase(product: AdaptyPaywallProduct) { try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { console.log('Purchase successful:', result.profile); } else if (result.type === 'user_cancelled') { console.log('User cancelled the purchase'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('In-app purchases not allowed'); break; case ErrorCodeName.productPurchaseFailed: console.log('Purchase failed:', error.detail); break; default: console.error('Purchase error:', error.localizedDescription); } } } } ``` ### 处理网络错误 \{#handle-network-errors\} ```typescript showLineNumbers async function fetchFlow(placementId: string) { try { const flow = await adapty.getFlow({ placementId }); return flow; } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.networkFailed: console.log('Network error, retrying...'); // Implement retry logic break; case ErrorCodeName.serverError: console.log('Server error:', error.detail); break; case ErrorCodeName.notActivated: console.log('SDK not activated'); break; default: console.error('Paywall fetch error:', error.localizedDescription); } } throw error; } } ``` ## 系统 StoreKit 错误码 \{#system-storekit-codes\} | 错误 | 错误码 | 描述 | |-----|----|-----------| | unknown | 0 | 发生了未知或意外的错误。 | | clientInvalid | 1 | 客户端不被允许执行该操作。 | | paymentCancelled | 2 | <p>用户取消了支付请求。</p><p>无需采取额外操作,但从业务逻辑角度,你可以向用户提供折扣或稍后再次提醒。</p> | | paymentInvalid | 3 | 支付参数中有一项未被商店识别。 | | paymentNotAllowed | 4 | <p>用户不被允许进行支付授权。可能的原因:</p><p></p><p>- 该用户所在国家/地区不支持支付。</p><p>- 用户未成年。</p> | | storeProductNotAvailable | 5 | 请求的产品在 App Store 中不存在。请确认该产品在对应国家/地区可用。 | | cloudServicePermissionDenied | 6 | 用户未授权访问云服务信息。 | | cloudServiceNetworkConnectionFailed | 7 | 设备无法连接到网络。 | | cloudServiceRevoked | 8 | 用户已撤销对该云服务的使用权限。 | | privacyAcknowledgementRequired | 9 | 用户尚未确认商店隐私政策。 | | unauthorizedRequestData | 10 | 请求构建有误。 | | invalidOfferIdentifier | 11 | <p>优惠标识符无效。可能的原因:</p><p></p><p>- 你未在 App Store 中设置该标识符对应的优惠。</p><p>- 该优惠已被撤销。</p><p>- 优惠 ID 填写有误。</p> | | invalidSignature | 12 | 支付折扣中的签名无效。请确认你已填写 **In-app purchase Key ID** 字段并上传了 **In-App Purchase Private Key** 文件。详情请参阅 [配置 App Store 集成](app-store-connection-configuration)。 | | missingOfferParams | 13 | <p>Adapty 集成或优惠配置存在问题。</p><p>详情请参阅 [配置 App Store 集成](app-store-connection-configuration) 和 [优惠](offers)。</p> | | invalidOfferPrice | 14 | 你在商店中指定的价格已失效。优惠价格必须低于原价。 | ## 自定义 Android 错误码 \{#custom-android-codes\} | 错误 | 错误码 | 描述 | |-----|----|-----------| | adaptyNotInitialized | 20 | 你需要通过 `Adapty.activate` 方法正确配置 Adapty SDK。了解如何 [在 React Native 中配置](sdk-installation-reactnative)。 | | productNotFound | 22 | 请求购买的产品在商店中不可用。 | | invalidJson | 23 | 付费墙 JSON 格式无效。请在 Adapty 看板中修复它。详情请参阅 [使用远程配置自定义付费墙](customize-paywall-with-remote-config)。 | | currentSubscriptionToUpdateNotFoundInHistory | 24 | 未找到需要续订的原始订阅记录。 | | pendingPurchase | 25 | 购买状态为待处理,而非已完成。详情请参阅 Android 开发者文档中的 [处理待处理交易](https://developer.android.com/google/play/billing/integrate#pending) 页面。 | | billingServiceTimeout | 97 | 请求在 Google Play 响应前已达到最大超时时间。例如,Play Billing Library 调用请求的操作执行出现延迟时可能触发此错误。 | | featureNotSupported | 98 | 当前设备上的 Play Store 不支持该功能。 | | billingServiceDisconnected | 99 | 这是一个致命错误,表示客户端应用与 Google Play Store 服务之间通过 `BillingClient` 建立的连接已断开。 | | billingServiceUnavailable | 102 | 这是一个暂时性错误,表示 Google Play 结算服务当前不可用。大多数情况下,这意味着客户端设备与 Google Play 结算服务之间的网络连接存在问题。 | | billingUnavailable | 103 | <p>购买过程中发生了用户结算错误。常见原因包括:</p><p></p><p>1\. 用户设备上的 Play Store 应用版本过旧。</p><p>2. 用户所在国家/地区不受支持。</p><p>3. 用户为企业用户,且其企业管理员已禁止用户进行购买。</p><p>4. Google Play 无法向用户的支付方式扣款,例如用户的信用卡已过期。</p><p>5. 用户未登录 Play Store 应用。</p> | | developerError | 105 | 这是一个致命错误,表示你正在不正确地使用某个 API。 | | billingError | 106 | 这是一个致命错误,表示 Google Play 内部出现了问题。 | | itemAlreadyOwned | 107 | 该消耗型商品已被购买。 | | itemNotOwned | 108 | 对该商品执行的请求操作失败。 | ## 自定义 StoreKit 错误码 \{#custom-storekit-codes\} | 错误 | 错误码 | 描述 | |-----|----|-----------| | noProductIDsFound | 1000 | <p>付费墙中的所有产品均无法在商店中找到。</p><p>如果遇到此错误,请按以下步骤排查:</p><p></p><p>1. 检查所有产品是否已添加到 Adapty 看板。</p><p>2. 确认应用的 Bundle ID 与 Apple Connect 中的一致。</p><p>3. 核实应用商店中的产品标识符与看板中添加的标识符一致。请注意,标识符中不应包含 Bundle ID,除非商店本身已包含它。</p><p>4. 确认你的 Apple 税务设置中应用的付费状态为有效,税务信息为最新,且证书有效。</p><p>5. 检查应用是否已绑定银行账户,以便具备变现资格。</p><p>6. 检查产品是否在所有地区可用,并确保产品状态为 **"Ready to Submit"**。</p> | | productRequestFailed | 1002 | <p>当前无法获取可用产品。可能的原因:</p><p></p><p>- 尚未创建缓存,同时也没有网络连接。</p> | | cantMakePayments | 1003 | 此设备不允许进行应用内购买。 | | noPurchasesToRestore | 1004 | Google Play 未找到可恢复的购买记录。 | | cantReadReceipt | 1005 | <p>设备上没有有效的收据。这在沙盒测试期间可能出现。</p><p>无需采取额外操作,但从业务逻辑角度,你可以向用户提供折扣或稍后再次提醒。</p> | | productPurchaseFailed | 1006 | 产品购买失败。此错误封装了底层 StoreKit 错误——请读取被封装的错误(或开启详细日志以在控制台查看)以获取实际原因。被封装的错误通常是上表中错误码 0–14 之一,最常见的是 `paymentCancelled`、`paymentInvalid`、`paymentNotAllowed` 或 `invalidOfferPrice`。如果无法确定具体原因,请尝试新建一个[沙盒用户画像](test-purchases-in-sandbox);若问题依然存在,请联系 Apple 支持。 | | refreshReceiptFailed | 1010 | 未收到收据。仅适用于 StoreKit 1。 | | receiveRestoredTransactionsFailed | 1011 | 购买恢复失败。 | ## 自定义网络错误码 \{#custom-network-codes\} | 错误 | 错误码 | 描述 | | :------------------- | :--- | :----------------------------------------------------------- | | notActivated | 2002 | 你需要通过 `Adapty.activate` 方法正确配置 Adapty SDK。了解如何 [在 React Native 中配置](sdk-installation-reactnative)。 | | badRequest | 2003 | 请求无效。 | | serverError | 2004 | 服务器错误。 | | networkFailed | 2005 | 网络请求失败。 | | decodingFailed | 2006 | 响应解码失败。 | | encodingFailed | 2009 | 请求编码失败。 | | analyticsDisabled | 3000 | 由于你已选择退出,我们无法处理分析事件。详情请参阅 [分析集成](analytics-integration)。 | | wrongParam | 3001 | 部分参数不正确:不能为空时传入了空值,或类型有误等。 | | activateOnceError | 3005 | `.activate` 方法只能调用一次。 | | profileWasChanged | 3006 | 操作执行期间用户画像发生了变更。 | | fetchTimeoutError | 3101 | 付费墙未能在规定时间内获取完成。为避免此问题,请 [设置本地备用方案](fetch-paywalls-and-products)。 | | operationInterrupted | 9000 | 该操作被系统中断。 | --- # File: capacitor-sdk-migration-guides --- --- title: "Capacitor SDK 迁移指南" description: "Adapty Capacitor SDK 各版本的迁移指南。" --- 本页包含 Adapty Capacitor SDK 的所有迁移指南。请选择您要迁移的目标版本以查看详细说明: - **[迁移至 v4.0 (beta)](migration-to-capacitor-sdk-v4)** - [**迁移至 v3.16**](migration-to-capacitor-316) --- # File: migration-to-capacitor-sdk-v4 --- --- title: "将 Adapty Capacitor SDK 迁移至 v. 4.0" description: "将 SDK 迁移至 Adapty Capacitor SDK v4.0(测试版),使用流程 API 替换付费墙 API,兼容 Flow Builder 和 Paywall Builder。" --- Adapty Capacitor SDK 4.0(测试版)引入了流程功能,并对付费墙 API 进行了相应重命名。新 API 同时兼容全新的 Flow Builder 和现有的 Paywall Builder,无需在 Adapty 看板侧做任何配置变更。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall({ placementId, locale?, params? })` | `adapty.getFlow({ placementId, params? })` | | `adapty.getPaywallForDefaultAudience({ placementId, locale?, params? })` | `adapty.getFlowForDefaultAudience({ placementId, params? })` | | `adapty.getPaywallProducts({ paywall })` | `adapty.getPaywallProducts({ flow })` | | `adapty.logShowPaywall({ paywall })` | `adapty.logShowFlow({ flow })` | | `AdaptyPaywall` (类型) | `AdaptyFlow` + `AdaptyFlowPaywall` | | `createPaywallView(paywall, params?)` | `createFlowView(flow, params?)` | | `PaywallViewController` | `FlowViewController` | | `EventHandlers` (类型) | `FlowEventHandlers` | | `CreatePaywallViewParamsInput` | `CreateFlowViewParamsInput` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` 保持其名称不变——产品仍属于一个 flow,`getPaywallProducts` 也保持其名称,现在接受一个 `AdaptyFlow` 参数。`getFlow` 和 `getFlowForDefaultAudience` 方法不再接受 `locale` 参数。购买和用户画像相关 API(`makePurchase`、`restorePurchases`、`getProfile`、`identify`、`updateProfile`)以及通过 `setFallback` 设置的备用付费墙均保持不变。视图方法 `present`、`dismiss`、`setEventHandlers`、`showDialog`,以及事件处理器 `onCloseButtonPress`、`onUrlPress`、`onCustomAction`、`onProductSelected`、`onPurchaseStarted`、`onPurchaseCompleted`、`onPurchaseFailed`、`onRestoreStarted`、`onRestoreCompleted`、`onRestoreFailed`、`onLoadingProductsFailed`、`onWebPaymentNavigationFinished` 和 `onAndroidSystemBack` 与 v3 中的名称相同。用户引导方法仍可使用,但已被弃用——详见[用户引导 API 弃用说明](#onboarding-api-deprecation)。部分默认行为已发生变更——详见[默认行为变更](#default-behavior-changes)。 ## 最低版本要求 \{#minimum-versions\} 运行时要求与 v3.16+ 保持不变:**iOS 15.0**、**Android minSdk 24** 以及 **Capacitor 8**,无需更改部署目标。 有一项新的构建要求:**Xcode 26 或更高版本** —— 此版本捆绑的原生 Adapty iOS SDK 4.0.0-beta.2 使用 Swift tools 6.2。 v4 捆绑了原生 Adapty SDK iOS 4.0.0-beta.2 和 Android BOM 4.0.0-beta.1。 ## 安装 \{#installation\} ### 更新包 \{#update-the-package\} v4.0 是预发布版本,请固定精确版本号——npm 不会通过脱字符/波浪号范围选择预发布版本: ```bash showLineNumbers npm install @adapty/capacitor@4.0.0-beta.2 ``` 然后同步原生项目: ```bash showLineNumbers npx cap sync ``` ### iOS:仅支持 Swift Package Manager \{#ios-swift-package-manager-only\} [CocoaPods 的 spec 仓库将于 2026 年 12 月进入只读模式](https://blog.cocoapods.org/CocoaPods-Specs-Repo/),因此从 v4 起 `AdaptyCapacitor.podspec` 已被移除,SDK 在 iOS 上**仅通过 Swift Package Manager(SPM)安装**。你的应用 iOS 工程必须使用 Capacitor 的 SPM 集成方式: - 新项目:使用 SPM 包管理器添加 iOS 平台: ```bash showLineNumbers npx cap add ios --packagemanager SPM ``` - 现有 CocoaPods 项目:按照 [Capacitor 关于在现有项目中使用 SPM 的指南](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project)迁移 iOS 项目。 参阅 [安装 Adapty SDK](sdk-installation-capacitor) 了解完整配置步骤。 ## 获取流程 \{#fetching-flows\} ### getPaywall → getFlow 返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,同时移除了 `locale` 选项——渲染 flow 时,语言环境会自动解析;对于自定义付费墙,所有语言环境均通过 `flow.remoteConfigs` 返回: ```diff showLineNumbers - const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); ``` `getPaywallForDefaultAudience` 以相同方式重命名: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID' }); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` 保持原名,但现在接收一个 `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts({ paywall }); + const products = await adapty.getPaywallProducts({ flow }); ``` ## 数据模型 \{#data-model\} `getFlow` 返回的是 `AdaptyFlow` 而非 `AdaptyPaywall`,对象结构也发生了变化: | v3 `AdaptyPaywall` 字段 | v4 `AdaptyFlow` 字段 | 操作 | |---|---|---| | `remoteConfig?`(单个) | `remoteConfigs?: AdaptyRemoteConfig[]`(数组) | 一个流程为每种已配置的语言携带一个远程配置。读取与用户匹配的那个:`flow.remoteConfigs?.find((c) => c.lang === 'en')`。 | | `products` | `flow.paywalls[i].productIdentifiers` | 产品标识符现在位于每个流程变体上,而不是在流程本身。 | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | 从流程移至每个付费墙变体。 | | `version?: number` | `flowVersionId?: string` | 已重命名,类型从 `number` 更改为 `string`。 | | `hasViewConfiguration` | 已移除 | 从代码中删除所有 `hasViewConfiguration` 检查——`createFlowView` 现在会直接抛出异常(参见[展示流程](#displaying-flows))。 | | `requestLocale` | 已移除 | 语言区域不再是模型的一部分。 | | _(新增)_ | `paywalls: AdaptyFlowPaywall[]` | 每个条目是流程中的一个付费墙变体。 | | _(新增)_ | `responseCreatedAt: number` | 服务器响应时间戳,单位为毫秒。 | `hasViewConfiguration` 和 `requestLocale` 保留在 `AdaptyOnboarding` 上——只有流程模型移除了它们。 产品标识符从流程移至每个实验变体: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Web 付费墙方法 \{#web-paywall-methods\} `openWebPaywall` 和 `createWebPaywallUrl` 保持原名不变,但 `paywallOrProduct` 选项现在接收 `AdaptyFlowPaywall`(流程变体)而非 `AdaptyPaywall`。你仍然可以传入 `AdaptyPaywallProduct`。在读取第一个条目之前,请先确认 `flow.paywalls` 非空: ```diff showLineNumbers const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); - await adapty.openWebPaywall({ paywallOrProduct: paywall }); + await adapty.openWebPaywall({ paywallOrProduct: flow.paywalls[0] }); ``` ## 跟踪流程查看次数 \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` 已重命名为 `logShowFlow`,现在接受一个 `AdaptyFlow` 参数。该事件仍针对相同的实验变体进行记录,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。 ```diff showLineNumbers - await adapty.logShowPaywall({ paywall }); + await adapty.logShowFlow({ flow }); ``` 与 v3 一样,当展示由 [Flow Builder](adapty-flow-builder) 或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些视图。 ## 显示流程 \{#displaying-flows\} ### createPaywallView → createFlowView 重命名工厂函数并传入 `AdaptyFlow`。返回的控制器从 `PaywallViewController` 更名为 `FlowViewController`,但其方法(`present`、`dismiss`、`setEventHandlers`、`showDialog`)保持不变。参数类型从 `CreatePaywallViewParamsInput` 更名为 `CreateFlowViewParamsInput`: ```diff showLineNumbers - import { createPaywallView } from '@adapty/capacitor'; + import { createFlowView } from '@adapty/capacitor'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` 如果 flow 未配置视图,`createFlowView` 会抛出 `AdaptyError`——这取代了 v3 中的 `hasViewConfiguration` 检查: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - const view = await createPaywallView(paywall); - await view.present(); - } + try { + const view = await createFlowView(flow); + await view.present(); + } catch (error) { + // the flow has no view configured, or view creation failed + } ``` :::note Flow 视图是一次性的:调用 `dismiss()` 后,视图会被销毁,其事件处理器也会被清除。如需再次展示该 flow,请重新调用 `createFlowView`。 ::: ### Android 安全区域内边距 \{#android-safe-area-paddings\} `CreateFlowViewParamsInput` 新增了一个参数:`enableSafeArea`,用于在运行时控制 Android 安全区域内边距。该参数嵌套在 `android` 键下,默认值为 `true`: ```typescript showLineNumbers const view = await createFlowView(flow, { android: { enableSafeArea: true }, }); ``` ## 处理事件 \{#handling-events\} 事件处理器接口从 `EventHandlers` 重命名为 `FlowEventHandlers`,同时有一个回调也进行了重命名。现有的处理器逻辑无需修改代码,只需重命名即可: ```diff showLineNumbers - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` 所有其他事件处理函数保持原有名称不变。其中两个新增了第二个参数:`onPurchaseCompleted` 现在是 `(purchaseResult, product)`,`onPurchaseFailed` 现在是 `(error, product)`,其中 `product` 是涉及的 `AdaptyPaywallProduct`。完整列表请参阅[处理 flow 与付费墙事件](capacitor-handling-events)。 v4 还新增了一些可选功能: - `adapty.openWebUrl({ url, openIn })` 和 `adapty.requestAppReview()` 方法——这两个方法支持默认的 `onUrlPress` 和 `onRequestAppReview` 处理器,因此 URL 和应用评价提示默认即可原生处理。仅在覆盖这些处理器时才需直接调用它们。 - 通过新的 `onObserverPurchaseInitiated` / `onObserverRestoreInitiated` 处理器,在流程中支持观察者模式下的购买处理。详见[在观察者模式下展示流程](capacitor-present-flows-in-observer-mode)。 ## 默认行为变更 \{#default-behavior-changes\} 这些变更不会导致编译错误,请在运行时进行测试: - **`onAndroidSystemBack`**:默认行为已从关闭视图改为保持打开状态。若要恢复之前的行为,请在处理程序中返回 `true`。 - **`onPurchaseCompleted`**:默认行为已从关闭视图(用户取消购买时除外)改为始终保持打开状态。若要恢复之前的行为,请在处理程序中返回 `purchaseResult.type !== 'user_cancelled'`。 - **`onRestoreCompleted`**:默认行为已从恢复成功后关闭视图改为保持打开状态。若要恢复之前的行为,请在处理程序中返回 `true`。 - **`onUrlPress`**:默认行为现在通过原生层打开 URL,遵循看板中配置的应用内浏览器或外部浏览器设置。如需自行处理 URL 的打开方式,请覆盖该处理程序。 - **视图仅可使用一次**:调用 `dismiss()` 后,视图将被销毁。如需再次展示该流程,请重新调用 `createFlowView`。 ## 已移除的 API \{#removed-apis\} ### 已移除的导出 \{#removed-exports\} 以下符号已不再从 `@adapty/capacitor` 中导出,请移除相关导入: - **`AdaptyPaywall`**:请改用 `AdaptyFlow` 和 `AdaptyFlowPaywall`。 - **`ProductReference`**:请改用 `AdaptyProductIdentifier`,从 `flow.paywalls[i].productIdentifiers` 中读取。 - **`AdaptyPaywallBuilder`**:已移除。流程和付费墙现在以原生方式渲染。 - **`AdaptyAndroidSubscriptionUpdateParameters`**:请改用嵌套的 `android` 购买参数结构(详见下文)。 ### activate: lockMethodsUntilReady `lockMethodsUntilReady`(在 v3 中已被弃用为空操作)现已移除。请从 `activate` 调用中删除它——保留该参数将无法编译: ```diff showLineNumbers - await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY', params: { lockMethodsUntilReady: true } }); + await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY' }); ``` ### makePurchase:Android 参数 \{#makepurchase-android-parameters\} 已废弃的 `MakePurchaseParamsInput` 扁平 Android 格式已被移除,现在只保留嵌套形式。请将所有 Android 购买参数迁移到 `params: { android: { ... } }` 中。完整示例请参阅[发起购买](capacitor-making-purchases)。 ## 用户引导 API 弃用 \{#onboarding-api-deprecation\} 旧版用户引导 API 已在 v4.0 中弃用,请改用 [Flow Builder](adapty-flow-builder)。该 API 目前仍可正常使用,但将在未来版本中移除,请提前将您的用户引导迁移至 Flow Builder。 已弃用的符号:`getOnboarding`、`getOnboardingForDefaultAudience`、`createOnboardingView` 和 `OnboardingViewController`。 --- # File: migration-to-capacitor-316 --- --- title: "将 Adapty Capacitor SDK 迁移至 v3.16" description: "迁移至 Adapty Capacitor SDK v3.16,享受更佳性能与全新变现功能。" --- 从 Adapty SDK v3.16.0 起,需要使用 Capacitor 8。如果你需要使用 Capacitor 7,请使用 Adapty SDK v3.15。 如需升级至 Capacitor SDK v3.16,请确保你的项目使用的是 Capacitor 8。如果你仍在使用 Capacitor 7,有以下两种选择: 1. **升级到 Capacitor 8**:按照 [Capacitor 官方迁移指南](https://capacitorjs.com/docs/updating/8-0) 更新项目,然后安装 Adapty SDK v3.16。 2. **继续使用 Adapty SDK v3.15**:如果暂时无法升级到 Capacitor 8,可以继续使用支持 Capacitor 7 的 Adapty SDK v3.15。 --- # End of Documentation _Generated on: 2026-07-24T13:01:53.322Z_ _Successfully processed: 45/45 files_ # FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.323Z Total files: 44 --- # File: sdk-installation-flutter --- --- title: "安装与配置 Flutter SDK" description: "在 Flutter 上安装 Adapty SDK 的分步指南,适用于基于订阅的应用。" --- Adapty SDK 包含两个核心模块,可无缝集成到您的 Flutter 应用中: - **Core Adapty**:这是 Adapty 正常运行所必需的核心 SDK。 - **AdaptyUI**:如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码即可轻松创建跨平台付费墙的可视化工具),则需要此模块。 :::tip 想看看 Adapty SDK 在真实移动应用中是如何集成的?欢迎查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example),其中展示了完整的集成配置,包括显示付费墙、完成购买以及其他基础功能。 ::: ## 要求 \{#requirements\} Adapty SDK 支持 iOS 13.0+,但要正常使用付费墙编辑工具创建的付费墙,需要 iOS 15.0+。 Adapty Flutter SDK 4.0——新增了 [Flow Builder](adapty-flow-builder) 支持——将最低要求提升至 **iOS 15.0+**、**Xcode 26+** 以及 **Flutter 3.32.0+**(Dart 3.8.0+)。安装详情请参阅下方的 [Adapty SDK 4.0](#adapty-sdk-40-swift-package-manager)。 :::info Adapty 兼容 Google Play Billing Library 8.x 及以下版本。默认情况下,Adapty 使用 Google Play Billing Library v7.0.0,但如果你想强制使用更高版本,可以手动[添加依赖项](https://developer.android.com/google/play/billing/integrate#dependency)。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) :::important 以下步骤安装的是最新稳定版 SDK(3.x)。如果你需要 v4([Flow Builder](adapty-flow-builder) 所需,[快速入门](flutter-quickstart-paywalls)也使用该版本),请直接参考下方的 [Adapty SDK 4.0: Swift Package Manager](#adapty-sdk-40-swift-package-manager)。 ::: 1. 将 Adapty 添加到你的 `pubspec.yaml` 文件中: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^<the latest SDK version> ``` 2. 运行以下命令安装依赖: ```bash showLineNumbers title="Terminal" flutter pub get ``` 3. 在应用中导入 Adapty SDK: ```dart showLineNumbers title="main.dart" import 'package:adapty_flutter/adapty_flutter.dart'; ``` ### Adapty SDK 4.0:Swift Package Manager 将支持 [Flow Builder](adapty-flow-builder) 的 Adapty Flutter SDK 4.0 添加到您的 `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` 从 v4 开始,原生 iOS SDK 不再通过 CocoaPods 分发——插件仅通过 **Swift Package Manager** 拉取([CocoaPods 的 spec 仓库将于 2026 年 12 月变为只读](https://blog.cocoapods.org/CocoaPods-Specs-Repo/))。如果您使用的是 Flutter 3.32–3.43,请先启用 Swift Package Manager 支持: ```bash showLineNumbers title="Terminal" flutter config --enable-swift-package-manager ``` Flutter 3.44 及更高版本默认启用 Swift Package Manager,因此无需额外操作。 有关 v4 中的 API 变更,请参阅[迁移指南](migration-to-flutter-sdk-v4)。 ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 在您的应用代码中激活 Adapty SDK。 :::note Adapty SDK 在您的应用中只需激活一次。 ::: 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 ```dart showLineNumbers title="main.dart" void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override void initState() { _initializeAdapty(); super.initState(); } Future<void> _initializeAdapty() async { try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), ); } catch (e) { // handle the error } } Widget build(BuildContext context) { return Text("Hello"); } } ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `activate` 完成。完整调用顺序请参阅 [Flutter SDK 调用顺序](flutter-sdk-call-order)。 ::: 现在在你的应用中配置付费墙: - 如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请先[激活 Adapty SDK 的 AdaptyUI 模块](#activate-adaptyui-module-of-adapty-sdk),然后按照[付费墙编辑工具快速入门](flutter-quickstart-paywalls)操作。 - 如果你自行构建付费墙 UI,请参阅[自定义付费墙快速入门](flutter-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果你计划使用[付费墙编辑工具](adapty-paywall-builder),并且已经[安装了 AdaptyUI 模块](sdk-installation-flutter#install-adapty-sdk),还需要激活 AdaptyUI: :::note 无论 AdaptyUI 是否已激活,与 AdaptyUI 相关的依赖项都会链接到你的应用中。 ::: :::important 在代码中,必须先激活 Adapty 核心模块,再激活 AdaptyUI。 ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withActivateUI(true), // This automatically activates AdaptyUI ); ``` ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误及其他重要信息,帮助你了解运行状况。目前支持以下日志级别: | 级别 | 描述 | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.error` | 仅记录错误日志 | | `AdaptyLogLevel.warn` | 记录错误日志,以及 SDK 中不会导致严重错误但值得关注的消息。 | | `AdaptyLogLevel.info` | 记录错误、警告及各类信息消息。默认值 | | `AdaptyLogLevel.verbose` | 记录调试时可能有用的额外信息,例如函数调用、API 请求等。 | | `AdaptyLogLevel.debug` | 记录调试信息。 | 您可以在配置 Adapty 之前在应用中设置日志级别: ```dart showLineNumbers title="main.dart" // Set log level before activation. // 'verbose' is recommended for development and the first production release await Adapty().setLogLevel(AdaptyLogLevel.verbose); // Or set it during configuration await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withLogLevel(AdaptyLogLevel.verbose), ); ``` ### 数据政策 \{#data-policies\} Adapty 不存储用户的个人数据,除非你明确发送,但你可以实施额外的数据安全策略以符合应用商店或国家/地区的规定。 #### 禁用 IP 地址收集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 即可禁用用户 IP 地址的收集与共享。默认值为 `false`。 使用此参数可以增强用户隐私保护、遵守区域性数据保护法规(如 GDPR 或 CCPA),或在您的应用不需要基于 IP 的功能时减少不必要的数据收集。 ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withIpAddressCollectionDisabled(true), ); ``` #### 禁用广告 ID 的收集与共享 \{#disable-advertising-id-collection-and-sharing\} 激活 Adapty 模块时,将 `appleIdfaCollectionDisabled`(iOS)或 `googleAdvertisingIdCollectionDisabled`(Android)设置为 `true` 可禁用广告标识符的收集。默认值为 `false`。 如需遵守 App Store/Play Store 政策、避免触发应用追踪透明度提示,或者您的应用不需要基于广告 ID 的广告归因或分析功能,请使用此参数。 ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleIdfaCollectionDisabled(true) // iOS ..withGoogleAdvertisingIdCollectionDisabled(true), // Android ); ``` #### 为 AdaptyUI 设置媒体缓存配置 \{#set-up-media-cache-configuration-for-adaptyui\} 该模块会随 Adapty SDK 自动激活。如果你不使用付费墙编辑工具,想要停用 AdaptyUI 模块,请在激活时传入 `withActivateUI(false)`。 默认情况下,AdaptyUI 会缓存媒体文件(如图片和视频)以提升性能、减少网络流量。你可以通过提供自定义配置来调整缓存设置。 使用 `withMediaCacheConfiguration` 可覆盖默认缓存限制。该方法为可选项——如果不调用,将使用默认值(磁盘大小 100MB,内存数量不限)。但一旦创建了配置对象,其所有参数均为必填项。 ```dart showLineNumbers title="main.dart" final mediaCacheConfig = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit: 2147483647, // max int value diskStorageSizeLimit: 200 * 1024 * 1024, // 200 MB ); await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withMediaCacheConfiguration(mediaCacheConfig), ); ``` **参数:** | 参数 | 是否必填 | 描述 | |-------------------------|----------|-----------------------------------------------------------------------------| | memoryStorageTotalCostLimit | 必填 | 内存缓存总大小,单位为字节。默认值为 100 MB。 | | memoryStorageCountLimit | 必填 | 内存存储的条目数量限制。默认值为 int 最大值。 | | diskStorageSizeLimit | 必填 | 磁盘文件大小限制,单位为字节。默认值为 100 MB。 | ### 启用本地访问等级(Android)\{#enable-local-access-levels-android\} 默认情况下,[本地访问等级](local-access-levels) 在 iOS 上已启用,在 Android 上处于禁用状态。如需在 Android 上也启用此功能,请将 `withGoogleLocalAccessLevelAllowed` 设置为 `true`: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleLocalAccessLevelAllowed(true), ); ``` ### 备份恢复时清除数据 \{#clear-data-on-backup-restore\} 将 `appleClearDataOnBackup` 设置为 `true` 后,SDK 会检测应用是否从 iCloud 备份中恢复,并删除所有本地存储的 SDK 数据,包括已缓存的用户画像信息、产品详情和付费墙。随后 SDK 将以全新状态重新初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易历史记录和 Adapty 服务器上的用户数据不受影响。 ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleClearDataOnBackup(true) // default – false ); ``` ## 故障排查 \{#troubleshooting\} #### Android 备份规则(Auto Backup 配置)\{#android-backup-rules-auto-backup-configuration\} 部分 SDK(包括 Adapty)会自带 Android 自动备份配置。如果多个 SDK 都定义了备份规则,Android 清单合并工具可能会报错,提示 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup` 相关问题。 常见错误示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note 以下更改应在你的 Android 平台目录(通常位于项目的 `android/` 文件夹)中进行。 ::: 要解决此问题,你需要: - 告知清单合并工具使用应用自身的备份相关属性值。 - 创建备份规则文件,将 Adapty 的规则与其他 SDK 的规则合并。 #### 1. 在清单中添加 `tools` 命名空间 \{#1-add-the-tools-namespace-to-your-manifest\} 在 `AndroidManifest.xml` 文件中,确保根标签 `<manifest>` 包含 tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. 在 `<application>` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在同一个 `AndroidManifest.xml` 文件中,更新 `<application>` 标签,使应用提供最终属性值,并告知清单合并工具替换库中的值: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 Android 项目的 `res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并。Android 根据系统版本使用不同的备份规则格式,同时创建两个文件可确保应用所支持的所有 Android 版本都能正常兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请替换或添加你应用中实际使用的其他 SDK 的规则。 ::: **Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Android 11 及更低版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### 在 Android 上从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app-in-android\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会错误地重建或复用该 Activity,导致购买结果丢失或被视为已取消。 为确保购买正常进行,请为启动购买流程的 Activity 仅使用 `standard` 或 `singleTop` 启动模式,避免使用其他任何模式。 在您的 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### 由 Podfile 中 SWIFT_VERSION 覆盖引起的 Swift 6 构建错误 \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} 在为 iOS 构建 Flutter 应用时,您可能会看到 Adapty pod 目标上出现 Swift 6 编译错误。常见症状包括:`AdaptyUIBuilderLogic` 中的 `@Sendable` 不匹配、Adapty 类型缺少 `Sendable` 一致性,或 actor 隔离错误。 The Adapty pods 声明 `s.swift_version = '6.0'` 并要求使用 Swift 6 进行构建。你自己的应用代码可以继续使用 Swift 5——只有 Adapty pod 目标(`Adapty`、`AdaptyUI`、`AdaptyUIBuilder`、`AdaptyLogger`、`AdaptyPlugin`)需要使用 Swift 6 构建。 最常见的原因是 `ios/Podfile` 中存在 `post_install` 钩子,它会为每个 pod 目标重写 `SWIFT_VERSION`: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **修复方法**:将 Adapty pod targets 排除在覆盖范围之外: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` 然后在 `ios/` 目录下运行 `pod install` 并重新构建。 如需验证,请打开 `ios/Pods/Pods.xcodeproj`,选择 `Adapty` pod target → **Build Settings** → **Swift Language Version**,确认其值为 **Swift 6**。 --- # File: flutter-quickstart-paywalls --- --- title: "在 Flutter SDK 中使用付费墙编辑工具启用购买功能" description: "使用 Adapty 付费墙编辑工具启用应用内购买的快速入门指南。" --- 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 向用户展示产品的界面序列,通过无代码 Flow Builder 构建。SDK 通过 `getFlow` 获取它们。如果你更倾向于在自己的代码中构建 UI,请使用付费墙代替 — 参见[手动实现付费墙](flutter-quickstart-manual)。 - [**版位**](placements) – 在应用中的何处、何时展示流程(如 `main`、`onboarding`、`settings`)。你在看板中将流程关联到版位,然后在代码中通过版位 ID 请求它们。这样可以轻松运行 A/B 测试,并向不同用户展示不同的流程。 Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可直接购买的流程](quickstart-paywalls)。Adapty 会自动渲染,并在后台处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 您在应用代码中自行实现付费墙 UI,但仍从 Adapty 获取流程对象,以保持产品方案的灵活性。请参阅[指南](flutter-quickstart-manual)。 | | Observer 模式 | 🔴 困难 | 您已有自己的购买处理基础设施,并希望继续使用。请注意,Observer 模式在 Adapty 中存在一定限制。请参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤说明如何实现在 Adapty Flow Builder 中创建的流程。** 如果您希望自行构建付费墙 UI,请参阅[手动实现付费墙](flutter-quickstart-manual)。 ::: 要在您的应用代码中展示 Adapty Flow Builder 中创建的流程,您只需: 1. **获取流程**:从 Adapty 获取流程。 2. **展示流程,Adapty 将为您处理购买**:在您的应用中显示该视图。 3. **处理按钮操作**:将用户交互与您的应用响应关联起来。例如,当用户点击按钮时打开链接或关闭流程。 ## 开始之前 \{#before-you-start\} 在开始之前,请完成以下步骤: 1. 在 Adapty 看板中将你的应用连接到 [App Store](initial_ios) 和/或 [Google Play](initial-android)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建流程并向其添加产品](create-paywall)。 4. [创建版位并将流程添加到其中](create-placement)。 5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-flutter)。本指南使用 Adapty Flutter SDK v4 API。 :::tip 完成这些步骤最快的方式是参照[快速入门指南](quickstart),或使用 [Developer CLI](developer-cli-quickstart) 创建付费墙和版位。 ::: ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。通过版位,你可以为不同的目标受众运行不同的流程,或进行 [A/B 测试](ab-tests)。 要获取在 Adapty 付费墙编辑工具中创建的流程,你需要: 1. 使用 `getFlow` 方法通过[版位](placements) ID 获取 `flow` 对象,并通过 `hasViewConfiguration` 属性检查该流程是否由编辑工具创建。 2. 使用 `createFlowView` 方法创建流程视图。该视图包含显示流程所需的 UI 元素和样式。 :::important 要获取视图配置,必须在编辑工具中开启 **Show on device** 开关。否则将获取到空的视图配置,流程也不会显示。 ::: ```dart showLineNumbers try { // the requested flow final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final view = await AdaptyUI().createFlowView( flow: flow, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 2. 展示流程 \{#display-the-flow\} 获取到 flow 视图后,只需添加几行代码即可将其展示出来。 要展示流程,请对由 `createFlowView` 方法创建的 `view` 调用 `view.present()` 方法。每个 `view` 只能展示一次:关闭后,该视图会从内存中释放。如果需要再次展示流程,请重新调用 `createFlowView` 以创建新的 `view` 实例。 ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip 有关如何展示流程的更多详情,请参阅我们的[指南](flutter-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#handle-button-actions\} 当用户点击流程中的按钮时,Flutter SDK 会自动处理购买、恢复购买、关闭视图和打开 URL 等操作。但其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作。 要控制或监控流程屏幕上的操作,请实现 `AdaptyUIFlowsEventsObserver` 方法,并在展示任何屏幕之前设置观察者。当用户执行某个操作时,`flowViewDidPerformAction` 会被触发,您的应用需要根据操作 ID 做出相应响应。 三个观察者方法是**必须实现的**:`flowViewDidFinishPurchase`、`flowViewDidFinishRestore` 和 `flowViewDidReceiveError` — 缺少这些方法,代码将无法编译。 :::tip 请参阅我们的指南,了解如何处理按钮[操作](flutter-handle-paywall-actions)和[事件](flutter-handling-events)。 ::: 将观察者实现为一个专用的长生命周期对象,而不是 widget。由于整个应用只有一个全局观察者插槽,将其绑定到 `State` 会导致页面泄漏(SDK 持有对它的强引用),并且在下一个页面注册自身时会被静默替换。使用 `extends` 还会继承 SDK 的默认行为,因此除了三个必需方法外,你只需覆盖自己关心的回调即可。 ```dart showLineNumbers title="Flutter" // A dedicated, long-lived handler for flow events. // It does NOT live inside a Widget/State, so it never leaks and is never // silently replaced when screens are pushed or popped. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // This method is called when user performs an action on the flow UI. // Overriding it replaces the default behavior (dismiss on close, open URLs), // so keep those cases if you want to preserve it. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } ``` 在应用启动时**注册一次**处理程序,须在任何流程显示之前完成: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); ``` ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中显示。在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试您的购买,确保可以从付费墙完成测试购买。 现在,您需要[检查用户的访问等级](flutter-check-subscription-status),以确保您向正确的用户展示付费墙或授予付费功能的访问权限。 ## 完整示例 \{#full-example\} 下面展示了如何将上述所有步骤整合到你的应用中。 ```dart void main() { // Register a single, long-lived observer once, before any flow is shown. // It is intentionally a plain object (NOT a Widget/State): its lifetime is the // whole app, so it never leaks and is never silently replaced when screens are // pushed or popped. AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); runApp(MaterialApp(home: FlowScreen())); } /// A dedicated handler for AdaptyUI flow events. /// /// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented /// by a `State`), which gives you two things for free: /// * the SDK's sensible defaults for optional callbacks, so besides the three /// required methods you only override what you actually care about; /// * a lifecycle that is independent of the widget tree — there is no strong /// reference back into a `Widget`, so nothing leaks and there is nothing to /// unregister. /// /// Every callback receives the [AdaptyUIFlowView] it relates to, so handling /// flow actions never requires a `BuildContext` or widget state. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // Called when the user performs an action on the flow UI. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): // Open the URL natively, honoring the dashboard browser setting. AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes. @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds. @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors. @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } class FlowScreen extends StatefulWidget { const FlowScreen({super.key}); @override State<FlowScreen> createState() => _FlowScreenState(); } class _FlowScreenState extends State<FlowScreen> { @override void initState() { super.initState(); _showFlowIfNeeded(); } Future<void> _showFlowIfNeeded() async { try { final flow = await Adapty().getFlow( placementId: 'YOUR_PLACEMENT_ID', ); if (!flow.hasViewConfiguration) return; final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); } catch (_) { // Handle any errors (network, SDK issues, etc.) } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Adapty Flow Example')), body: Center( // Add a button to re-trigger the flow for testing purposes. child: ElevatedButton( onPressed: _showFlowIfNeeded, child: const Text('Show Flow'), ), ), ); } } ``` --- # File: flutter-check-subscription-status --- --- title: "在 Flutter SDK 中检查订阅状态" description: "了解如何在 Flutter 应用中使用 Adapty 检查订阅状态。" --- 要决定用户是否可以访问付费内容或查看付费墙,您需要检查其用户画像中的[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种选择: - 如果需要立即获取最新用户画像数据(例如应用启动时)或希望强制更新,请调用 `getProfile`。 - 设置**自动用户画像更新**,以保留一份本地副本,该副本会在订阅状态发生变化时自动刷新。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方法是使用 `getProfile` 方法访问用户画像: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 要在应用中自动接收用户画像更新: 1. 使用 `Adapty().didUpdateProfileStream.listen()` 监听用户画像变更——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。 2. 在此方法被调用时保存更新后的用户画像数据,这样无需额外发起网络请求,即可在应用各处直接使用。 ```dart class SubscriptionManager { AdaptyProfile? _currentProfile; SubscriptionManager() { // Listen for profile updates Adapty().didUpdateProfileStream.listen((profile) { _currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() bool hasAccess() { return _currentProfile?.accessLevels['premium']?.isActive ?? false; } } ``` :::note Adapty 会在应用启动时自动调用用户画像更新流监听器,即使设备处于离线状态,也能提供已缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当你需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。这种方式适用于应用启动、进入高级功能区域或展示特定内容之前等场景。 ```dart Future<bool> _checkAccessLevel() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false; } catch (e) { print('Error checking access level: $e'); return false; // Show paywall if access check fails } } Future<void> _initializePaywall() async { await _loadPaywall(); final hasAccess = await _checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } ``` ## 后续步骤 \{#next-steps\} 现在您已了解如何追踪订阅状态,接下来请学习如何[管理用户画像](flutter-quickstart-identify),以确保用户能够访问其已付费的内容。 --- # File: flutter-quickstart-identify --- --- title: "在 Flutter SDK 中识别用户" description: "在 Flutter 中设置 Adapty 以管理应用内订阅的快速入门指南。" --- :::important 本指南适用于拥有自有认证系统的开发者。你将在此了解如何在 Adapty 中管理用户画像,使其与现有认证系统保持一致。 ::: 如何管理用户购买行为取决于你的应用认证模式: - 如果你的应用不使用后端认证且不存储用户数据,请参阅[匿名用户部分](#anonymous-users)。 - 如果你的应用已有(或将有)后端认证,请参阅[已识别用户部分](#identified-users)。 **核心概念**: - **用户画像**是 SDK 正常运行所必需的实体,由 Adapty 自动创建。 - 用户画像可以是匿名的**(不含 customer user ID)**,也可以是已识别的**(含 customer user ID)**。 - 你提供 **customer user ID** 是为了将 Adapty 中的用户画像与你内部的身份验证系统进行关联对照。 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|-----------------------------------|-----------------------------------------------| | **购买管理** | 商店级购买恢复 | 通过客户用户 ID 跨设备保留购买历史记录 | | **用户画像管理** | 每次重装生成新的用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用重装后仍然保留 | ## 匿名用户 \{#anonymous-users\} 如果你没有后端身份验证,**则无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时被激活,Adapty 会**为该用户创建一个新的用户画像**。 2. 当用户在应用内完成任何购买后,该购买记录会**与其 Adapty 用户画像及应用商店账户关联**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建一个新的匿名用户画像**。 4. 如果用户之前已在您的应用中完成过购买,默认情况下,其购买记录会在 SDK 激活时自动从 App Store 同步。 对于匿名用户,每次安装都会创建新的用户画像,但这不是问题——在 Adapty 看板的分析设置中,你可以[配置什么将被视为新安装](general#4-installs-definition-for-analytics)。 对于匿名用户,你需要按**设备 ID** 统计安装量。在这种情况下,设备上的每次应用安装(包括重新安装)都会被计为一次安装。 ## 已识别用户 \{#identified-users\} 在应用中识别用户有两种方式: - [**登录/注册时:**](#during-loginsignup) 如果用户在应用启动后才登录,请在用户完成身份验证时调用 `identify()`,并传入 customer user ID。 - [**SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时已有存储的 customer user ID,可在调用 `activate()` 时直接传入。 :::important 默认情况下,当 Adapty 收到来自某个 Customer User ID 的购买请求,而该 ID 当前与另一个 Customer User ID 关联时,访问等级将被共享,因此两个用户画像都拥有付费访问权限。你可以配置此设置,将付费访问权限从一个用户画像转移到另一个,或完全禁用共享功能。详情请参阅[这篇文章](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 登录/注册时 \{#during-loginsignup\} 如果您是在应用启动后才识别用户身份(例如在用户登录或注册之后),请使用 `identify` 方法来设置其 customer user ID。 - 如果您**之前从未使用过该 customer user ID**,Adapty 会自动将其与当前用户画像关联。 - 如果您**之前已使用该 customer user ID 识别过用户**,Adapty 将切换至与该 customer user ID 关联的用户画像。 :::important 客户用户 ID 对每个用户必须唯一。如果将参数值硬编码,所有用户将被视为同一个人。 ::: 调用其他 SDK 方法之前,请务必 `await` `identify`。并发调用会产生 `#3006 profileWasChanged` 错误或落到匿名用户画像上。详见 [Flutter SDK 的调用顺序](flutter-sdk-call-order)。 ```dart showLineNumbers try { await Adapty().identify(customerUserId); // 每个用户唯一 } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### 在 SDK 激活期间 \{#during-the-sdk-activation\} 如果在激活 SDK 时已知道用户的 customer user ID,可以直接在 `activate` 方法中传入,无需单独调用 `identify`。 如果已知 customer user ID,但在激活之后才进行设置,则意味着在激活时 Adapty 会创建一个新的匿名用户画像,并仅在你调用 `identify` 后才切换到已有的用户画像。 您可以传入已有的 customer user ID(之前使用过的),也可以传入一个全新的。如果传入新的,激活时创建的新用户画像将自动与该 customer user ID 关联。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装量是基于设备 ID 统计的。 设备 ID 代表应用在设备上的一次安装实例,仅在应用重新安装后才会重新生成。 无论是首次安装还是重复安装,也无论是否使用了已有的客户用户 ID,设备 ID 均不受影响。 创建用户画像(在 SDK 激活或退出登录时)、登录,或在不重新安装应用的情况下升级应用,均不会产生新的安装事件。 如果您希望按唯一用户而非设备来统计安装量,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```dart showLineNumbers" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. ); } catch (e) { // handle the error } ``` ### 退出用户登录 \{#log-users-out\} 如果您有供用户退出登录的按钮,请使用 `logout` 方法。 :::important 退出用户登录会为该用户创建新的匿名用户画像。 ::: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::info 要将用户重新登录到应用,请使用 `identify` 方法。 ::: ### 允许未登录状态下购买 \{#allow-purchases-without-login\} 如果你的用户在登录前和登录后都可以进行购买,你需要确保他们登录后仍能保留访问权限: 1. 当未登录用户完成购买时,Adapty 会将该购买绑定到其匿名用户画像 ID。 2. 当用户登录账号后,Adapty 会切换到其已识别的用户画像。 - 如果是新的 customer user ID(例如购买发生在注册之前),Adapty 会将该 customer user ID 分配给当前用户画像,所有购买记录均得以保留。 - 如果是已存在的 customer user ID(该 customer user ID 已关联某个用户画像),则需要在切换用户画像后获取实际的访问等级。你可以在识别完成后立即调用 [`getProfile`](flutter-check-subscription-status),也可以[监听用户画像更新](flutter-check-subscription-status)以实现数据自动同步。 ## 下一步 \{#next-steps\} 恭喜!您已经在应用中完成了应用内支付逻辑的接入!祝您的应用变现一切顺利! 要充分发挥 Adapty 的价值,可以探索以下主题: - [**测试**](troubleshooting-test-purchases):确保一切正常运行 - [**用户引导**](flutter-onboardings):通过用户引导吸引用户并提升留存率 - [**集成**](configuration):只需一行代码,即可与营销归因和分析服务集成 - [**设置自定义用户画像属性**](flutter-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,以便针对不同用户启动 A/B 测试或展示不同的付费墙 --- # File: adapty-sdk-integration-skill-flutter --- --- title: "使用 SDK 集成技能将 Adapty 集成到 Flutter 应用" description: "使用 adapty-sdk-integration 技能,借助 AI 编程工具将 Adapty SDK 端到端集成到 Flutter 应用中。" --- <AdaptySdkIntegrationSkill platform="Flutter" /> :::important 该技能目前处于 Beta 阶段。如果出现卡顿或异常行为,请改用[逐步集成指南](adapty-cursor-flutter)——它会引导你的 AI 工具完成每个阶段,并提供正确的文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-flutter --- --- title: "借助 AI 将 Adapty 集成到 Flutter 应用中" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具,将 Adapty 集成到 Flutter 应用的分步指南。" --- 本指南将引导你逐步使用 AI 编程工具将 Adapty 集成到 Flutter 应用中——你只需按正确的顺序为它提供 Adapty 文档即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。您可以使用交互式 LLM 技能,或通过看板手动操作。 ### 技能方式(推荐)\{#skill-approach-recommended\} Adapty CLI 技能让您的 LLM 能够直接设置应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。您只需在看板中[连接您的商店](integrate-payments)即可。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在您的代理中运行 `/adapty-cli`。它将引导您完成每个步骤——包括何时打开看板连接您的商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你更倾向于手动配置,以下是编写代码前需要准备的内容。LLM 无法自动查找看板中的值,需要你手动提供。 1. **连接应用商店**:在 Adapty 看板中,前往 **App settings → General**,将 App Store 和 Google Play 均完成连接(如果你的 Flutter 应用需要同时支持两个平台)。这是购买功能正常运行的必要条件。 [连接应用商店](integrate-payments) 2. **复制公共 SDK 密钥**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这就是你传入 Adapty 配置的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,前往 **Products** 页面。你无需在代码中直接引用产品——Adapty 会通过付费墙将其下发。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配到版位。在代码中,版位 ID 就是传给 `Adapty().getPaywall()` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板的 **Products** 页面中,按产品进行配置。在代码中,通过 `profile.accessLevels['premium']?.isActive` 检查对应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户会根据所购产品获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编写代码之前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五项信息后,就可以开始写代码了。告诉你的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",这样它就能生成正确的初始化和付费墙获取代码。 ::: ### 准备就绪后再设置 \{#set-up-when-ready\} 以下内容不是开始编码的必要条件,但随着集成的深入,您会需要它们: - **A/B 测试**:在 **Placements** 页面进行配置,无需更改代码。 [A/B 测试](ab-tests) - **更多付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。 - **分析集成**:在 **Integrations** 页面进行配置,具体设置因集成而异。请参阅[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 向 LLM 提供 Adapty 文档 \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) [Context7](https://context7.com) 是一个 MCP 服务器,可让你的 LLM 直接访问最新的 Adapty 文档。LLM 会根据你的提问自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the Flutter SDK ``` :::warning 虽然 Context7 无需手动粘贴文档链接,但实现顺序很重要。请按照下方的[实现步骤](#implementation-walkthrough)逐步操作,确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 您可以以纯文本 Markdown 格式访问任何 Adapty 文档。在 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-flutter.md](https://adapty.io/docs/zh/adapty-cursor-flutter.md)。 下面[实现演练](#implementation-walkthrough)的每个阶段都包含一个"发送给 LLM"的代码块,其中包含可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下方的[索引文件和平台特定子集](#plain-text-doc-index-files)。 ## 实现演练 \{#implementation-walkthrough\} 本指南的其余部分按实现顺序介绍 Adapty 集成。每个阶段都包含发送给 LLM 的文档、完成后的预期效果以及常见问题。 ### 规划你的集成方案 \{#plan-your-integration\} 在开始写代码之前,先让 LLM 分析你的项目并制定实现计划。如果你使用的 AI 工具支持规划模式(比如 Cursor 或 Claude Code 的 plan 模式),建议先用它让 LLM 同时读取你的项目结构和 Adapty 文档,再动手写代码。 告诉你的 LLM 你使用的是哪种购买方式——这会影响它需要参考的文档: - [**Adapty 付费墙编辑工具**](adapty-paywall-builder):在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染。 - [**手动创建的付费墙**](flutter-making-purchases):自行编写付费墙 UI 代码,但仍使用 Adapty 获取产品并处理购买。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅使用 Adapty 进行数据分析和集成。 不确定该选哪个?请查看[快速入门中的对比表](flutter-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 使用 `flutter pub add` 添加 Adapty SDK 依赖,并通过你的 Public SDK key 激活。这是一切的基础——没有它,其他功能都无法运行。 **指南:** [安装并配置 Adapty SDK](sdk-installation-flutter) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-flutter.md ``` :::tip[Checkpoint] - **预期结果:** 应用可在 iOS 和 Android 上正常构建和运行,调试控制台显示 Adapty 激活日志。 - **常见问题:** 出现"Public API key is missing"→ 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 显示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙、展示付费墙并处理购买事件。具体需要参考哪些指南,取决于你处理购买的方式。 在操作过程中,每完成一步就在沙盒中测试一次购买——不要等到最后再测。设置说明请参阅[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="付费墙编辑工具" default> **指南:** - [使用付费墙启用购买(快速入门)](flutter-quickstart-paywalls) - [获取付费墙编辑工具付费墙及其配置](flutter-get-pb-paywalls) - [展示付费墙](flutter-present-paywalls) - [处理付费墙事件](flutter-handling-events) - [响应按钮操作](flutter-handle-paywall-actions) 请将以下内容发送给您的 LLM: ``` 在编写代码之前,请先阅读这些 Adapty 文档: - https://adapty.io/docs/zh/flutter-quickstart-paywalls.md - https://adapty.io/docs/zh/flutter-get-pb-paywalls.md - https://adapty.io/docs/zh/flutter-present-paywalls.md - https://adapty.io/docs/zh/flutter-handling-events.md - https://adapty.io/docs/zh/flutter-handle-paywall-actions.md ``` :::tip[检查点] - **预期效果:** 付费墙显示已配置的产品,点击产品会触发沙盒购买对话框。 - **常见问题:** 付费墙为空或出现 `getPaywall` 错误 → 检查版位 ID 是否与看板中完全一致,以及该版位是否已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="手动付费墙"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](flutter-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-flutter) - [渲染通过远程配置设计的付费墙](present-remote-config-paywalls-flutter) - [发起购买](flutter-making-purchases) - [恢复购买](flutter-restore-purchase) 将这段内容发送给你的 LLM: ``` 在编写代码之前,请先阅读以下 Adapty 文档: - https://adapty.io/docs/zh/flutter-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-flutter.md - https://adapty.io/docs/zh/present-remote-config-paywalls-flutter.md - https://adapty.io/docs/zh/flutter-making-purchases.md - https://adapty.io/docs/zh/flutter-restore-purchase.md ``` :::tip[Checkpoint] - **预期效果:** 你的自定义付费墙能够展示从 Adapty 获取的产品,点击产品会触发沙盒购买弹窗。 - **常见问题:** 产品列表为空 → 请确认看板中已为付费墙分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **指南:** - [Observer 模式概述](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-flutter) - [在 Observer 模式中上报交易](report-transactions-observer-mode-flutter) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode-flutter.md - https://adapty.io/docs/zh/report-transactions-observer-mode-flutter.md ``` :::tip[检查点] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易应出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认你已向 Adapty 上报交易,并已为两个应用商店配置了服务器通知。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买后,检查用户画像中的活跃访问等级,以控制高级内容的访问权限。 **指南:**[检查订阅状态](flutter-check-subscription-status) 发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/flutter-check-subscription-status.md ``` :::tip[检查点] - **预期效果:** 沙盒购买后,`profile.accessLevels['premium']?.isActive` 返回 `true`。 - **注意事项:** 购买后 `accessLevels` 为空 → 检查看板中该产品是否已分配访问等级。 ::: ### 识别用户 \{#identify-users\} 将您的应用用户账户与 Adapty 用户画像关联,确保购买记录在多设备间持久保存。 :::important 如果您的应用无需身份验证,请跳过此步骤。 ::: **指南:** [识别用户](flutter-quickstart-identify) 将以下内容发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/flutter-quickstart-identify.md ``` :::tip[Checkpoint] - **预期结果:** 调用 `Adapty().identify()` 后,看板的 **Profiles** 页面会显示你的自定义用户 ID。 - **注意事项:** 请在激活之后、获取付费墙之前调用 `identify`,以避免匿名用户画像归因问题。 ::: ### 准备发布 \{#prepare-for-release\} 沙盒中的集成测试通过后,请按照发布检查清单逐项确认,确保一切都已准备好投入生产。 **指南:** [发布检查清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[Checkpoint] - **预期结果:** 所有检查项均已确认:商店连接、服务器通知、购买流程、访问等级检查,以及隐私要求。 - **注意事项:** 如果缺少服务器通知 → 请在 **App settings → iOS SDK** 中配置 App Store 服务器通知,在 **App settings → Android SDK** 中配置 Google Play 实时开发者通知。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单个页面范围的更广泛上下文,我们提供了列出或汇总所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面及其 `.md` 链接。这是一种[新兴标准](https://llmstxt.org/),旨在让 LLM 能够访问网站内容。请注意,对于某些 AI 代理(如 ChatGPT),你需要先下载 `llms.txt`,然后将其作为文件上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站点合并为单个文件。内容非常庞大——仅在需要完整信息时使用。 - Flutter 专用版本 [`flutter-llms.txt`](https://adapty.io/docs/zh/flutter-llms.txt) 和 [`flutter-llms-full.txt`](https://adapty.io/docs/zh/flutter-llms-full.txt):平台专属子集,相比完整站点可节省 token 用量。 --- # File: flutter-get-pb-paywalls --- --- title: "获取流程与付费墙 - Flutter" description: "在 Flutter 应用中从 Adapty 获取流程和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" /> 在[设计好您的流程或付费墙编辑工具付费墙](adapty-paywall-builder)之后,您可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体方法如下所述。 请注意,本主题涉及流程和付费墙编辑工具定制的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中为远程配置付费墙获取付费墙和产品](fetch-paywalls-and-products-flutter)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在开始于您的移动应用中展示流程和付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建流程/付费墙并将产品加入其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程/付费墙加入其中](create-placement)。 4. 在您的移动应用中安装 [Adapty SDK](sdk-installation-flutter)。 </details> ## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已使用流程编辑工具或付费墙编辑工具设计了流程或付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示。此类流程或付费墙已包含展示内容和展示方式的完整配置。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动应用中进行呈现。 尽早获取流程或付费墙并[创建其视图](flutter-get-pb-paywalls#fetch-the-view-configuration)——最好在展示之前就提前完成。`createFlowView` 方法会加载视图配置,并在后台开始下载和缓存图片。调用时机越早,下载完成的时间就越充裕。等到真正展示流程或付费墙时,其配置和图片往往已经缓存好、随时可用。 使用 `getFlow` 方法获取流程或付费墙: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在的情况下直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。</p><p></p><p>请注意,缓存在应用重启后依然保留,只有在应用重新安装或手动清除时才会被清空。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 来加快付费墙的获取速度,并在 CDN 不可用时提供独立的备用服务器。整套机制旨在确保你始终能获取最新版本的付费墙,同时在网络条件较差时也能保证可靠性。</p> | | **loadTimeout** | 默认:5 秒 | <p>限制该方法超时时间的 `Duration` 值。若超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为底层操作可能由多个请求组成。</p> | 响应参数: | 参数 | 描述 | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | 一个 `AdaptyFlow` 对象,包含流程的标识符(`instanceIdentity`、`variationId`)、名称、版位、其付费墙实验变体(`paywalls`)以及任何远程配置(`remoteConfigs`)。 | ## 获取视图配置 \{#fetch-the-view-configuration\} :::important 请确保在编辑工具中启用了 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 如果版位是在 **Flow Builder** 或 **Paywall Builder** 中设计的,Adapty 会自动为你渲染 UI —— 所获取的 flow 的 `hasViewConfiguration` 属性为 `true`。使用 `createFlowView` 创建视图,然后[展示 flow 或付费墙](flutter-present-paywalls)。如果版位是未使用编辑工具的自定义付费墙(`hasViewConfiguration` 为 `false`),请改为[将其作为远程配置付费墙处理](present-remote-config-paywalls-flutter)。 :::warning `createFlowView` 方法的返回结果只能呈现一次。如需再次呈现,请重新调用 `createFlowView` 方法。 ::: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView(flow: flow); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | 必填 | 一个 `AdaptyFlow` 对象,用于获取所需流程/付费墙的视图。 | | **customTags** | 选填 | 定义自定义标签及其对应值的映射。自定义标签作为内容中的占位符,在流程/付费墙中动态替换为指定字符串,实现个性化内容。详情请参阅[付费墙编辑工具中的自定义标签](custom-tags-in-paywall-builder)。 | | **preloadProducts** | 选填 | 启用后可优化产品在屏幕上的显示时机。设为 `true` 时,AdaptyUI 将自动预加载所需产品。默认值:`false`。 | | **loadTimeout** | 选填 | 一个 `Duration`,用于限制视图配置的加载超时时间。若超时,将使用缓存数据或本地备用内容。 | :::note 如果您使用多种语言,请了解如何添加[流程本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码([点击此处了解](flutter-localizations-and-locale-codes))。 ::: 完成视图设置后,[展示流程/付费墙](flutter-present-paywalls)。 ## 获取默认目标受众的流程或付费墙以加快加载速度 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,流程和付费墙的加载几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和版位,且用户的网络连接较弱,加载流程或付费墙可能会比预期慢。在这种情况下,你可能希望先展示默认流程或付费墙,以保证用户体验的流畅性,而不是什么都不显示。 为了解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的流程或付费墙。但需要特别注意的是,推荐的做法是通过 `getFlow` 方法来获取流程或付费墙,详情请参阅上方的[获取流程/付费墙](#fetch-flowpaywall)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在一些明显的缺点: - **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会面临挑战。你要么针对当前(旧版)版本设计兼容的付费墙,要么接受旧版用户可能遇到付费墙无法渲染的问题。 - **目标定向失效**:所有用户都将看到针对 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些弊端以换取更快的流程或付费墙加载速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。 ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements) 的标识符。这是您在 Adapty 看板中创建版位时所指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方案,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存数据存在时直接返回缓存。这样一来,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会被清除。</p> | ## 自定义资源 \{#customize-assets\} 要自定义流程/付费墙中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,您可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在播放视频前先显示预览图。 以下是如何通过简单字典提供自定义资源的示例: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createFlowView( flow: flow, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note 如果找不到某个资源,流程/付费墙将回退到其默认外观。 ::: ## 设置开发者自定义计时器 \{#set-up-developer-defined-timers\} 要在移动应用中使用自定义计时器,请将 `customTimers` 映射传递给 `createFlowView` 方法。映射中每个键为计时器 ID,对应的值为定义计时器结束时间的 `DateTime` 对象。示例如下: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView( flow: flow, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2027, 1, 1), // New Year 2027 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` 在此示例中,`CUSTOM_TIMER_NY` 和 `CUSTOM_TIMER_6H` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`customTimers` 映射可确保你的应用动态地为每个计时器更新正确的值。例如: - `CUSTOM_TIMER_NY`:距计时器结束时间(如元旦)的剩余时间。 - `CUSTOM_TIMER_6H`:用户打开流程后,6 小时倒计时的剩余时间。 </SDKv4> <SDKv3> 在 [Adapty 看板中使用新版付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,你可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体方法如下。 :::warning 新版付费墙编辑工具需要 Flutter SDK 3.3.0 或更高版本。 ::: 请注意,本主题适用于使用付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙和产品](fetch-paywalls-and-products-flutter)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中展示付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在您的移动应用中安装 [Adapty SDK](sdk-installation-flutter)。 </details> ## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),就无需在移动应用代码中手动处理渲染逻辑来向用户展示它。这类付费墙同时包含展示内容和展示方式。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动应用中将其呈现出来。 为确保最佳性能,请尽早获取付费墙及其[视图配置](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够的时间完成图片下载。 使用 `getPaywall` 方法获取付费墙: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为语言代码,由一个或两个子标签组成,子标签之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言区域代码及推荐用法,请参阅[本地化与语言区域代码](flutter-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时直接返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清除时才会被清空。</p><p></p><p>Adapty SDK 在本地以两层方式存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加快付费墙的获取速度,并在 CDN 不可用时提供独立的备用服务器。这套机制旨在确保你始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为底层操作可能包含多个请求。</p><p>对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。如需不设限制,请使用 `TimeInterval.INFINITE`。</p> | 响应参数: | 参数 | 描述 | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中开启 **Show on device** 开关。如果未启用此选项,将无法获取视图配置。 ::: 获取付费墙后,检查其是否包含 `ViewConfiguration`,这表明该付费墙是使用付费墙编辑工具创建的。这将帮助你确定如何展示该付费墙。如果存在 `ViewConfiguration`,则将其作为付费墙编辑工具付费墙处理;如果不存在,请[将其作为远程配置付费墙处理](present-remote-config-paywalls-flutter)。 ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` 获取视图后,[展示付费墙](flutter-present-paywalls)。 ## 为默认目标受众获取付费墙以加快加载速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络连接较差,获取付费墙可能会比预期耗时更长。在这种情况下,你可能希望优先展示默认付费墙,以保证流畅的用户体验,而不是让用户看到空白页面。 要解决这个问题,你可以使用 `getPaywallForDefaultAudience` 方法,它会获取指定版位中 **All Users** 目标受众对应的付费墙。但需要特别注意的是,推荐的做法是通过 `getPaywall` 方法来获取付费墙,详见上方的[获取付费墙信息](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显的缺点: - **潜在的向后兼容问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么将付费墙设计为同时兼容当前(旧版)版本,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去定向能力**:所有用户都将看到专为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向功能(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些不足,以换取更快的付费墙加载速度,可按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-paywall-designed-with-paywall-builder)介绍的 `getPaywall` 方法。 ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note `getPaywallForDefaultAudience` 方法从 Flutter SDK 3.2.0 版本起可用。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间用短横线(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及推荐使用方式,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果你认为用户的网络状况不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存数据存在时直接返回缓存。这种方式下用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后依然保留,只有在应用重新安装或手动清除时才会被清空。</p> | ## 自定义资源 \{#customize-assets\} 要自定义付费墙中的图片和视频,请实现自定义资源。 主图和主视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你可以通过这些 ID 定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 为部分用户显示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在播放视频前显示预览图片。 :::important 要使用此功能,请将 Adapty Flutter SDK 更新至 3.8.0 或更高版本。 ::: 以下是如何通过简单字典提供自定义资源的示例: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note 如果找不到相应素材,付费墙将回退到其默认外观。 ::: ## 设置自定义计时器 \{#set-up-developer-defined-timers\} 要在移动应用中使用自定义计时器,请将 `customTimers` 映射传递给 `createPaywallView` 方法。映射中的每个键是计时器 ID,对应的值是一个 `DateTime` 对象,用于定义计时器的结束时间。示例如下: ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2025, 1, 1), // New Year 2025 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` 在此示例中,`CUSTOM_TIMER_NY` 和 `CUSTOM_TIMER_6H` 是您在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`customTimers` 映射确保您的应用为每个计时器动态更新正确的值。例如: - `CUSTOM_TIMER_NY`:距计时器结束时间(如元旦)的剩余时长。 - `CUSTOM_TIMER_6H`:从用户打开付费墙开始计算的 6 小时倒计时剩余时长。 </SDKv3> --- # File: flutter-present-paywalls --- --- title: "展示流程与付费墙 - Flutter" description: "使用 Adapty 的变现功能在 Flutter 应用中展示流程和付费墙。" --- <SDKv4> 如果你已经使用流程编辑工具(Flow Builder)或付费墙编辑工具(Paywall Builder)设计了流程或付费墙,则无需在移动应用代码中手动实现渲染逻辑来向用户展示它。这类流程或付费墙本身已经包含了展示内容与展示方式的完整定义。 :::warning 本指南适用于流程和付费墙编辑工具构建的付费墙。如需展示**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls-flutter)。 ::: Adapty Flutter SDK 提供两种展示流程和付费墙的方式: - **独立页面** - **嵌入式组件** ## 作为独立屏幕展示 \{#present-as-standalone-screen\} 要将流程或付费墙作为独立屏幕显示,请对通过 [`createFlowView`](flutter-get-pb-paywalls#fetch-the-view-configuration) 方法创建的 `view` 调用 `view.present()` 方法。每个 `view` 只能展示一次:关闭后,该 view 会从内存中释放。如需再次显示流程或付费墙,请重新调用 `createFlowView` 创建新的 `view` 实例。 ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 关闭流程或付费墙 \{#dismiss-the-flow-or-paywall\} 当需要以编程方式关闭流程或付费墙时,请使用 `dismiss()` 方法: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note 关闭视图会将其从内存中释放——已关闭的视图无法再次呈现。请改用 `createFlowView` 创建新的视图。 ::: ### 显示对话框 \{#show-dialog\} 在 Android 上展示流程或付费墙视图时,请使用此方法替代原生的弹窗对话框。在 Android 上,普通弹窗会出现在视图背后,用户无法看到。此方法可确保对话框在所有平台上都能正确显示在流程或付费墙的上方。 ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置流程或付费墙在 iOS 上的展示方式。该参数接受 `AdaptyUIIOSPresentationStyle.fullScreen`(默认值)或 `AdaptyUIIOSPresentationStyle.pageSheet`。 ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## 嵌入到 widget 层级中 \{#embed-in-widget-hierarchy\} 如需将流程或付费墙嵌入到现有的 widget 树中,可以直接在 Flutter widget 层级里使用 `AdaptyUIFlowPlatformView` widget。 ```dart showLineNumbers title="Flutter" AdaptyUIFlowPlatformView( flow: flow, // The flow object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidReceiveError: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note 对于 Android 平台视图,请确保你的 `MainActivity` 继承自 `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv4> <SDKv3> 如果你已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示它。此类付费墙已包含展示内容及展示方式的完整配置。 :::warning 本指南仅适用于**新版付费墙编辑工具**创建的付费墙,需要 SDK v3.2.0 或更高版本。不同版本付费墙编辑工具设计的付费墙及远程配置付费墙的展示流程各有不同。 - 如需展示**远程配置付费墙**,请参阅[展示远程配置设计的付费墙](present-remote-config-paywalls-flutter)。 ::: Adapty Flutter SDK 提供两种付费墙展示方式: - **独立页面** - **嵌入式组件** ## 作为独立页面展示 \{#present-as-standalone-screen\} 要将付费墙作为独立页面展示,请在由 [`createPaywallView`](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示付费墙,请重新调用 `createPaywallView` 创建新的 `view` 实例。 :::warning 重复使用同一个 `view` 而不重新创建,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 关闭付费墙 \{#dismiss-the-paywall\} 当你需要以编程方式关闭付费墙时,请使用 `dismiss()` 方法: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### 显示对话框 \{#show-dialog\} 在 Android 上展示付费墙视图时,请使用此方法代替原生的提示对话框。在 Android 上,普通提示框会出现在付费墙视图的后面,导致用户看不到它。此方法可确保对话框在所有平台上都能正确显示在付费墙的上方。 ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置付费墙在 iOS 上的呈现方式。该参数接受 `AdaptyUIIOSPresentationStyle.fullScreen`(默认值)或 `AdaptyUIIOSPresentationStyle.pageSheet` 两个值。 ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## 嵌入到组件层级中 \{#embed-in-widget-hierarchy\} 如需将付费墙嵌入到现有的组件树中,可以直接在 Flutter 组件层级里使用 `AdaptyUIPaywallPlatformView` 组件。 ```dart showLineNumbers title="Flutter" AdaptyUIPaywallPlatformView( paywall: paywall, // The paywall object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidFailRendering: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note 要使 Android 平台视图正常工作,请确保你的 `MainActivity` 继承自 `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv3> --- # File: flutter-handle-paywall-actions --- --- title: "在 Flutter SDK 中响应按钮操作" description: "使用 Adapty 在 Flutter 中处理付费墙按钮操作,提升应用变现效果。" --- <SDKv4> 如果你正在使用 Adapty 编辑工具构建流程或付费墙,务必正确设置按钮: 1. 在[编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理每个已分配的操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **关闭视图和打开 URL 由默认的 `flowViewDidPerformAction` 实现自动处理**,SDK 本身负责处理购买和恢复操作。其他所有按钮动作,例如登录或打开另一个流程,则需要在应用代码中实现相应的响应逻辑。请注意,对*已完成*购买和恢复操作的响应发生在必要的观察者回调中——详见[处理流程和付费墙事件](flutter-handling-events)。 ::: ## 关闭流程和付费墙 \{#close-flows-and-paywalls\} 要添加一个关闭流程或付费墙的按钮,请在编辑工具中添加一个按钮,并为其分配 **Close** 操作。无需编写任何代码:默认的 `flowViewDidPerformAction` 实现会在收到 `CloseAction` 时自动关闭视图。 :::info Android 系统的**返回**按钮不再默认关闭视图。它会以 `AndroidSystemBackAction` 的形式传递给 `flowViewDidPerformAction`——如果你希望返回按钮能够关闭流程或付费墙,请自行处理该事件。 ::: 如需自定义行为,可覆写 `flowViewDidPerformAction`——例如,像 v3 中那样,在用户点击 Android 系统返回按钮时关闭视图: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } ``` :::warning 覆盖 `flowViewDidPerformAction` 会完全替换默认实现——如果你希望保留默认的关闭和打开 URL 行为,请保留 `CloseAction` 和 `OpenUrlAction` 的处理逻辑。 ::: ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果您想添加一组链接(例如使用条款和购买恢复),请在编辑工具中添加 **Link** 元素,并以与具有 **Open URL** 操作的按钮相同的方式处理它。 ::: 要添加一个打开链接的按钮(例如**使用条款**或**隐私政策**),请在编辑工具中添加一个按钮,为其分配 **Open URL** 操作,然后输入您想要打开的 URL。 无需编写代码:`flowViewDidPerformAction` 的默认实现会通过 `AdaptyUI().openUrl` 以原生方式打开 URL,并遵循看板中设置的应用内浏览器或外部浏览器偏好。 默认行为已能满足大多数场景的需求。如果你仍希望自行处理 URL 的打开方式,可以覆盖该处理器: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL in whatever way fits your app break; default: break; } } ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Login** 操作。 2. 在应用代码中,实现一个 `login` 操作的处理器,用于识别当前用户。 ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并为其设置一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现对应的处理逻辑。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮来展示另一个流程或付费墙: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another flow or paywall break; default: break; } } ``` </SDKv4> <SDKv3> 如果您正在使用 Adapty 付费墙编辑工具构建付费墙,正确设置按钮至关重要: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配一个已有的操作,或创建一个自定义操作 ID。 2. 在应用代码中编写逻辑,处理您分配的每个操作。 本指南介绍如何在代码中处理自定义操作和预设操作。 :::warning **只有购买和恢复购买会被自动处理。** 其他所有按钮操作(例如关闭付费墙或打开链接)都需要在应用代码中实现相应的响应逻辑。 ::: ## 关闭付费墙 \{#close-paywalls\} 要添加一个关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,为 `CloseAction` 和 `AndroidSystemBackAction` 操作实现处理程序。 :::info 在 Flutter SDK 中,`CloseAction` 和 `AndroidSystemBackAction` 操作默认会触发关闭付费墙。不过,如有需要,你可以在代码中覆盖此行为。例如,关闭一个付费墙可能会触发打开另一个付费墙。 ::: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; default: break; } } ``` ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在付费墙编辑工具中添加 **Link** 元素,并像处理带有 **Open URL** 操作的按钮一样进行处理。 ::: 要添加一个从付费墙打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入要打开的 URL。 2. 在你的应用代码中,实现一个 `openUrl` 操作的处理器,用于在浏览器中打开接收到的 URL。 ```dart // You have to install url_launcher plugin in order to handle urls: // https://pub.dev/packages/url_launcher void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case OpenUrlAction(url: final url): final Uri uri = Uri.parse(url); launchUrl(uri, mode: LaunchMode.inAppBrowserView); break; default: break; } } ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 动作。 2. 在应用代码中,实现一个用于标识用户的 `login` 动作处理器。 ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他任意操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并为其指定一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现对应的处理逻辑。 例如,如果你有另一组订阅方案或一次性购买产品,可以添加一个按钮来展示另一个付费墙: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another paywall break; default: break; } } ``` </SDKv3> --- # File: flutter-handling-events --- --- title: "Flutter - 处理 flow 与付费墙事件" description: "了解如何在 Flutter 中使用 Adapty 处理订阅相关事件,从而有效追踪用户交互。" --- <SDKv4> :::important 本指南涵盖购买、恢复、产品选择和渲染的事件处理。关闭视图和打开链接由默认的 `flowViewDidPerformAction` 实现处理——如需覆盖这些行为或处理自定义按钮动作,请参阅[按钮动作处理指南](flutter-handle-paywall-actions)。 ::: 通过编辑工具配置的流程和付费墙无需额外代码即可完成购买和恢复购买操作。但它们会产生一些事件供你的应用响应,包括按钮点击(关闭按钮、URL、产品选择等)以及流程或付费墙上与购买相关的操作通知。请参阅以下内容了解如何响应这些事件。 如需在移动应用中控制或监控流程或付费墙屏幕上发生的过程,请实现 `AdaptyUIFlowsEventsObserver` 方法,并在展示任何屏幕之前设置观察者: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(this); ``` 有三个观察者方法是**必须实现**的——缺少它们类将无法编译:`flowViewDidFinishPurchase`、`flowViewDidFinishRestore` 和 `flowViewDidReceiveError`。其他方法均为可选。若要移除已设置的观察者,请向 `setFlowsEventsObserver` 传入 `null`。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: 以下事件示例展示了每个对象上可用的属性,注释中提供了示意性的参考值。 ### 用户生成的事件 \{#user-generated-events\} #### 视图已出现 \{#view-appeared\} 当流程或付费墙视图显示在屏幕上时,将调用此方法。 :::note 在 iOS 上,当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button),且网页付费墙在应用内浏览器中打开时,也会调用此方法。 ::: ```dart showLineNumbers title="Flutter" void flowViewDidAppear(AdaptyUIFlowView view) { } ``` #### 视图已消失 \{#view-disappeared\} 当流程或付费墙视图从屏幕上关闭时,将调用此方法。 :::note 在 iOS 上,当从付费墙中在应用内浏览器打开的 [web 付费墙](web-paywall#step-2a-add-a-web-purchase-button) 从屏幕消失时,也会触发此方法。 ::: ```dart showLineNumbers title="Flutter" void flowViewDidDisappear(AdaptyUIFlowView view) { } ``` #### 产品选择 \{#product-selection\} 当某个产品被选中购买(由用户或系统触发)时,将调用此方法: ```dart showLineNumbers title="Flutter" void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### 开始购买 \{#started-purchase\} 当用户发起购买流程时,将调用此方法: ```dart showLineNumbers title="Flutter" void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### 完成购买 \{#finished-purchase\} 此方法为**必需**。当购买成功、用户取消购买或购买处于待处理状态时,系统会调用此方法: ```dart showLineNumbers title="Flutter" void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> :::info 与 v3 不同,此方法没有默认行为——视图不再在购买成功后自动关闭。请自行决定后续操作:继续流程或调用 `view.dismiss()`。有关关闭屏幕的详细信息,请参阅[响应按钮操作](flutter-handle-paywall-actions)。 ::: #### 完成 Web 支付导航 \{#finished-web-payment-navigation\} 此方法在尝试为特定产品打开 [Web 付费墙](web-paywall)后调用,包括导航成功和失败的情况: ```dart showLineNumbers title="Flutter" void flowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **参数:** | 参数 | 描述 | |:------------|:---------------------------------------------------------------------------------------------------| | **product** | 打开 Web 付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 | | **error** | 如果 Web 付费墙导航失败,则为 `AdaptyError` 对象;导航成功时为 `null`。 | #### 购买失败 \{#failed-purchase\} 当购买失败时(例如由于支付问题或网络错误),此方法将被调用。对于用户主动取消或待处理的交易,**不会**触发此方法——这些情况由 `flowViewDidFinishPurchase` 处理: ```dart showLineNumbers title="Flutter" void flowViewDidFailPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` #### 开始恢复购买 \{#started-restore\} 如果用户发起恢复流程,此方法将被调用: ```dart showLineNumbers title="Flutter" void flowViewDidStartRestore(AdaptyUIFlowView view) { } ``` #### 恢复成功 \{#successful-restore\} 此方法为**必需**。如果购买恢复成功,将会调用此方法: ```dart showLineNumbers title="Flutter" void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> 如果用户已拥有所需的 `accessLevel`,我们建议关闭该页面。请参阅[订阅状态](flutter-listen-subscription-changes)了解如何检查,以及[响应按钮操作](flutter-handle-paywall-actions)了解如何关闭页面。 #### 恢复失败 \{#failed-restore\} 如果恢复购买失败,将触发此方法: ```dart showLineNumbers title="Flutter" void flowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) { } ``` ### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果你在初始化时没有传入产品数组,AdaptyUI 会自动从服务器获取所需对象。如果该操作失败,AdaptyUI 会通过调用以下方法来上报错误: ```dart showLineNumbers title="Flutter" void flowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) { } ``` #### 视图错误 \{#view-errors\} 此方法为**必填项**。它替代了 v3 的 `paywallViewDidFailRendering` 方法:界面渲染过程中发生的错误以及其他视图错误,都会通过调用此方法来上报。实现此方法后,关闭视图的逻辑由你来决定——我们建议在发生此类错误时关闭视图,这也是未设置观察者时 SDK 内置默认行为的处理方式: ```dart showLineNumbers title="Flutter" void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { // log the error and dismiss the broken view view.dismiss(); } ``` 正常情况下不应出现渲染错误,如果遇到,请告知我们。 ### 分析事件 \{#analytics-events\} 可选的 `flowViewDidReceiveAnalyticEvent` 方法用于接收来自流程的自定义分析事件。目前流程尚未向您的代码发送此类事件,因此无需实现该方法。 ### 在观察者模式下处理购买 \{#handle-purchases-in-observer-mode\} 如果你以[观察者模式](implement-observer-mode-flutter)激活了 SDK,并展示了由 Adapty 渲染的流程或付费墙,SDK 不会自动为你发起购买。当用户点击购买或恢复购买按钮时,SDK 会调用你的 `AdaptyUIObserverModeResolver`。完整的配置说明请参阅[在观察者模式下展示流程](flutter-present-flows-in-observer-mode)。 ### 处理系统请求 \{#handle-system-requests\} `AdaptyUISystemRequestsHandler`(通过 `AdaptyUI().setSystemRequestsHandler(...)` 注册)用于处理来自流程的系统请求:操作系统权限提示(如推送通知或相机访问)以及 App Store 评价请求。目前流程尚未触发此类请求,因此无需注册处理程序。 如果你注册了处理器,请注意:`handlePermission` 是该类的必需方法——用你自己的代码请求权限,然后返回 `AdaptyUIPermissionResult.granted()` 或 `AdaptyUIPermissionResult.denied()`;`handleAppReviewRequest` 是可选的。 </SDKv4> <SDKv3> :::important 本指南介绍购买、恢复、产品选择和付费墙渲染的事件处理。你还必须实现按钮处理(关闭付费墙、打开链接等)。详情请参阅[按钮操作处理指南](flutter-handle-paywall-actions)。 ::: 使用[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会产生一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上与购买相关操作的通知。请参阅以下内容了解如何响应这些事件。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.0 或更高版本。 ::: 要控制或监控移动应用中付费墙屏幕上发生的事件,请实现 `AdaptyUIPaywallsEventsObserver` 的相关方法,并在展示任何屏幕之前设置观察者: ```dart showLineNumbers title="Flutter" AdaptyUI().setPaywallsEventsObserver(this); ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: 以下事件示例展示了每个对象上可用的属性,注释中提供了示例值。 ### 用户生成的事件 \{#user-generated-events\} #### 付费墙已显示 \{#paywall-appeared\} 当付费墙视图显示在屏幕上时,会调用此方法。 :::note 在 iOS 上,当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button),且网页付费墙在应用内浏览器中打开时,也会调用此方法。 ::: ```dart showLineNumbers title="Flutter" void paywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### 付费墙已关闭 \{#paywall-disappeared\} 当付费墙视图从屏幕上消失时,会调用此方法。 :::note 在 iOS 上,当从付费墙中打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)在应用内浏览器中消失时,也会触发此方法。 ::: ```dart showLineNumbers title="Flutter" void paywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### 商品选择 \{#product-selection\} 当用户或系统选中某个商品进行购买时,会触发此方法: ```dart showLineNumbers title="Flutter" void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### 开始购买 \{#started-purchase\} 当用户发起购买流程时,将触发此方法: ```dart showLineNumbers title="Flutter" void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### 购买完成 \{#finished-purchase\} 当购买成功、用户取消购买或购买处于待处理状态时,将调用此方法: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> 我们建议在这种情况下关闭该页面。有关关闭付费墙页面的详细信息,请参阅[响应按钮操作](flutter-handle-paywall-actions)。 #### 完成 Web 支付跳转 \{#finished-web-payment-navigation\} 此方法在尝试为特定产品打开 [Web 付费墙](web-paywall)后触发,无论跳转成功还是失败均会调用: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **参数:** | 参数 | 描述 | |:------------|:---------------------------------------------------------------------------------------------------| | **product** | 打开网页付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 | | **error** | 如果网页付费墙跳转失败,则为 `AdaptyError` 对象;跳转成功则为 `null`。 | <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { // product — AdaptyPaywallProduct?: product?.vendorProductId; // 'premium_monthly' if (error == null) { // navigation succeeded } else { // error — AdaptyError: error.code; // AdaptyErrorCode.networkFailed (2005) error.message; // 'Network request failed' error.detail; // platform-specific underlying error, or null } } ``` </Details> #### 购买失败 \{#failed-purchase\} 当购买失败时(例如因为支付问题或网络错误)会触发此方法。它**不会**在用户主动取消或待处理交易时触发——这些情况由 `paywallViewDidFinishPurchase` 处理: ```dart showLineNumbers title="Flutter" void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' // error — AdaptyError: error.code; // AdaptyErrorCode.productPurchaseFailed (1006) error.message; // 'Product purchase failed.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### 开始恢复购买 \{#started-restore\} 当用户发起恢复购买流程时,将触发此方法: ```dart showLineNumbers title="Flutter" void paywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### 恢复成功 \{#successful-restore\} 如果恢复购买成功,将调用此方法: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> 如果用户已拥有所需的 `accessLevel`,我们建议关闭该页面。请参阅[订阅状态](flutter-listen-subscription-changes)主题了解如何检查,以及[响应按钮操作](flutter-handle-paywall-actions)主题了解如何关闭付费墙页面。 #### 恢复失败 \{#failed-restore\} 如果恢复购买失败,将调用以下方法: ```dart showLineNumbers title="Flutter" void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.receiveRestoredTransactionsFailed (1011) error.message; // 'Error occurred in the process of restoring purchases.' error.detail; // platform-specific underlying error, or null } ``` </Details> ### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果你在初始化时未传入产品数组,AdaptyUI 会自行从服务器获取所需对象。若此操作失败,AdaptyUI 将通过调用以下方法报告错误: ```dart showLineNumbers title="Flutter" void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.productRequestFailed (1002) error.message; // 'Unable to fetch available In-App Purchase products at the moment.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### 渲染错误 \{#rendering-errors\} 如果在界面渲染过程中发生错误,系统会通过调用此方法来上报该错误。默认情况下(自 v3.15.2 起),当渲染错误发生时,付费墙会自动关闭,但你可以根据需要覆盖此行为。 ```dart showLineNumbers title="Flutter" void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // Default behavior: view.dismiss() // Override with custom logic if needed, for example: // - Log the error // - Show an error message to the user } ``` <Details> <summary>事件示例(点击展开)</summary> ```dart void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.jsException (4105) error.message; // 'An exception was thrown from JS during AdaptyUI flow execution.' error.detail; // platform-specific underlying error, or null // Default behavior: view.dismiss() } ``` </Details> 正常情况下不应出现此类错误,如果遇到,请告知我们。 </SDKv3> --- # File: flutter-use-fallback-paywalls --- --- title: "Flutter - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况" --- :::warning 备用付费墙需要 Flutter SDK v2.11 或更高版本。 ::: 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} 1. 将备用配置文件添加到项目根目录下应用的 `assets` 目录中。 2. 在获取目标付费墙或用户引导**之前**调用 `.setFallback` 方法。 ```dart showLineNumbers title="Flutter" final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { await Adapty().setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 参数: | 参数 | 描述 | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **assetId** | 备用配置文件的路径。 | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: flutter-localizations-and-locale-codes --- --- title: "在 Flutter SDK 中使用本地化和语言区域代码" description: "管理应用本地化和语言区域代码,触达全球用户。" --- <SDKv4> ## 为什么这很重要 \{#why-this-is-important\} 当 Adapty 为流程选择本地化语言,以及你读取自定义付费墙的远程配置时,语言区域代码就会发挥作用。 语言区域代码较为复杂,在不同平台之间可能存在差异,因此 Adapty 在其支持的所有平台上统一采用一套内部标准。了解这一标准,有助于你预测用户最终会收到哪个本地化版本。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} Adapty 的语言代码采用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,各子标签之间用连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 查找与用户语言区域匹配的本地化内容时,会按以下步骤进行: 1. 将语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. Adapty 查找与完整语言区域代码完全匹配的本地化内容 3. 如果未找到匹配项,Adapty 取第一个连字符之前的子字符串(例如 `pt-br` 对应 `pt`),并查找匹配的本地化内容 4. 如果仍未找到匹配项,Adapty 返回默认的 `en` 本地化内容 因此,`'pt_BR'`、`pt-BR` 和 `pt-br` 最终都会解析为同一个本地化内容。 ## 实现本地化 \{#implementing-localizations\} 在 SDK v4 中,获取流程时无需传入语言区域代码。 - **付费墙编辑工具与流程构建器付费墙**:Adapty 会根据设备设置及你在构建器中配置的本地化内容自动解析语言区域。使用 `createFlowView` 渲染流程,无需传入语言区域代码。 - **自定义(远程配置)付费墙**:`getFlow` 会在 `flow.remoteConfigs` 中返回所有已配置的本地化内容。每个条目都包含一个 `locale` 代码及配置内容(`data` 字符串或已解析的 `dictionary`)。请自行实现回退逻辑,选择与用户匹配的条目: ```dart showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; // the first remote config, if present // read your values from config?.dictionary ``` 上述语言区域代码匹配规则描述了 Adapty 如何对每个远程配置中存储的 `locale` 代码进行规范化处理。 </SDKv4> <SDKv3> ## 为什么这很重要 \{#why-this-is-important\} 有几种情况下需要用到语言区域代码——例如,当你尝试为当前应用的本地化版本获取正确的付费墙时。 由于语言区域代码比较复杂,且在不同平台之间可能存在差异,我们为所有支持的平台制定了一套内部标准。正因为这些代码较为复杂,理解你究竟向我们的服务器发送了什么内容以获取正确的本地化版本,以及后续会发生什么,就变得尤为重要——这样你才能始终获得预期的结果。 ## Adapty 语言代码标准 \{#locale-code-standard-at-adapty\} 在语言代码方面,Adapty 采用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。例如:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 发来的语言区域代码请求并开始查找对应的付费墙本地化版本时,流程如下: 1. 将传入的语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. 查找与完整语言区域代码完全匹配的本地化版本 3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 取 `pt`),再次查找匹配的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 这样,发送 `'pt_BR'` 的 iOS 设备、发送 `pt-BR` 的 Android 设备,以及发送 `pt-br` 的其他设备,都会得到相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果你正在考虑本地化问题,很可能已经在项目中使用了本地化字符串文件。在这种情况下,我们建议在每个本地化文件中添加一个键值对,用于存储对应语言的 Adapty 语言代码。然后在调用 SDK 时提取该键的值,示例如下: ```dart showLineNumbers // 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files /* app_en.arb */ "adapty_paywalls_locale": "en", /* app_es.arb */ "adapty_paywalls_locale": "es", /* app_pt_br.arb */ "adapty_paywalls_locale": "pt-br", // 2. Extract and use the locale code final locale = AppLocalizations.of(context)!.adapty_paywalls_locale; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 这样,你就能完全掌控每位用户获取到的本地化内容。 ## 实现本地化的另一种方式 \{#implementing-localizations-the-other-way\} 你也可以不为每个本地化显式定义语言区域代码,同样能得到类似(但不完全相同)的结果。这种方式是从平台提供的其他对象中提取语言区域代码,如下所示: ```dart showLineNumbers final locale = Localizations.localeOf(context).languageCode; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 不过,出于以下几点原因,我们不推荐这种方式: 1. 在 iOS 上,首选语言和当前语言区域并不相同。如果希望正确选取本地化内容,你需要依赖 Apple 的内置逻辑(使用推荐的本地化字符串文件方案时开箱即用),或者自行重新实现该逻辑。 2. 很难预测 Adapty 服务器实际收到的内容。例如,在 iOS 上,设备可能会返回类似 `ar_OM@numbers='latn'` 这样的语言区域标识,并将其发送至我们的服务器。对于这个请求,你得到的不会是预期的 `ar-om` 本地化内容,而是 `ar`,这很可能不符合预期。 Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: flutter-web-paywall --- --- title: "在 Flutter SDK 中实现 web 付费墙" description: "设置 web 付费墙,无需支付 App Store 费用和审核即可获得收入。" --- :::important 在开始之前,请确保您已[在看板中配置了 web 付费墙](web-paywall),并安装了 Adapty SDK 3.6.1 或更高版本。 ::: 如果您使用的是自行开发的付费墙,则需要使用 SDK 方法处理 web 付费墙。`.openWebPaywall` 方法: 1. 生成唯一 URL,使 Adapty 能够将向特定用户展示的付费墙与他们被重定向到的网页关联起来。 2. 跟踪用户何时返回应用,然后以短时间间隔请求 `.getProfile`,以确定用户画像的访问权限是否已更新。 这样,如果付款成功且访问权限已更新,订阅几乎会立即在应用中激活。 ```dart showLineNumbers title="Flutter" try { await Adapty().openWebPaywall(product: <YOUR_PRODUCT>); // The web paywall will be opened } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall(product)` 根据付费墙生成 URL,并将产品数据附加到 URL 中。 2. `openWebPaywall(paywall)` 根据付费墙生成 URL,但不附加产品数据。当 Adapty 付费墙中的产品与网页付费墙中的产品不同时,请使用此版本。 在 SDK v4 中,`paywall` 参数接受 `AdaptyFlowPaywall`——即获取到的 flow 的付费墙变体。在对其进行索引之前,请先检查 `flow.paywalls` 不为空,例如 `flow.paywalls[0]`。 ::: #### 处理错误 \{#handle-errors\} | 错误 | 描述 | 建议操作 | |-----------------------------------------|-----------------------------------|---------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | 付费墙未配置 web 购买 URL | 检查付费墙是否已在 Adapty 看板中正确配置 | | AdaptyError.productWithoutPurchaseUrl | 产品没有 web 购买 URL | 在 Adapty 看板中验证产品配置 | | AdaptyError.failedOpeningWebPaywallUrl | 无法在浏览器中打开 URL | 检查设备设置或提供其他购买方式 | | AdaptyError.failedDecodingWebPaywallUrl | 无法正确编码 URL 中的参数 | 验证 URL 参数是否有效且格式正确 | ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} :::important 从 Adapty SDK v3.15 开始支持在应用内浏览器中打开网页付费墙。 ::: 默认情况下,网页付费墙会在外部浏览器中打开。 为了提供流畅的用户体验,你可以在应用内浏览器中打开网页付费墙。这样,网页购买页面会直接显示在你的应用内,用户无需切换应用即可完成交易。 要启用此功能,请将 `in` 参数设置为 `.inAppBrowser`: ```dart showLineNumbers try { await Adapty().openWebPaywall( product: <YOUR_PRODUCT>, openIn: AdaptyWebPresentation.inAppBrowser, ); // The web paywall will be opened in the in-app browser } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` --- # File: flutter-troubleshoot-paywall-builder --- --- title: "在 Flutter SDK 中排查付费墙编辑工具问题" description: "在 Flutter SDK 中排查付费墙编辑工具问题" --- 本指南帮助您解决在 Flutter SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:`createPaywallView` 方法无法获取付费墙配置。 **原因**:该付费墙未在付费墙编辑工具中启用设备展示。 **解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。 <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 付费墙浏览次数过大 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙浏览次数显示为预期值的两倍。 **原因**:你可能在代码中调用了 `logShowFlow`(Flutter SDK v4+)/ `logShowPaywall`,如果你正在使用付费墙编辑工具或 Flow Builder,这会导致浏览次数重复计算。对于使用这些工具构建的流程和付费墙,分析数据会自动追踪,无需手动调用此方法。 **解决方案**:如果你正在使用付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(Flutter SDK v4+)/ `logShowPaywall`。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。 **解决方案**:如有需要,请参考[迁移指南](flutter-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: flutter-present-flows-in-observer-mode --- --- title: "在 Flutter SDK 的 Observer 模式下展示流程" description: "在 Flutter 应用中以 Observer 模式展示流程和付费墙编辑工具付费墙,同时使用自己的代码处理购买。" --- 如果你使用编辑工具自定义了流程或付费墙,就无需在移动应用代码中手动处理其渲染逻辑来向用户展示它。这类流程或付费墙本身已包含展示内容和展示方式的完整定义。 :::warning 本节仅适用于[观察者模式](observer-vs-full-mode)。如果你不在观察者模式下工作,请参阅[展示流程与付费墙](flutter-present-paywalls)主题。 ::: :::info 此功能需要 Adapty Flutter SDK 4.0 或更高版本——此前仅在 iOS 和 Android 原生 SDK 中提供。请参阅[迁移指南](migration-to-flutter-sdk-v4)进行升级。 ::: <details> <summary>开始展示流程前(点击展开)</summary> 1. 完成 Adapty 与 [App Store](initial_ios) 以及[与 Google Play](initial-android) 的初始集成。 2. 安装并配置 Adapty SDK,务必将 `observerMode` 参数设置为 `true`。请参阅 [Flutter SDK 安装指南](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk)。 3. 在 Adapty 看板中[创建产品](create-product)。 4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。 5. [创建版位并将流程或付费墙分配给对应版位](create-placement)。 6. 在移动端应用代码中[获取流程及其配置](flutter-get-pb-paywalls)。 </details> 在观察者模式下,SDK 不会代您发起购买。当用户点击 Adapty 渲染的流程或付费墙中的购买或恢复按钮时,SDK 会调用您的 `AdaptyUIObserverModeResolver`——请在其中用您自己的代码执行购买或恢复操作。 1. 实现 `AdaptyUIObserverModeResolver`: ```dart showLineNumbers title="Flutter" class MyObserverModeResolver extends AdaptyUIObserverModeResolver { @override void observerModeDidInitiatePurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, void Function() onStartPurchase, void Function() onFinishPurchase, ) { onStartPurchase(); // the view shows its loading indicator // make the purchase with your own code, then: onFinishPurchase(); // the view hides the loading indicator } @override void observerModeDidInitiateRestore( AdaptyUIFlowView view, void Function() onStartRestore, void Function() onFinishRestore, ) { onStartRestore(); // restore purchases with your own code, then: onFinishRestore(); } } ``` `observerModeDidInitiatePurchase` 方法会通知你用户已发起购买,`observerModeDidInitiateRestore` 则通知你用户已发起恢复购买。收到通知后,请触发你自定义的购买或恢复流程。 另外,请记得调用以下回调方法,将购买或恢复流程的进展通知给 AdaptyUI。这对于正确的流程行为(例如显示加载动画等)是必要的: | 回调函数 | 描述 | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | 应调用此回调函数,通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 应调用此回调函数,通知 AdaptyUI 购买已完成。 | | onStartRestore() | 应调用此回调函数,通知 AdaptyUI 恢复购买已开始。 | | onFinishRestore() | 应调用此回调函数,通知 AdaptyUI 恢复购买已完成。 | 2. 在展示任何界面之前注册解析器: ```dart showLineNumbers title="Flutter" AdaptyUI().setObserverModeResolver(MyObserverModeResolver()); ``` 3. 按常规方式创建并展示流程视图:[获取流程并创建其视图](flutter-get-pb-paywalls),然后[展示它](flutter-present-paywalls)。无需额外参数——一旦注册了解析器,所有由 Adapty 渲染的流程或付费墙都会通过它来处理购买和恢复操作。 :::warning 别忘了[上报交易并将其关联到付费墙](report-transactions-observer-mode-flutter)。否则,Adapty 将无法识别该交易,也无法确定购买来源的付费墙。 ::: --- # File: flutter-quickstart-manual --- --- title: "在 Flutter SDK 的自定义付费墙中启用购买功能" description: "将 Adapty SDK 集成到您的自定义 Flutter 付费墙中,以启用应用内购买。" --- 本指南介绍如何将 Adapty 集成到自定义付费墙中。您可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。本指南使用 Adapty Flutter SDK v4 API——如果您使用的是 v3,请参阅[迁移指南](migration-to-flutter-sdk-v4)了解对应的方法名称。 :::important **本指南适用于需要实现自定义付费墙的开发者。** 如果你想以最简便的方式开启购买功能,请使用 [Adapty 付费墙编辑工具](flutter-quickstart-paywalls)。借助付费墙编辑工具,你可以在无代码可视化编辑器中创建付费墙,Adapty 自动处理所有购买逻辑,无需重新发布应用即可测试不同设计方案。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,你需要了解以下三个核心概念: - [**产品**](product) – 用户可以购买的一切内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一途径,但这种设计让你无需修改应用代码即可调整产品、价格和优惠。在 SDK v4 中,版位的付费墙变体由 **flow** 对象承载——你获取一个 flow,然后查询其中的产品。 - [**版位**](placements) – 应用中展示付费墙的位置和时机(例如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求对应内容。这样就能轻松运行 A/B 测试,并向不同用户展示不同的付费墙。 确保你理解这些概念,即使你使用的是自定义付费墙。简单来说,这些不过是你管理应用内销售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到一个**版位**中。这样才能获取你的产品。如需了解在看板中具体需要做什么,请参考[这里](quickstart)的快速入门指南。 ### 管理用户 \{#manage-users\} 您可以选择是否使用后端身份验证。 但是,Adapty SDK 对匿名用户和已识别用户的处理方式有所不同。请阅读[身份识别快速入门指南](flutter-quickstart-identify)以了解具体细节,并确保您正确处理用户信息。 ## 步骤 1:获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,你需要: 1. 通过将[版位](placements) ID 传入 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该流程的产品数组。 ```dart showLineNumbers Future<void> loadPaywall() async { try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final products = await Adapty().getPaywallProducts(flow: flow); // Use products to build your custom paywall UI } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## 第二步:处理购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,调用 `makePurchase` 方法并传入所选产品。该方法会处理购买流程并返回更新后的用户画像。 ```dart showLineNumbers Future<void> purchaseProduct(AdaptyPaywallProduct product) async { try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // Purchase successful, profile updated break; case AdaptyPurchaseResultUserCancelled(): // User canceled the purchase break; case AdaptyPurchaseResultPending(): // Purchase is pending (e.g., user will pay offline with cash) break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## 步骤 3:恢复购买 \{#step-3-restore-purchases\} 应用商店要求所有包含订阅的应用为用户提供恢复购买的途径。 当用户点击恢复按钮时,调用 `restorePurchases` 方法。这将把用户的购买历史与 Adapty 同步,并返回更新后的用户画像。 ```dart showLineNumbers Future<void> restorePurchases() async { try { final profile = await Adapty().restorePurchases(); // Restore successful, profile updated } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## 第四步:检查订阅状态 \{#step-4-check-the-subscription-status\} 购买或恢复购买后,检查用户的[访问等级](access-level),以决定是否显示付费墙或解锁付费功能。`makePurchase` 和 `restorePurchases` 方法已经返回更新后的用户画像;如果在应用的其他地方需要获取当前状态,请使用 `getProfile` 方法: ```dart showLineNumbers Future<bool> hasPremiumAccess() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['premium']?.isActive ?? false; } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } return false; } ``` 有关检查和监控订阅状态的更多方法(包括监听实时更新),请参阅[检查订阅状态](flutter-check-subscription-status)。 ## 下一步 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox) 或 [Google Play Store](testing-on-android) 中测试您的购买流程,确保能够从付费墙完成测试购买。如需了解生产环境中的完整实现示例,请参考我们示例应用中的 [PurchasesObserver](https://github.com/adaptyteam/AdaptySDK-Flutter/blob/master/example/lib/purchase_observer.dart),其中演示了包含完善的错误处理、UI 观察者及全面 SDK 集成的购买处理逻辑。 --- # File: fetch-paywalls-and-products-flutter --- --- title: "在 Flutter SDK 中获取远程配置付费墙的付费墙和产品" description: "在 Adapty Flutter SDK 中获取付费墙和产品,提升用户变现效果。" --- <SDKv4> 在展示远程配置和自定义付费墙之前,你需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。有关获取流程和付费墙编辑工具自定义付费墙的指导,请参阅[获取流程与付费墙](flutter-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在你的移动应用中开始获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建你的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-flutter)。 </details> ## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被整合到付费墙中,让你能够在移动应用的特定版位中展示它们。 要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果一个付费墙今天返回两个产品,明天返回三个,则无需修改代码即可全部展示。 ::: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储付费墙:上述定期更新的缓存,以及[备用付费墙](flutter-use-fallback-paywalls)。我们还使用 CDN 来加快付费墙的获取速度,并在 CDN 不可用时启用独立的备用服务器。整套系统旨在确保您始终能获取最新版本的付费墙,同时在网络连接不稳定的情况下也能保持可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个不同请求组成。</p> | :::note 在 v4 中,`getFlow` 不接受 `locale` 参数。对于自定义付费墙,所有可用的本地化内容都会通过流程的远程配置(`flow.remoteConfigs`)返回——从中选取与用户设备或应用设置匹配的那个。详见[本地化与语言代码](flutter-localizations-and-locale-codes)。 ::: 返回参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含流程的标识符(`instanceIdentity`、`variationId`)、名称、版位、付费墙实验变体(`paywalls`)以及远程配置(`remoteConfigs`)。 | ## 获取产品 \{#fetch-products\} 获取到 flow 后,你可以查询与其对应的产品数组: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(flow: flow); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他几个属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) 对象中的这些属性。下面列出了最常用的属性,有关所有可用属性的完整详情,请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title(标题)** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户选择的商店国家/地区,而非设备本身的语言区域设置。 | | **Price(价格)** | 要显示本地化版本的价格,请使用 `product.price.localizedString`。该本地化基于设备的语言区域信息。您也可以使用 `product.price.amount` 以数字形式访问价格,该值以本地货币提供。要获取关联的货币符号,请使用 `product.price.currencySymbol`。 | | **Subscription Period(订阅周期)** | 要显示订阅周期(例如周、月、年等),请使用 `product.subscription?.localizedPeriod`。该本地化基于设备的语言区域设置。要以编程方式获取订阅周期,请使用 `product.subscription?.period`。从中可以访问 `unit` 枚举以获取时长(即天、周、月、年或未知)。`numberOfUnits` 值将获取周期单位的数量。例如,对于季度订阅,`unit` 属性中将显示 `AdaptyPeriodUnit.month`,`numberOfUnits` 属性中将显示 `3`。 | | **Introductory Offer(新用户优惠)** | 要显示标记或其他指示符以表明订阅包含新用户优惠,请查看 `product.subscription?.offer?.phases` 属性。这是一个最多可包含两个折扣阶段的列表:免费试用阶段和优惠价格阶段。每个阶段对象包含以下有用属性:<br/>• `paymentMode`:枚举值为 `AdaptyPaymentMode.freeTrial`、`AdaptyPaymentMode.payAsYouGo`、`AdaptyPaymentMode.payUpFront` 和 `AdaptyPaymentMode.unknown`。免费试用为 `AdaptyPaymentMode.freeTrial` 类型。<br/>• `price`:折扣价格(数字形式)。免费试用时此处为 `0`。<br/>• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `3 days`。<br/>• `subscriptionPeriod`:或者,您可以使用此属性获取优惠周期的各项详情。其工作方式与上一节描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期。 | ## 使用默认目标受众流程加快流程获取速度 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流程获取几乎是即时完成的,无需担心速度问题。但如果你的版位和目标受众数量较多,且用户网络连接较差,流程获取可能会比预期慢。在这种情况下,你可能希望展示一个默认流程,以确保用户体验流畅,而不是让用户看到空白页面。 要解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中**所有用户**目标受众的流程。但请务必了解,推荐的做法是通过 `getFlow` 方法来获取流程,详情请参阅上方的[获取流程信息](fetch-paywalls-and-products-flutter#fetch-flow-information)部分。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺陷: - **潜在的向后兼容性问题**:如果你需要针对不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么设计出同时兼容当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向能力**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家/地区、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的流程获取速度,请按以下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-flutter#fetch-flow-information)介绍的 `getFlow`。 ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时所指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时优先返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清除时才会被清空。</p> | </SDKv4> <SDKv3> 在展示远程配置和自定义付费墙之前,你需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。有关获取付费墙编辑工具自定义付费墙的指南,请参阅[获取付费墙编辑工具付费墙及其配置](flutter-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中开始获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建你的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-flutter)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是来自 App Store 和 Google Play 的产品组合。这些跨平台产品被整合到付费墙中,使您能够在特定的移动应用版位中展示它们。 要显示产品,您需要使用 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 :::important **不要硬编码产品 ID。** 您唯一需要硬编码的 ID 是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。您的应用必须动态处理这些变化——如果付费墙今天返回两个产品,明天返回三个,则无需修改代码即可显示所有产品。 ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个以减号(**-**)分隔的子标签组成的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言代码及其推荐用法的更多信息,请参阅[本地化与语言代码](flutter-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 将尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此方式,因为它确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户网络不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存保持不变,只有在卸载应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在两层中存储付费墙:上述定期更新的缓存和[备用付费墙](flutter-use-fallback-paywalls)。我们还使用 CDN 加快付费墙获取速度,并在 CDN 不可达时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的付费墙,同时在网络连接不佳的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作可能在内部由不同请求组成。</p> | 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)可能随时变化。请确保您的代码能够动态处理这些情况。 例如,如果您最初获取了 2 个产品,您的应用应显示这 2 个产品。但是,如果您后来获取了 3 个产品,您的应用应显示全部 3 个,而无需修改任何代码。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他几个属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,您可以查询与之对应的产品数组: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) 对象中的这些属性。以下列出了最常用的属性,完整属性列表请参阅上方链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 使用 `product.localizedTitle` 显示产品名称。请注意,本地化基于用户在商店中选择的国家/地区,而非设备本身的语言环境。 | | **Price** | 使用 `product.price.localizedString` 显示本地化价格,该本地化基于设备的语言环境信息。也可以通过 `product.price.amount` 以数字形式获取价格,其值以本地货币计。如需获取对应的货币符号,请使用 `product.price.currencySymbol`。 | | **Subscription Period** | 使用 `product.subscription?.localizedPeriod` 显示订阅周期(如周、月、年等),该本地化基于设备语言环境。如需以编程方式获取订阅周期,请使用 `product.subscription?.period`,通过 `unit` 枚举可获取周期长度(即 day、week、month、year 或 unknown),`numberOfUnits` 则表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `AdaptyPeriodUnit.month`,`numberOfUnits` 属性值为 `3`。 | | **Introductory Offer** | 如需显示徽章或其他标识以表明订阅包含新用户优惠,请查看 `product.subscription?.offer?.phases` 属性。这是一个列表,最多包含两个折扣阶段:免费试用阶段和新用户价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:枚举类型,可选值为 `AdaptyPaymentMode.freeTrial`、`AdaptyPaymentMode.payAsYouGo`、`AdaptyPaymentMode.payUpFront` 和 `AdaptyPaymentMode.unknown`。免费试用对应 `AdaptyPaymentMode.freeTrial` 类型。<br/>• `price`:折扣价格(数字形式)。免费试用时该值为 `0`。<br/>• `localizedNumberOfPeriods`:根据设备语言环境本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段显示为 `3 days`。<br/>• `subscriptionPeriod`:也可通过此属性获取优惠周期的具体信息,其使用方式与上一节描述的订阅周期一致。<br/>• `localizedSubscriptionPeriod`:按用户语言环境格式化的折扣订阅周期字符串。 | ## 使用默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙几乎可以立即获取,因此您无需担心加速此过程。但是,在您拥有大量目标受众和付费墙,且用户网络连接较弱的情况下,获取付费墙可能需要比预期更长的时间。在这种情况下,您可能希望显示默认付费墙,以确保流畅的用户体验,而不是完全不显示付费墙。 为解决此问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法为 **All Users** 目标受众获取指定版位的付费墙。但是,请务必了解,推荐的方式是通过 `getPaywall` 方法获取付费墙,详见上方的[获取付费墙信息](fetch-paywalls-and-products-flutter#fetch-paywall-information)部分。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在一些重要缺点: - **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)显示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一付费墙,这意味着您将失去个性化定向能力(包括基于国家/地区、营销归因或您自定义属性的定向)。 如果你愿意接受上述缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-flutter#fetch-paywall-information)介绍的 `getPaywall`。 ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note `getPaywallForDefaultAudience` 方法从 Flutter SDK 3.2.0 版本开始支持。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个以减号(**-**)分隔的子标签组成的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言代码及其推荐用法的更多信息,请参阅[本地化与语言代码](flutter-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 将尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此方式,因为它确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户网络不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存保持不变,只有在卸载应用或手动清理时才会被清除。</p> | </SDKv3> --- # File: present-remote-config-paywalls-flutter --- --- title: "在 Flutter SDK 中渲染远程配置设计的付费墙" description: "了解如何在 Adapty Flutter SDK 中展示远程配置付费墙,以个性化用户体验。" --- <SDKv4> 如果您已使用远程配置自定义了付费墙,则需要在移动应用的代码中实现渲染逻辑,以便向用户展示该付费墙。由于远程配置提供了灵活性以满足您的需求,您可以完全掌控付费墙视图所包含的内容及其呈现方式。我们提供了一个获取远程配置的方法,让您能够自主展示通过远程配置设置的自定义付费墙。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 在 v4 中,流程包含一个 `remoteConfigs` 列表——每个已配置的本地化对应一条远程配置。选取与用户语言区域匹配的条目并提取所需的值。请参阅[本地化与语言区域代码](flutter-localizations-and-locale-codes),了解如何选择正确的本地化。 ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // one entry per configured localization; fall back to the first one final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; final String? headerText = config?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 至此,获取所有必要数据后,就可以将其渲染并组合成一个美观的页面了。请确保设计能够适配各种手机屏幕尺寸和方向,在不同设备上提供流畅且友好的用户体验。 :::warning 请务必按照以下说明记录付费墙查看事件,以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 展示付费墙后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](flutter-making-purchases)。 我们建议[创建一个备用付费墙(即备用付费墙)](flutter-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,该备用付费墙将自动展示,确保在上述情况下依然能为用户提供流畅的体验。 ## 追踪付费墙展示事件 \{#track-paywall-view-events\} Adapty 帮助你衡量付费墙的表现。购买数据会自动收集,但付费墙的展示事件需要你手动记录,因为只有你知道用户何时看到了付费墙。 要记录付费墙展示事件,只需调用 `.logShowFlow(flow: flow)`,该事件将反映在漏斗和 A/B 测试的付费墙数据指标中。 :::important 如果你展示的是通过[编辑工具](adapty-paywall-builder)创建的流程或付费墙,则无需调用 `.logShowFlow(flow: flow)`。 ::: ```dart showLineNumbers try { await Adapty().logShowFlow(flow: flow); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:----------------------------------------------------------------------| | **flow** | 必填 | 一个 `AdaptyFlow` 对象。 | </SDKv4> <SDKv3> 如果你通过远程配置自定义了付费墙,则需要在移动应用代码中自行实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,付费墙视图的内容和样式也由你决定。我们提供了一个获取远程配置的方法,让你能够自主展示通过远程配置搭建的自定义付费墙。 ## 获取付费墙远程配置并展示它 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 至此,一旦获取到所有必要的值,就可以将它们渲染并组合成视觉效果出色的页面了。请确保设计能够适配各种手机屏幕尺寸和方向,在不同设备上提供流畅、易用的体验。 :::warning 请务必按照以下说明记录付费墙查看事件,以便 Adapty 分析系统能够采集漏斗和 A/B 测试所需的数据。 ::: 展示付费墙之后,接下来需要配置购买流程。当用户发起购买时,直接调用 `.makePurchase()` 并传入付费墙中的产品即可。有关 `.makePurchase()` 方法的详细说明,请参阅[发起购买](flutter-making-purchases)。 建议[创建备用付费墙(即备用付费墙)](flutter-use-fallback-paywalls)。当用户没有网络连接或无可用缓存时,将展示该备用付费墙,从而保证在这些情况下依然有流畅的体验。 ## 追踪付费墙展示事件 \{#track-paywall-view-events\} Adapty 可帮助您衡量付费墙的表现。虽然我们会自动收集购买数据,但付费墙展示事件需要您手动记录,因为只有您才知道用户何时看到了付费墙。 要记录付费墙展示事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗分析和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是通过[付费墙编辑工具](adapty-paywall-builder)创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) 对象。 | </SDKv3> --- # File: flutter-making-purchases --- --- title: "在 Flutter SDK 中进行应用内购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙,是为用户提供高级内容或服务访问权限的关键步骤。不过,如果你使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,仅展示付费墙本身就足以支持购买流程。 如果您不使用付费墙编辑工具,则必须使用名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成所需交易的入口。 如果您的付费墙为用户正在购买的产品设置了有效的促销活动,Adapty 将在购买时自动应用该优惠。 :::warning 请注意,新用户优惠仅在您使用付费墙编辑工具配置的付费墙时才会自动应用。 在其他情况下,您需要[在 iOS 上验证用户的新用户优惠资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布时被拒绝,还可能对本应享受新用户优惠的用户收取全价。 ::: 请确保您已完成[初始配置](quickstart),不要跳过任何步骤。否则,我们将无法验证购买。 ## 发起购买 \{#make-purchase\} :::note **使用[付费墙编辑工具](adapty-paywall-builder)?** 购买流程会自动处理——你可以跳过此步骤。 **需要分步指引?** 请查看[快速入门指南](flutter-implement-paywalls-manually),其中包含完整上下文的端到端实现说明。 ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) 对象。 | 响应参数: | 参数 | 描述 | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>请求成功后,响应中会包含此对象。[AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) 对象提供了用户在应用内的访问等级、订阅及一次性购买的完整信息。</p><p>请检查访问等级状态,以确认用户是否具备所需的应用访问权限。</p> | :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。此方法目前已被 Apple 弃用。 ::: ## 购买时更改订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而非续订当前订阅时,具体行为取决于所使用的应用商店: - 对于 App Store,订阅会在同一订阅组内自动更新。如果用户购买了某个订阅组的订阅,而此时已有另一个订阅组的有效订阅,则两个订阅将同时处于激活状态。 - 对于 Google Play,订阅不会自动更新。您需要按照以下说明在移动应用代码中手动处理切换逻辑。 在 Android 中,如需将订阅替换为另一个订阅,请在调用 `.makePurchase()` 方法时传入额外参数: ```dart showLineNumbers try { final subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( 'OLD_PRODUCT_ID', AdaptyAndroidSubscriptionUpdateReplacementMode.immediateWithTimeProration, ); final result = await Adapty().makePurchase( product: product, parameters: AdaptyPurchaseParameters( subscriptionUpdateParams: subscriptionUpdateParams, ), ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` 附加请求参数: | 参数 | 是否必填 | 描述 | | :--------------------------- | :------- |:--------------------------------------------------------------------------------------------------------| | **parameters** | required | 一个 `AdaptyPurchaseParameters` 对象,其 `subscriptionUpdateParams` 字段需设置为 [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.html) 对象。 | 您可以在 Google 开发者文档中了解更多关于订阅和替换模式的信息: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 关于替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更仅在当前订阅计费周期结束后才会生效。 ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 在应用中展示兑换码页面: ```dart showLineNumbers try { await Adapty().presentCodeRedemptionSheet(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` :::danger 根据我们的观察,部分应用的优惠码兑换页面可能无法稳定运行。建议直接将用户跳转至 App Store。 要执行此操作,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ### 管理预付费方案(Android) \{#manage-prepaid-plans-android\} 如果您的应用用户可以购买[预付费方案](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月的非续订订阅),您可以为预付费方案启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。 ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleEnablePendingPrepaidPlans(true), ); ``` --- # File: flutter-restore-purchase --- --- title: "在 Flutter SDK 的移动应用中恢复购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 在 iOS 和 Android 上恢复购买是一项功能,允许用户重新访问之前购买的内容(例如订阅或应用内购买),而无需再次付费。此功能对于那些可能已卸载并重新安装应用,或切换到新设备并希望访问之前购买内容的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您属于这种情况,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```dart showLineNumbers try { final profile = await Adapty().restorePurchases(); if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) { // successful access restore } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 响应参数: | 参数 | 描述 | |---------|-----------| | **Profile** | <p>一个 [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) 对象。该模型包含访问等级、订阅和非订阅购买的相关信息。</p><p>请检查**访问等级状态**以确定用户是否有权访问应用。</p> | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: implement-observer-mode-flutter --- --- title: "在 Flutter SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以在 Flutter SDK 中跟踪用户订阅事件。" --- 如果你已有自己的购买基础设施,暂时还不打算完全切换到 Adapty,可以了解一下[观察者模式](observer-vs-full-mode)。在基础形态下,观察者模式提供了高级分析功能,并能与归因和分析系统无缝集成。 如果这满足您的需求,只需: 1. 在配置 Adapty SDK 时,将 `observerMode` 参数设置为 `true` 以启用该功能。请参阅 [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) 的设置说明。 2. 将现有购买基础设施中的[交易上报](report-transactions-observer-mode-flutter)给 Adapty。 ## 观察者模式设置 \{#observer-mode-setup\} 如果你自己处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请开启观察者模式。 :::important 在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保你自己处理。 ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` 参数: | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | observerMode | 一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在 Observer 模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果你还想使用 Adapty 的付费墙和 A/B 测试功能,也是可以的——但在 Observer 模式下需要一些额外配置。除了上述步骤之外,你还需要完成以下操作: 1. 按照常规方式展示[远程配置付费墙](present-remote-config-paywalls-flutter)。 3. 将付费墙与购买交易[进行关联](report-transactions-observer-mode-flutter)。 :::tip 在 SDK v4 中,你也可以在 Observer 模式下展示 Adapty 渲染的流程和付费墙:注册一个 `AdaptyUIObserverModeResolver`,当用户点击对应按钮时,用你自己的代码执行购买或恢复操作。详见[在 Observer 模式下展示流程](flutter-present-flows-in-observer-mode)。 ::: --- # File: report-transactions-observer-mode-flutter --- --- title: "在 Flutter SDK 的观察者模式下上报交易" description: "在 Adapty 观察者模式下上报购买交易,用于 Flutter SDK 的用户洞察和收入跟踪。" --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+(当前版本)" default> 在观察者模式下,Adapty SDK 无法自动跟踪通过您现有购买系统完成的购买。您需要从应用商店上报交易。在发布应用之前,务必完成此设置,否则会导致分析数据出错。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别。 :::warning **请勿跳过交易上报!** 如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送至集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时携带 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```dart showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | <ul><li>iOS:交易的标识符。</li><li>Android:购买的字符串标识符 `purchase.getOrderId`,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x(旧版)" default> 在观察者模式下,Adapty SDK 无法自动跟踪通过您现有购买系统完成的购买。您需要从应用商店上报或恢复交易。在发布应用之前,务必完成此设置,否则会导致分析数据出错。 在两个平台上均使用 `reportTransaction` 显式上报每笔交易,并在 Android 上额外调用 `restorePurchases`,以确保 Adapty 能够识别该交易。 :::warning **请勿跳过交易上报和购买恢复!** 如果您不调用这些方法,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送至集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时携带 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```dart showLineNumbers // every time when calling transaction.finish() if (Platform.isAndroid) { try { await Adapty().restorePurchases(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | <ul><li>iOS,StoreKit 1:[SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</li><li>iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</li><li>Android:字符串标识符(purchase.getOrderId,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例)。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old2" label="Adapty SDK 3.2.x 及以下版本(旧版)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **上报交易** - 3.1.x 及以下版本会自动监听 App Store 中的交易,因此无需手动上报。 - 3.2 版本不支持观察者模式。 </TabItem> <TabItem value="kotlin" label="Android 及基于 Android 的跨平台" default> **上报交易** 使用 `restorePurchases` 在观察者模式下向 Adapty 上报交易,详情请参阅[在移动代码中恢复购买](flutter-restore-purchase)页面。 :::warning **请勿跳过交易上报!** 如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送至集成渠道。 ::: </TabItem> </Tabs> **将付费墙与交易关联** Adapty SDK 无法确定购买的来源,因为这部分由您自行处理。因此,如果您打算在观察者模式下使用付费墙和/或 A/B 测试,则需要在移动应用代码中将来自应用商店的交易与对应的付费墙关联起来。在发布应用之前,务必正确完成此设置,否则会导致分析数据出错。 ```dart final transactionId = transaction.transactionIdentifier final variationId = paywall.variationId try { await Adapty().setVariationId('transactionId', variationId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> </Tabs> --- # File: flutter-troubleshoot-purchases --- --- title: "排查 Flutter SDK 中的购买问题" description: "排查 Flutter SDK 中的购买问题" --- 本指南帮助您解决在 Flutter SDK 中手动实现购买时常见的问题。 ## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **问题**:`makePurchase` 方法成功完成,但用户的用户画像和订阅状态在 Adapty 中未更新。 **原因**:这通常表明 Google Play Store 设置不完整或存在配置问题。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\} **问题**:`makePurchase` 方法针对同一购买被多次调用。 **原因**:这通常发生在由于 UI 状态管理问题或用户快速交互导致购买流程被多次触发时。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## 观察者模式下的 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时出现 `AdaptyError.cantMakePayments`。 **原因**:在观察者模式下,您应在自己的代码中处理购买,而不是使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 进行购买,请关闭观察者模式。您需要二选一:要么使用 `makePurchase`,要么在观察者模式下自行处理购买。详情请参阅[实现观察者模式](implement-observer-mode-flutter)。 ## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **问题**:您收到来自 Google Play Store 的结算不可用错误。 **原因**:此错误与 Adapty 无关,是 Google Play 结算库的错误,表示该设备上结算服务不可用。 **解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查看并了解更多信息:[处理 BillingResult 响应代码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。 ## 未找到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:您遇到了找不到 `makePurchasesCompletionHandlers` 的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建一个新的沙盒用户并重试。这通常可以解决与沙盒相关的购买完成处理程序问题。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他购买相关问题。 **解决方案**:如有需要,请参考[迁移指南](flutter-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新版本的 SDK 中得到修复。 --- # File: flutter-identifying-users --- --- title: "在 Flutter SDK 中识别用户" description: "在 Adapty 中识别用户,以改善个性化订阅体验。" --- Adapty 会为每位用户创建一个内部 Profile ID。但如果您有自己的身份验证系统,则应设置您自己的 Customer User ID。您可以在 [用户画像](profiles-crm) 部分通过 Customer User ID 查找用户,也可以在 [服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 还会发送到所有集成渠道。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) ); } catch (e) { // handle the error } ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 在配置后设置客户用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以随时通过 `.identify()` 方法进行设置。最常见的使用场景是用户注册或登录后,从匿名用户切换为已认证用户时。 ```dart showLineNumbers try { await Adapty().identify(customerUserId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重新提交重要的用户数据 在某些情况下,例如用户重新登录账户时,Adapty 的服务器可能已经存储了该用户的信息。在这种情况下,Adapty SDK 会自动切换到新用户。如果你之前向匿名用户传递了任何数据(例如自定义属性或来自第三方网络的归因数据),则需要为已识别的用户重新提交这些数据。 还需要注意的是,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 登出与登录 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法将用户登出: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` 之后,您可以使用 `.identify()` 方法将用户重新登录。 ## 分配 `appAccountToken`(iOS)\{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,用于将 App Store 交易与你的内部用户身份关联起来。 StoreKit 会将此令牌与每笔交易绑定,方便你的后端将 App Store 数据与用户匹配。 为每位用户生成一个稳定的 UUID,并在同一账号的不同设备上复用它。 这样可以确保购买记录和 App Store 通知始终正确关联。 您可以通过两种方式设置令牌——在 SDK 激活时或在识别用户时。 :::important 您必须始终将 `appAccountToken` 与 `customerUserId` 一起传递。 如果仅传递令牌,该令牌将不会包含在交易中。 ::: ```dart showLineNumbers // 在配置时: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN") ); } catch (e) { // 处理错误 } // 或在识别用户时 try { await Adapty().identify(customerUserId, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN"); } on AdaptyError catch (adaptyError) { // 处理错误 } catch (e) { } ``` ### 设置混淆账户 ID(Android)\{#set-obfuscated-account-ids-android\} Google Play 在某些使用场景下要求提供混淆账户 ID,以增强用户隐私和安全性。这些 ID 帮助 Google Play 识别购买行为,同时保持用户信息匿名,对于防欺诈和数据分析尤为重要。 如果您的应用处理敏感用户数据,或需要遵守特定隐私法规,则可能需要设置这些 ID。混淆 ID 使 Google Play 能够在不暴露真实用户标识符的情况下追踪购买记录。 ```dart showLineNumbers // During configuration: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID") ); } catch (e) { // handle the error } // Or when identifying users try { await Adapty().identify(customerUserId, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ## 跨设备用户识别 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: flutter-setting-user-attributes --- --- title: "在 Flutter SDK 中设置用户属性" description: "了解如何在 Adapty 中设置用户属性以实现更好的目标受众细分。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。然后,您可以使用这些属性创建用户[市场细分](segments),或直接在 CRM 中查看它们。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setEmail("email@email.com") ..setPhoneNumber("+18888888888") ..setFirstName('John') ..setLastName('Appleseed') ..setGender(AdaptyProfileGender.other) ..setBirthday(DateTime(1970, 1, 3)); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 请注意,您之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 允许的键 `<Key>` 及其对应的值 `<Value>` 如下所示: | 键 | 值 | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | 枚举类型,允许的值为:`female`、`male`、`other` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 你可以设置自己的自定义用户属性,这些属性通常与应用的使用情况相关。例如,健身类应用可以记录每周的锻炼次数,语言学习类应用可以记录用户的知识水平等。你可以在市场细分中使用这些属性来创建精准的付费墙和优惠活动,也可以在分析中利用它们来找出哪些产品数据图表对收入影响最大。 ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 要删除已有的键,请使用 `.withRemoved(customAttributeForKey:)` 方法: ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 有时你需要查看之前已设置了哪些自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能已过时,因为用户属性可以随时从不同设备发送,服务器上的属性可能在上次同步后已被修改。 ::: ### 限制 \{#limits\} - 每个用户最多 30 个自定义属性 - 键名最长为 30 个字符。键名可以包含字母数字字符以及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,最多 50 个字符。 --- # File: flutter-listen-subscription-changes --- --- title: "在 Flutter SDK 中查看订阅状态" description: "在 Adapty 中追踪和管理用户订阅状态,提升 Flutter 应用的用户留存率。" --- 借助 Adapty,订阅状态管理变得轻而易举。你无需在代码中手动填入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可确认其订阅状态。 <details> <summary>开始检查订阅状态前的准备工作(点击展开)</summary> - iOS 请配置 [App Store Server Notifications](enable-app-store-server-notifications) - Android 请配置 [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) 对象的属性。我们建议在应用启动时获取用户画像,例如在[识别用户](flutter-identifying-users#setting-customer-user-id-on-configuration)时,之后在发生变化时及时更新。这样,你就可以直接使用已有的用户画像对象,而无需反复请求。 要接收用户画像更新通知,请按照下方[监听用户画像更新(包括访问等级)](flutter-listen-subscription-changes)部分的说明监听用户画像变更。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 使用 `.getProfile()` 方法从服务器获取访问等级: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 响应参数: | 参数 | 描述 | | --------- | ------------------------------------------------------------ | | Profile | <p>一个 [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) 对象。通常,你只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。</p><p></p><p>`.getProfile` 方法始终尝试请求 API,因此返回的结果是最新的。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。还需注意,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持数据的实时性。</p> | `.getProfile()` 方法可以获取用户画像,从中你可以了解访问等级的状态。一个应用可以拥有多个访问等级。例如,如果你有一个新闻应用,并对不同主题独立销售订阅,可以创建"sports"和"science"两个访问等级。但大多数情况下,你只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。 以下是检查默认"premium"访问等级的示例: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化时,Adapty 都会触发一个事件。 要接收来自 Adapty 的消息,您需要进行一些额外配置: ```dart showLineNumbers Adapty().didUpdateProfileStream.listen((profile) { // handle any changes to subscription state }); ``` Adapty 也会在应用启动时触发一个事件,此时将传递缓存中的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 内置了缓存机制,用于存储用户画像的订阅状态。这意味着即使服务器无法访问,也可以从缓存数据中获取用户画像的订阅状态信息。 但需要注意的是,无法直接从缓存中请求数据。SDK 每隔一分钟会定期向服务器查询,检查用户画像是否有任何更新或变更。如果存在修改(例如新的交易记录或其他更新),这些内容将同步到缓存数据中,以保持其与服务器的一致性。 --- # File: flutter-deal-with-att --- --- title: "在 Flutter SDK 中处理 ATT" description: "开始在 Flutter 上使用 Adapty,简化订阅设置与管理。" --- 如果您的应用使用了 AppTrackingTransparency 框架,并向用户展示应用跟踪授权请求,则应将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning 我们强烈建议您在该值发生变化时尽早发送,只有这样,数据才能及时传递到您所配置的集成渠道。 ::: --- # File: kids-mode-flutter --- --- title: "Flutter SDK 中的儿童模式" description: "轻松启用儿童模式,遵守 Apple 和 Google 政策。Flutter SDK 中不收集 IDFA、GAID 或广告数据。" --- 如果您的 Flutter 应用面向儿童,则必须遵守 [Apple](https://developer.apple.com/kids/) 和 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单步骤即可将其配置为符合这些政策,并顺利通过应用商店审核。 ## 需要做什么?\{#whats-required\} 你需要配置 Adapty SDK 以禁止收集以下信息: - [IDFA(广告主标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers)(iOS) - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)(Android) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用 customer user ID。以 `<FirstName.LastName>` 格式设置的 User ID 与使用邮箱地址一样,会被认定为收集个人数据。在儿童模式下,最佳做法是使用随机化或匿名化的标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。为此,请前往 [App settings](https://app.adapty.io/settings/general),然后在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 更新您的移动应用代码 \{#updates-in-your-mobile-app-code\} 为了遵守相关政策,请禁用用户 IDFA(iOS)、GAID/AAID(Android)以及 IP 地址的收集。 **Android:更新 SDK 配置** ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` ..withIpAddressCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` **iOS:在 SDK v4 中启用儿童模式** :::important 在 SDK v4 中,原生 iOS SDK 通过 Swift Package Manager 安装,儿童模式通过 `KidsMode` Swift 包 trait 启用,该 trait 会在编译时移除所有 IDFA、AdSupport 和 AppTrackingTransparency 相关代码。此功能需要 **Xcode 26** 或更高版本。 ::: 在 SDK v4 中,请在 `pubspec.yaml` 中使用 `adapty_flutter_kids` 包替代 `adapty_flutter`。这是该插件的儿童模式变体,具有相同的公共 API 和相同的版本号——唯一的区别是其原生 iOS SDK 使用 `KidsMode` trait 构建: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` 您的 Dart 代码保持不变——只需将 import 更新为新的包名: ```dart showLineNumbers title="Dart" ``` **iOS:通过 CocoaPods 启用儿童模式(SDK v3)** 1. 更新您的 Podfile: - 如果您**没有** `post_install` 部分,请添加下面的完整代码块。 - 如果您**已有** `post_install` 部分,请将高亮行合并进去。 ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. 运行以下命令以应用更改 ```sh showLineNumbers title="Shell" pod install ``` --- # File: flutter-get-onboardings --- --- title: "在 Flutter SDK 中获取用户引导" description: "了解如何在 Adapty 的 Flutter SDK 中获取用户引导。" --- :::warning **用户引导功能在 SDK v4 中已被废弃,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](flutter-get-pb-paywalls):与运行在 WebView 中的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画效果、一致的原生外观体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅[获取 flows 和付费墙](flutter-get-pb-paywalls)和[展示 flows 和付费墙](flutter-present-paywalls)快速上手。 ::: 在 Adapty 看板中[使用编辑工具设计好用户引导的视觉部分](design-onboarding)后,您可以在 Flutter 应用中展示它。此过程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。 开始之前,请确保: 1. 已安装 [Adapty Flutter SDK](sdk-installation-flutter) 3.8.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 ## 获取用户引导 \{#fetch-onboarding\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器形式存储,包含您的应用需要获取并展示的配置信息。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(如测验答案或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。 为获得最佳性能,请尽早获取用户引导配置,以便在向用户展示之前有足够时间下载图片。 要获取用户引导,请使用 `getOnboarding` 方法: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboarding(placementId: "YOUR_PLACEMENT_ID"); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` 然后,调用 `createOnboardingView` 方法获取将要展示的视图。 :::warning `createOnboardingView` 方法的结果只能使用一次。如果需要再次使用,请重新调用 `createOnboardingView` 方法。在不重新创建的情况下调用两次可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```dart showLineNumbers try { final onboardingView = await Adapty().createOnboardingView(onboarding: onboarding); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获得最新数据,但无论网络状况如何,都能体验更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后依然保留,仅在重新安装应用或手动清理时才会清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存层和备用用户引导层。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可访问时使用独立备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接稀缺的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个不同请求组成。</p> | 响应参数: | 参数 | 描述 | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html) 对象,包含:用户引导标识符和配置、远程配置以及其他几个属性。 | ## 使用默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常,用户引导几乎可以即时获取,因此您无需担心加速此过程。但是,当您有大量目标受众和用户引导,且用户网络连接较弱时,获取用户引导可能比预期耗时更长。在这种情况下,您可能希望展示默认用户引导以确保流畅的用户体验,而不是不显示任何内容。 为解决这一问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法会获取指定版位中针对**所有用户**目标受众的用户引导。但请务必了解,推荐的方式仍是通过 `getOnboarding` 方法获取用户引导,详见上方的[获取用户引导](#fetch-onboarding)部分。 :::warning 请考虑使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者有以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要设计向后兼容的界面,否则旧版本可能显示异常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向。 如果在您的使用场景中更快的获取速度优于上述缺点,请按以下方式使用 `getOnboardingForDefaultAudience`。否则,请按[上方](#fetch-onboarding)所述使用 `getOnboarding`。 ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` 参数: | 参数 | 是否必填 | 描述 | |-----------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获得最新数据,但无论网络状况如何,都能体验更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后依然保留,仅在重新安装应用或手动清理时才会清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存层和备用用户引导层。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可访问时使用独立备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接稀缺的情况下也能保证可靠性。</p> | --- # File: flutter-present-onboardings --- --- title: "在 Flutter SDK 中展示用户引导" description: "了解如何有效展示用户引导以提升转化率。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](flutter-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画、一致的原生外观体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取 flows 与付费墙](flutter-get-pb-paywalls) 和 [展示 flows 与付费墙](flutter-present-paywalls) 快速上手。 ::: 如果您已使用编辑工具自定义了用户引导,则无需在 Flutter 应用代码中手动处理渲染逻辑来向用户展示它。这类用户引导已包含展示内容与展示方式的完整配置。 开始之前,请确认以下事项: 1. 您已安装 [Adapty Flutter SDK](sdk-installation-flutter) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 Adapty Flutter SDK 提供两种展示用户引导的方式: - **独立页面** - **嵌入式组件** ## 以独立屏幕呈现 \{#present-as-standalone-screen\} 要将用户引导以独立屏幕的形式展示,请对 `createOnboardingView` 方法创建的 `onboardingView` 调用 `onboardingView.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示用户引导,请重新调用 `createOnboardingView` 创建一个新的 `onboardingView` 实例。 :::warning 重复使用同一个 `onboardingView` 而不重新创建,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```dart showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### 关闭用户引导 \{#dismiss-the-onboarding\} 当需要以编程方式关闭用户引导时,请使用 `dismiss()` 方法: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传入 `iosPresentationStyle` 参数,可配置用户引导在 iOS 上的展示方式。该参数接受 `AdaptyUIIOSPresentationStyle.fullScreen`(默认值)或 `AdaptyUIIOSPresentationStyle.pageSheet` 值。 ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## 嵌入到 Widget 层级中 \{#embed-in-widget-hierarchy\} 若要将用户引导嵌入到现有的 Widget 树中,可直接在 Flutter Widget 层级里使用 `AdaptyUIOnboardingPlatformView` 组件。 ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note 要使 Android 平台视图正常工作,请确保你的 `MainActivity` 继承自 `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## 用户引导加载中 \{#loader-during-onboarding\} 展示用户引导时,你可能会注意到在启动画面和用户引导之间有一个短暂的加载界面,这是底层视图初始化时产生的。你可以根据自己的需求,用不同的方式来处理这个问题。 #### 使用 onDidFinishLoading 控制启动画面 \{#control-splash-screen-using-ondidfinishloading\} :::note 该方式仅在将用户引导作为 widget 嵌入时可用,不支持以独立页面方式展示。 ::: 推荐的跨平台方案是:保持启动屏或自定义遮罩可见,直到用户引导完全加载后,再手动将其隐藏。 使用嵌入式 widget 时,在其上方叠加自定义 widget,并在 `onDidFinishLoading` 触发时隐藏遮罩: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### 自定义原生加载界面 \{#customize-native-loader\} :::important 此方式与平台相关,需要维护原生 UI 代码。除非您的应用已维护独立的原生层,否则不建议使用。 ::: 如果需要自定义默认加载界面本身,可以使用平台专属布局进行替换。此方式需要分别针对 Android 和 iOS 进行实现: - **iOS**:将 `AdaptyOnboardingPlaceholderView.xib` 添加到您的 Xcode 项目中 - **Android**:在 `res/layout` 中创建 `adapty_onboarding_placeholder_view.xml` 并在其中定义占位视图 ## 自定义用户引导中的链接打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接的打开方式需要 Adapty SDK v3.15.1 及以上版本。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开。这样可以在应用内直接展示网页,用户无需切换应用,体验更流畅。 如果你希望在外部浏览器中打开链接,可以将 `externalUrlsPresentation` 参数设置为 `AdaptyWebPresentation.externalBrowser` 来自定义此行为: <Tabs> <TabItem value="standalone" label="独立页面" default> ```dart showLineNumbers title="Flutter" final onboardingView = await AdaptyUI().createOnboardingView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser ); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="embedded" label="嵌入式组件"> ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` </TabItem> </Tabs> ## 禁用安全区域内边距(Android) \{#disable-safe-area-paddings-android\} 默认情况下,在 Android 设备上,用户引导视图会自动应用安全区域内边距,以避免与状态栏、导航栏等系统 UI 元素重叠。如果你想禁用此行为并完全自定义布局,可以在应用中添加一个布尔值资源: 1. 进入 `android/app/src/main/res/values` 目录。如果没有 `bools.xml` 文件,请新建一个。 2. 添加以下资源: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` 请注意,这些更改会全局应用于你应用中的所有用户引导。 --- # File: flutter-handling-onboarding-events --- --- title: "在 Flutter SDK 中处理用户引导事件" description: "使用 Adapty 在 Flutter 中处理用户引导相关事件。" --- :::warning **用户引导功能在 SDK v4 中已弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](flutter-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画效果、一致的原生外观、更快的加载速度,以及无 WebView 运行时依赖。请参阅[获取 flows 与付费墙](flutter-get-pb-paywalls)和[展示 flows 与付费墙](flutter-present-paywalls)快速上手。 ::: 使用编辑工具配置的用户引导会生成应用可以响应的事件。处理这些事件的方式取决于你使用的展示方式: - **全屏展示**:需要设置一个全局事件观察者,用于处理所有用户引导视图的事件 - **嵌入式 Widget**:通过 Widget 中的内联回调参数直接处理事件 开始之前,请确保: 1. 你已安装 [Adapty Flutter SDK](sdk-installation-flutter) 3.8.0 或更高版本。 2. 你已[创建用户引导](create-onboarding)。 3. 你已将用户引导添加到[版位](placements)。 ## 全屏展示事件 \{#full-screen-presentation-events\} ### 设置事件观察者 \{#set-up-event-observer\} 要处理全屏用户引导的事件,请实现 `AdaptyUIOnboardingsEventsObserver` 并在展示前进行设置: ```dart showLineNumbers title="Flutter" AdaptyUI().setOnboardingsEventsObserver(this); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### 处理事件 \{#handle-events\} 在您的观察者中实现以下方法: ```dart showLineNumbers title="Flutter" void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { // Onboarding finished loading } void onboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error, ) { // Handle loading errors } void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle close action view.dismiss(); } void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle custom actions } void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle user input updates } void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { // Track analytics events } ``` ## 嵌入式 Widget 事件 \{#embedded-widget-events\} 使用 `AdaptyUIOnboardingPlatformView` 时,你可以直接通过 Widget 内联回调参数处理事件。注意,事件会同时发送给 Widget 回调和全局观察者(如果已设置),但全局观察者是可选的: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Onboarding finished loading }, onDidFailWithError: (error) { // Handle loading errors }, onCloseAction: (meta, actionId) { // Handle close action }, onPaywallAction: (meta, actionId) { _openPaywall(actionId); }, onCustomAction: (meta, actionId) { // Handle custom actions }, onStateUpdatedAction: (meta, elementId, params) { // Handle user input updates }, onAnalyticsEvent: (meta, event) { // Track analytics events }, ) ``` ## 事件类型 \{#event-types\} 以下章节介绍了您可以处理的各类事件,无论您使用哪种展示方式。 ### 处理自定义操作 \{#handle-custom-actions\} 在编辑工具中,你可以为按钮添加 **custom** 操作并为其分配一个 ID。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 之后,您可以在代码中使用该 ID,并将其作为自定义操作来处理。例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,委托方法 `onboardingController` 会以 `.custom(id:)` 的形式触发,`actionId` 参数即为编辑工具中设置的 **Action ID**。您可以自定义任意 ID,例如 "allowNotifications"。 ```dart // Full-screen presentation void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { switch (actionId) { case 'login': _login(); break; case 'allow_notifications': _allowNotifications(); break; } } // Embedded widget onCustomAction: (meta, actionId) { _handleCustomAction(actionId); } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### 用户引导加载完成 \{#finishing-loading-onboarding\} 当用户引导加载完成时,将触发以下事件: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { print('Onboarding loaded: ${meta.onboardingId}'); } // Embedded widget onDidFinishLoading: (meta) { print('Onboarding loaded: ${meta.onboardingId}'); } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了 **Close** 操作的按钮时,用户引导即视为已关闭。 <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,你需要自行处理用户关闭用户引导后的逻辑。例如,你需要停止显示用户引导界面本身。 ::: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { await view.dismiss(); } // Embedded widget onCloseAction: (meta, actionId) { Navigator.of(context).pop(); } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### 打开付费墙 \{#opening-a-paywall\} :::tip 如果希望在用户引导内部打开付费墙,请处理此事件。如果希望在付费墙关闭后再打开另一个付费墙,有一种更直接的方式——处理关闭操作并直接打开付费墙,无需依赖事件数据。 ::: 在用户引导中使用付费墙最顺畅的方式,是将操作 ID 设置为与付费墙版位 ID 相同: :::note 请注意,在 iOS 上,同一时间只能显示一个视图(付费墙或用户引导)。如果在用户引导上方呈现付费墙,则无法以编程方式控制后台的用户引导。尝试关闭用户引导时,实际上会关闭付费墙,导致用户引导仍然可见。为避免此问题,请始终在呈现付费墙之前先关闭用户引导视图。 ::: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } Future<void> _openPaywall(String actionId) async { // Implement your paywall opening logic here } // Embedded widget onPaywallAction: (meta, actionId) { _openPaywall(actionId); } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### 跟踪导航 \{#tracking-navigation\} 在用户引导流程中,各种导航相关事件发生时,你都会收到对应的分析事件: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { trackEvent(event.type, meta.onboardingId); } // Embedded widget onAnalyticsEvent: (meta, event) { trackEvent(event.type, meta.onboardingId); } ``` `event` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `onboardingStarted` | 用户引导加载完成时触发 | | `screenPresented` | 任意屏幕显示时触发 | | `screenCompleted` | 屏幕完成时触发。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的响应)。当用户执行任何退出屏幕的操作时触发。| | `secondScreenPresented` | 第二个屏幕显示时触发 | | `userEmailCollected` | 通过输入框收集到用户邮箱时触发 | | `onboardingCompleted` | 当用户到达 ID 为 `final` 的屏幕时触发。如需使用此事件,请[将 `final` ID 分配给最后一个屏幕](design-onboarding)。| | `unknown` | 用于任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据)| 每个事件都包含 `meta` 元信息,具体字段如下: | 字段 | 描述 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前页面的标识符 | | `screenIndex` | 当前页面在流程中的位置 | | `screensTotal` | 流程中的总页面数 | <Details> <summary>事件示例(点击展开)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: flutter-onboarding-input --- --- title: "在 Flutter SDK 中处理用户引导数据" description: "使用 Adapty SDK 在 Flutter 应用中保存和使用用户引导数据。" --- :::warning **用户引导功能在 SDK v4 中已弃用,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](flutter-get-pb-paywalls):与运行在 WebView 中的用户引导不同,flows 直接在设备上原生渲染,带来更流畅的动画效果、一致的原生外观、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取 flows 和付费墙](flutter-get-pb-paywalls) 及 [展示 flows 和付费墙](flutter-present-paywalls) 以开始使用。 ::: 当用户回答测验问题或在输入字段中输入数据时,`onStateUpdatedAction` 方法将被调用。您可以在代码中保存或处理字段类型。 例如: ```dart // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Process data } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Process data } ``` 有关动作格式,请参阅[此处](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyUIOnboardingPlatformView/onStateUpdatedAction.html)。 <Details> <summary>每种参数类型的属性结构(点击展开)</summary> ```dart void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // elementId is a String: elementId; // 'preference_selector' // meta — AdaptyUIOnboardingMeta: meta.onboardingId; // 'onboarding_123' meta.screenClientId; // 'preferences_screen' meta.screenIndex; // 1 meta.screensTotal; // 3 // params is one of the AdaptyOnboardingsStateUpdatedParams subclasses: switch (params) { case AdaptyOnboardingsSelectParams(:final id, :final value, :final label): // a single selected option id; // 'option_1' value; // 'premium' label; // 'Premium Plan' break; case AdaptyOnboardingsMultiSelectParams(:final params): // a list of selected options, each an AdaptyOnboardingsSelectParams params; // [(id: 'interest_1', value: 'sports', label: 'Sports'), (id: 'interest_2', value: 'music', label: 'Music')] break; case AdaptyOnboardingsInputParams(:final input): switch (input) { case AdaptyOnboardingsTextInput(:final value): value; // 'John Doe' break; case AdaptyOnboardingsEmailInput(:final value): value; // 'user@example.com' break; case AdaptyOnboardingsNumberInput(:final value): value; // 25.0 (a double) break; } break; case AdaptyOnboardingsDatePickerParams(:final day, :final month, :final year): day; // 15 month; // 6 year; // 1990 break; } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用户画像数据填充 \{#enrich-user-profiles-with-data\} 如果你希望立即将用户输入的数据与用户画像关联起来,避免重复询问相同信息,可以在处理操作时通过[更新用户画像](flutter-setting-user-attributes)的方式写入这些数据。 例如,你让用户在 ID 为 `name` 的文本框中输入姓名,并希望将该值设置为用户的名字;同时,你还让用户在 `email` 字段中输入邮箱地址。在你的应用代码中,实现方式如下: ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` ### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\} 在用户引导中使用问卷,你可以根据用户完成用户引导后的答案来自定义展示给他们的付费墙。 例如,你可以询问用户的运动经验,并向不同的用户群体展示不同的 CTA 和产品。 1. 在用户引导编辑器中[添加问卷](onboarding-quizzes),并为各选项分配有意义的 ID。 2. 根据 ID 处理问卷响应,并为用户[设置自定义属性](flutter-setting-user-attributes)。 ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为你创建的每个市场细分添加[目标受众](audience)。 5. 在应用代码中为该版位[展示付费墙](flutter-paywalls)。如果你的用户引导中有一个打开付费墙的按钮,请将付费墙代码作为[此按钮操作的响应](flutter-handling-onboarding-events#opening-a-paywall)来实现。 --- # File: flutter-sdk-call-order --- --- title: "Flutter SDK 中的调用顺序" description: "按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、归因缺失以及间歇性 #2002 错误。" --- `Adapty().activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其完成之前,SDK 没有任何状态。在 `activate()` 之前或与其并行发出的任何调用都会因 [`#2002 notActivated`](error-handling-on-flutter-react-native-unity#custom-network-codes) 错误而失败。 如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到后调用 `Adapty().identify()`。在 `identify` 完成之前,不要调用任何用户操作相关的方法。与其并发执行的调用要么会以 [`#3006 profileWasChanged`](error-handling-on-flutter-react-native-unity#custom-network-codes) 失败,要么会作用于激活时创建的匿名用户画像。一旦出现这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属不一定会迁移到已识别的用户画像上。如果你的应用不需要用户认证,则跳过 `identify`,直接使用匿名用户画像即可。 MMP 和分析类 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待其 UID 回调后再调用 `Adapty().activate`。否则,MMP ID 会落在一个短暂的匿名用户画像上,不一定能转移到已识别的用户画像中。有关 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的路径取决于两件事:何时获取到用户 ID,以及是否使用了 MMP 或分析 SDK。 - **步骤 2 和 5**:每个应用都必须执行。先激活 SDK,再调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。 - **步骤 4**:仅在应用需要用户认证,且在启动后才能获取用户 ID 时需要。 如果在应用启动时已知客户用户 ID,可直接在 `activate()` 中传入(步骤 2a)。这样不会创建匿名用户画像,因此无需执行步骤 4。 | 步骤 | 调用 | 时机 | 说明 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化您的 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动时,最先执行 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `Adapty().activate(configuration: ...)` 并在配置中设置 `withCustomerUserId` | 应用启动时,在步骤 1 之后执行,前提是您已有 customer user ID | 推荐方式。不会创建匿名用户画像。 | | 2b | `Adapty().activate(configuration: ...)` 不设置 `withCustomerUserId` | 应用启动时,在步骤 1 之后执行,前提是您没有 customer user ID(或从不收集) | Adapty 会创建匿名用户画像。 | | 3 | 为每个 MMP 调用 `Adapty().setIntegrationIdentifier(key: ..., value: ...)` | 在步骤 2 之后、任何用户操作调用之前执行 | 必须执行,以确保 MMP ID 关联到正确的用户画像。 | | 4 | `await Adapty().identify(customerUserId)` | 在步骤 3 之后(若无 MMP 则在步骤 2 之后)、步骤 5 之前执行——仅适用于走路径 2b 且需要身份验证的情况 | 务必使用 `await`。在 `identify` 执行期间并发调用会产生 `#3006 profileWasChanged` 错误。 | | 5 | `getPaywall`(SDK v4 中为 `getFlow`)、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 若调用了 `identify`,则在步骤 4 之后执行;否则在步骤 3 之后(若无 MMP 则在步骤 2 之后)执行 | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回归用户丢失高级访问权限、用户画像中缺少 `appsflyer_id`,以及付费墙针对错误目标受众返回内容。 ::: ## Web2app 与 Web 漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在 Web 结账页面(Stripe、Paddle)完成购买后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与 Web 端的用户画像关联。如果你能在应用启动前(通过认证流程或安装来源追踪)获取到 customer user ID,可以直接将其传入 `activate()`。否则,在你调用 `identify("YOUR_USER_ID")` 并执行 `restorePurchases` 之前,设备端将无法看到 Web 端的购买记录。 关于每次 Web 结账时需要传递的元数据,请参阅: - [Stripe](stripe) - [Paddle](paddle) --- # File: flutter-optimize-paywall-fetching --- --- title: "在 Flutter SDK 中优化付费墙加载" description: "可靠地获取 Adapty 付费墙:Flutter 中的时机选择、缓存策略与备用方案。" --- 在 Flutter 中可靠地获取付费墙需要做到三点:快速渲染、返回针对目标受众的付费墙,以及在网络较慢时优雅降级。以下规则涵盖了实现这一目标所需的时机选择、缓存策略和备用方案。 :::tip 以下规则假设 `Adapty().activate()` 和 `Adapty().identify()` 已完成。请参阅 [Flutter SDK 的调用顺序](flutter-sdk-call-order)。 ::: 以下建议使用 v3 方法名称。在 SDK v4 中,`getPaywall` 已重命名为 `getFlow`,获取策略类型更名为 `AdaptyFlowFetchPolicy`——所有规则均适用,不受影响。 ## 规则与注意事项 \{#rules-and-pitfalls\} | 应该这样做 | 不要这样做 | 原因 | |---|---|---| | 只在即将展示时获取对应的版位。 | 在启动时并发预取所有版位。 | 批量预取会阻塞主线程,导致启动时出现黑屏。 | | 在归因数据有机会解析之后再调用 `getPaywall`——例如,在 `activate` 之后等待 1-2 秒,或等待 `didUpdateProfileStream` 触发后再调用。 | 在 `runApp` 之前的 `main()` 中调用 `getPaywall`。 | 此时归因数据尚未到位。付费墙会按默认目标受众进行解析,静默绕过市场细分和 ASA 个性化设置。 | | 为每个版位设置 `loadTimeout` 并配置[备用付费墙](fallback-paywalls)。 | 无限等待 `getPaywall` 返回。 | 没有超时设置,网络状况较差的用户会看到空白屏幕,直到网络恢复——或者直接关闭应用。 | 有关 `fetchPolicy` 和 `loadTimeout` 参数的说明,请参阅[获取付费墙和产品](fetch-paywalls-and-products-flutter);有关如何选择合适版位的信息,请参阅[版位](placements)。 ## 针对弱网环境进行优化 \{#tune-for-poor-connectivity\} 对于网络连接持续较差的市场(农村地区、交通途中、受路由问题影响的地区): - 除首次请求外,所有获取操作均设置 `fetchPolicy: AdaptyPaywallFetchPolicy.returnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeout` 设置为 3–5 秒,并在超时触发时接受备用付费墙。 - 不要将付费墙的显示挂起在 `getProfile()` 上。独立调用 `getPaywall`,避免因获取用户画像缓慢而阻塞 UI。 --- # File: flutter-show-aa-targeted-paywall --- --- title: "在 Flutter SDK 中首次启动时显示 AA 定向付费墙" description: "在 Flutter 中首次启动时短暂等待 Apple Ads 归因数据后再显示付费墙,超时则回退到默认目标受众。使用 AdaptyProfile.appliedAttributionSources。" --- Apple Ads (AA) 归因数据在 `Adapty().activate()` 调用后会异步到达。首次启动时,归因数据通常尚未就位,因此如果你立即调用 `getPaywall`,Adapty 会基于默认目标受众处理该请求,导致 Apple Ads 用户无法看到针对 AA 细分设置的付费墙。与其先展示付费墙再替换,不如在显示任何内容前短暂等待 AA 归因数据:若归因在短暂超时内到达,则展示定向付费墙;否则展示默认目标受众的付费墙。`AdaptyProfile.appliedAttributionSources` 可告知你 AA 归因是否已生效。 ## 开始之前 \{#before-you-start\} 您需要: - Adapty Flutter SDK **3.17.0** 或更高版本。 - 在 Adapty 中为应用配置 Apple Ads。请参阅 [Apple Ads](apple-search-ads)。 ## 工作原理 \{#how-it-works\} 调用 `Adapty().activate()` 后,SDK 会在后台向 Apple 请求 Apple Ads 归因数据,并将结果转发至 Adapty 后端。当 AA 成为该用户画像的有效归因来源时,SDK 会向你的 `didUpdateProfileStream` 监听器推送更新后的 `AdaptyProfile`,其 `appliedAttributionSources` 列表中将包含 `AdaptyAttributionSource.appleAds`。 首次启动时,你需要处理以下两种情况: 1. **归因在超时时间内完成。** 调用 `getPaywall` — Adapty 根据 Apple Ads 目标受众解析请求,并返回对应的付费墙。 2. **超时时间先到。** 改为显示默认目标受众的付费墙,避免没有 Apple Ads 归因的用户白白等待。`getPaywallForDefaultAudience` 无需等待分群即可直接返回结果。 `appliedAttributionSources` 可以为空,这意味着: - 该用户画像的 Apple Ads 归因尚未处理完成,或 - 根本没有收到任何归因数据。 无论如何,`getPaywallForDefaultAudience` 都可以安全调用——它会直接返回默认受众的付费墙,不受用户画像状态影响。 :::important 等待仅发生在首次启动时。一旦 Apple Ads 归因数据记录完成,它就会永久保存在用户画像中。在此后的每次启动时,缓存的用户画像已经在 `appliedAttributionSources` 中包含了 `AdaptyAttributionSource.appleAds`,因此归因路径会立即解析,`getPaywall` 无需任何等待即可返回针对 Apple Ads 市场细分的付费墙。 ::: ## 实现 \{#implementation\} 首次启动时,等待 `AdaptyAttributionSource.appleAds` 回调并设置硬超时——如果 Apple Ads 归因数据始终未到达,这些用户仍然需要看到付费墙。 1. **激活 SDK。** 请参阅[安装并配置 Flutter SDK](sdk-installation-flutter)。 2. **订阅用户画像更新**,使用 `Adapty().didUpdateProfileStream.listen(…)`。如果尚未设置监听器,请参阅[监听订阅更新](flutter-check-subscription-status#listen-to-subscription-updates)。 3. **在 `appliedAttributionSources` 中监听 `AdaptyAttributionSource.appleAds`。** 当它出现时,使用 `getPaywall` 加载付费墙 —— Adapty 将返回 AA 细分的实验变体: ```dart final subscription = Adapty().didUpdateProfileStream.listen((profile) async { if (!profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) return; final paywall = await Adapty().getPaywall(placementId: placementId); // present the segmented paywall, then cancel the subscription and the timer }); ``` `didUpdateProfileStream` 是广播流,不会重播历史事件,因此还需通过 `getProfile()` 单独检查当前用户画像。应用重启后,已存储的归因数据不会再次触发事件。 4. **与订阅同步启动一个 3–5 秒的计时器。** 如果计时器先于 `AdaptyAttributionSource.appleAds` 触发,则改用 `getPaywallForDefaultAudience` 加载默认受众付费墙。优先展示先完成的那个付费墙,并取消另一路请求,避免重复获取。为该版位配置[备用付费墙](flutter-use-fallback-paywalls),确保网络请求失败时用户也不会卡住。 ## 完整示例 \{#complete-example\} 下面的实现方案会让归因与超时同时竞争,并行预取默认受众付费墙,然后返回合适的付费墙。调用方只需等待单个函数,无需在调用处管理监听器或状态标志: - 如果归因在 `timeout` 内完成,则通过 `getPaywall` 返回分段付费墙。 - 如果 `timeout` 先到期,则通过 `getPaywallForDefaultAudience` 返回预取的默认受众付费墙。 ```dart title="apple_ads_paywall.dart" /// Returns the Apple Ads-segmented paywall if attribution is applied within /// [timeout], otherwise the default-audience paywall. Call after Adapty().activate(). Future<AdaptyPaywall> getPaywallOrDefault({ required String placementId, required Duration timeout, }) { // Prefetch the default-audience paywall right away so the timeout path resolves // without an extra network round-trip. `getPaywallForDefaultAudience` skips the // wait for segmentation data. `..ignore()` keeps an unused prefetch from surfacing // as an unhandled error; the error still reaches the caller if this paywall wins. final defaultPaywall = Adapty().getPaywallForDefaultAudience(placementId: placementId)..ignore(); final completer = Completer<AdaptyPaywall>(); late final StreamSubscription<AdaptyProfile> subscription; late final Timer timer; void resolve(Future<AdaptyPaywall> paywall) { if (completer.isCompleted) return; timer.cancel(); subscription.cancel(); completer.complete(paywall); } void onProfile(AdaptyProfile profile) { if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) { resolve(Adapty().getPaywall(placementId: placementId)); } } // Attribution path: react to profile updates as attribution is applied. subscription = Adapty().didUpdateProfileStream.listen(onProfile); // The stream is a broadcast stream and doesn't replay, so check the current // profile too — on relaunches attribution is already stored and won't re-emit. Adapty().getProfile().then(onProfile).ignore(); // Timeout path: fall back to the prefetched default-audience paywall. timer = Timer(timeout, () => resolve(defaultPaywall)); return completer.future; } ``` 在启动画面中调用,待其完成后再展示付费墙: ```dart try { final paywall = await getPaywallOrDefault( placementId: 'YOUR_PLACEMENT_ID', timeout: const Duration(seconds: 5), ); // present the paywall } on AdaptyError catch (adaptyError) { // handle the error or show a fallback paywall } catch (e) { // handle the error } ``` 调整 `timeout` 参数,设置用户在付费墙出现前最多等待的时间。大多数用户没有 Apple Ads 归因数据,因此他们会等待完整的超时时长——3 到 5 秒是一个合理的平衡点。如果有归因数据,通常会在应用启动后几秒内到达。 如果你的应用已经在监听 `didUpdateProfileStream`(例如用于[检查订阅状态](flutter-check-subscription-status#listen-to-subscription-updates)),则无需做任何修改。`didUpdateProfileStream` 是一个广播流,支持多个独立监听器互不干扰。 --- # File: flutter-test --- --- title: "在 Flutter SDK 中测试与发布" description: "了解如何在 Flutter 应用中使用 Adapty 检查订阅状态。" --- 如果您已经在 Flutter 应用中集成了 Adapty SDK,您可能需要测试一切是否设置正确,以及购买流程在 iOS 和 Android 平台上是否按预期运行。这包括使用 Apple 沙盒环境和 Google Play 测试环境,对 SDK 集成和实际购买流程进行测试。 ## 测试您的应用 \{#test-your-app\} 如需全面测试应用内购买,请参阅我们的平台专属测试指南:[iOS 测试指南](test-purchases-in-sandbox) 和 [Android 测试指南](testing-on-android)。 ## 准备发布 \{#prepare-for-release\} 在将应用提交到应用商店之前,请按照[发布检查清单](release-checklist)确认以下事项: - 应用商店连接和服务器通知已完成配置 - 购买流程顺畅完成,并已上报至 Adapty - 访问等级解锁与恢复功能正常运行 - 隐私与审核要求均已满足 --- # File: InvalidProductIdentifiers-flutter --- --- title: "Flutter SDK 中 Code-1000 noProductIDsFound 错误的修复方法" description: "解决在 Adapty 中管理订阅时出现的无效产品标识符错误。" --- 1000 错误码 `noProductIDsFound` 表示你在付费墙上请求的产品在 App Store 中均无法购买,尽管它们已列在其中。该错误有时会伴随 `InvalidProductIdentifiers` 警告一同出现。如果只出现警告而没有错误,可以忽略。 如果你遇到了 `noProductIDsFound` 错误,请按以下步骤排查: ## 第一步:检查 Bundle ID \{#step-2-check-bundle-id\} 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 第二步:检查产品 \{#step-3-check-products\} 1. 前往 **App Store Connect**,在左侧菜单中找到 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions)。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,在 **Subscriptions** 部分即可看到你的产品列表。 3. 确认你正在测试的产品状态为 **Ready to Submit**。 <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将表格中的产品 ID 与 Adapty 看板 [**Products**](https://app.adapty.io/products) 标签页中的 ID 进行对比。如果 ID 不匹配,请复制表格中的产品 ID,并在 Adapty 看板中[创建产品](create-product)。 <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第三步:检查产品可用性 \{#step-4-check-product-availability\} 1. 返回 **App Store Connect**,打开同一个 **Subscriptions** 页面。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称查看产品列表。 3. 选择你正在测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动到 **Availability** 部分,确认所有所需的国家和地区均已列出。 <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第四步:检查产品价格 \{#step-5-check-product-prices\} 1. 再次前往 **App Store Connect** 的 **Monetization** → **Subscriptions** 页面。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称。 3. 选择你正在测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动到 **Subscription Pricing**,展开 **Current Pricing for New Subscribers** 部分。 <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 确认所有所需价格均已列出。 <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第五步:检查应用付费状态、银行账户和税务表格是否有效 \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. 在 [**App Store Connect**](https://appstoreconnect.apple.com/) 首页,点击 **Business**。 <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择你的公司名称。 <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 向下滚动,确认 **Paid Apps Agreement**、**Bank Account** 和 **Tax forms** 均显示为 **Active**。 <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 完成以上步骤后,你应该能够解决 `InvalidProductIdentifiers` 警告,并让产品在商店中正常上线。 ## 第六步:如果产品卡住,尝试重新创建 \{#step-6-recreate-the-product-if-its-stuck\} 第 1 至 5 步可能都检查无误——状态为 `Approved`、Bundle ID 匹配、API 密钥有效——但 SDK 仍然返回 `1000 noProductIDsFound`。这种情况下,产品可能卡在了 Apple 的注册表中。Apple 的产品注册表偶尔会出现这样的状态:产品在 App Store Connect 的界面中存在,但 StoreKit 的查询路径无法访问到它。 请在 App Store Connect 中删除该产品,然后用相同的产品 ID 重新创建。重新创建后,最多等待 24 小时以完成同步。 --- # File: cantMakePayments-flutter --- --- title: "修复 Flutter SDK 中的 Code-1003 cantMakePayment 错误" description: "解决在 Adapty 中管理订阅时出现的支付错误。" --- 1003 错误 `cantMakePayments` 表示该设备无法进行应用内购买。 如果你遇到了 `cantMakePayments` 错误,通常是由以下原因之一导致的: - 设备限制:该错误与 Adapty 无关。请参阅下方的修复方法。 - 观察者模式配置:`makePurchase` 方法与观察者模式不能同时使用。请参阅下方相关章节。 ## 问题:设备限制 \{#issue-device-restrictions\} | 问题 | 解决方案 | |---------------------------|---------------------------------------------------------| | 屏幕使用时间限制 | 在 [Screen Time](https://support.apple.com/en-us/102470) 中关闭应用内购买限制 | | 账户被暂停 | 联系 Apple 支持以解决账户问题 | | 地区限制 | 使用受支持地区的 App Store 账户 | ## 问题:同时使用观察者模式和 makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} 如果你使用 `makePurchases` 来处理购买,则无需启用观察者模式。[观察者模式](observer-vs-full-mode) 仅在你自行实现购买逻辑时才需要使用。 因此,如果你正在使用 `makePurchase`,可以安全地从 SDK 激活代码中移除对观察者模式的启用。 --- # File: migration-to-flutter-sdk-v4 --- --- title: "将 Adapty Flutter SDK 迁移至 v. 4.0" description: "通过将付费墙 API 替换为 flow API,迁移至 Adapty Flutter SDK v4.0,兼容 Flow Builder 和 Paywall Builder。" --- Adapty Flutter SDK 4.0 引入了 flow,并相应地对付费墙 API 进行了重命名。新 API 同时兼容全新的 Flow Builder 和现有的 Paywall Builder——无需在 Adapty 看板侧做任何配置变更。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty().getPaywall(placementId: id)` | `Adapty().getFlow(placementId: id)` | | `Adapty().getPaywallForDefaultAudience(placementId: id)` | `Adapty().getFlowForDefaultAudience(placementId: id)` | | `Adapty().getPaywallProducts(paywall: paywall)` | `Adapty().getPaywallProducts(flow: flow)` | | `Adapty().logShowPaywall(paywall: paywall)` | `Adapty().logShowFlow(flow: flow)` | | `AdaptyPaywall`(类型) | `AdaptyFlow` | | `AdaptyPaywallFetchPolicy`(类型) | `AdaptyFlowFetchPolicy` | | `AdaptyUI().createPaywallView(paywall: paywall)` | `AdaptyUI().createFlowView(flow: flow)` | | `AdaptyUIPaywallView`(类型) | `AdaptyUIFlowView` | | `AdaptyUIPaywallPlatformView`(widget) | `AdaptyUIFlowPlatformView` | | `AdaptyUI().presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI().presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUI().setPaywallsEventsObserver(observer)` | `AdaptyUI().setFlowsEventsObserver(observer)` | | `paywallViewDid*` 回调 | `flowViewDid*` 回调 | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` 保持其名称不变——产品仍属于某个 flow,`getPaywallProducts` 现在接受 `AdaptyFlow` 作为参数。获取 flow 时不再需要传入 `locale`。购买和用户画像相关的 API(`makePurchase`、`restorePurchases`、`getProfile`、`identify` 等)保持不变,视图方法 `present`、`dismiss` 和 `showDialog` 也同样不变。部分默认行为有所改动——详见[默认行为变更](#default-behavior-changes)。 ## 最低版本要求 \{#minimum-versions\} Adapty Flutter SDK 4.0 提高了最低要求: - **iOS 15.0** — 最低 iOS 部署目标,从 iOS 13.0 提升。 - **Xcode 26** 或更高版本 — 原生 iOS SDK 使用 Swift tools 6.2。 - **Flutter 3.32.0**(Dart 3.8.0)或更高版本。 ## 安装 \{#installation\} ### 更新软件包 \{#update-the-package\} 安装哪个软件包取决于你的应用是否使用了儿童模式。 对于大多数应用,在 `pubspec.yaml` 中将 `adapty_flutter` 更新至 v4.0: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` 如果你的应用使用了儿童模式,请改为指定 `adapty_flutter_kids`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` 此**独立软件包**移除了 IDFA 和广告追踪相关代码,以符合 App Store 的要求。请将 Dart 导入路径更新为 `package:adapty_flutter_kids/adapty_flutter.dart`。除此之外,迁移步骤与常规软件包完全相同。 Kids Mode 还需要你在 Adapty 看板中禁用 IP 地址收集——完整配置步骤请参阅 [Kids Mode](kids-mode-flutter)。 ### iOS:原生 SDK 现在通过 Swift Package Manager 分发 \{#ios-native-sdks-now-come-through-swift-package-manager\} [CocoaPods 的 spec 仓库将于 2026 年 12 月进入只读模式](https://blog.cocoapods.org/CocoaPods-Specs-Repo/),因此从 v4 开始,原生 iOS SDK **不再通过 CocoaPods 分发** — 插件仅通过 **Swift Package Manager** 拉取依赖。 如果你使用的是 Flutter 3.32–3.43,请执行以下命令一次性启用 Swift Package Manager 支持: ```bash flutter config --enable-swift-package-manager ``` Flutter 3.44 及更高版本默认启用 Swift Package Manager,无需额外操作。 ## 获取流程 \{#fetching-flows\} ### getPaywall → getFlow 返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,并且不再需要传入 `locale` 参数——渲染 flow 时会自动解析本地化;对于自定义付费墙,所有已配置的语言版本将通过 `flow.remoteConfigs` 返回: ```diff showLineNumbers - final paywall = await Adapty().getPaywall(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` 也以同样的方式重命名: ```diff showLineNumbers - final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); ``` fetch policy 类型从 `AdaptyPaywallFetchPolicy` 重命名为 `AdaptyFlowFetchPolicy`;其选项(`reloadRevalidatingCacheData`、`returnCacheDataElseLoad`、`returnCacheDataIfNotExpiredElseLoad`)保持不变。 ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` 保持名称不变,但现在通过 `flow` 参数接收 `AdaptyFlow`: ```diff showLineNumbers - final products = await Adapty().getPaywallProducts(paywall: paywall); + final products = await Adapty().getPaywallProducts(flow: flow); ``` ## 数据模型 \{#data-model\} `getFlow` 返回的是 `AdaptyFlow` 而非 `AdaptyPaywall`,对象结构也有所变化: | v3 `AdaptyPaywall` 成员 | v4 `AdaptyFlow` 成员 | 操作 | |---|---|---| | `remoteConfig`(单个,可为空) | `remoteConfigs`(列表) | 一个流程为每种已配置的语言各携带一份远程配置。`remoteConfig` getter 仍然存在,返回第一个条目;若需指定语言,可按 `locale` 在 `remoteConfigs` 中查找。 | | `productIdentifiers` | `productIdentifiers` | 保留,但现在会汇总流程中所有付费墙变体的标识符。各变体的标识符存放在 `flow.paywalls[i].productIdentifiers`。 | | `hasViewConfiguration` | `hasViewConfiguration` | 不变。 | | `placementId`(已弃用) | 已移除 | 使用 `flow.placement.id`。 | | `revision`(已弃用) | 已移除 | 使用 `flow.placement.revision`。 | | `vendorProductIds`(已弃用) | 已移除 | 使用 `productIdentifiers`。 | | _(新增)_ | `paywalls`(`AdaptyFlowPaywall` 列表) | 每个条目对应流程中的一个付费墙变体,包含各自的 `name`、`variationId` 和 `productIdentifiers`。 | `AdaptyPaywallViewConfiguration` 不再对外暴露——视图配置现在是不透明的。请删除所有对该类型的引用。 ## Web 付费墙方法 \{#web-paywall-methods\} `openWebPaywall` 和 `createWebPaywallUrl` 的名称保持不变,但 `paywall` 参数现在接受 `AdaptyFlowPaywall`(流程变体),而不再是 `AdaptyPaywall`。你仍然可以传入 `AdaptyPaywallProduct`。 ```diff showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); - await Adapty().openWebPaywall(paywall: paywall); + if (flow.paywalls.isNotEmpty) { + await Adapty().openWebPaywall(paywall: flow.paywalls[0]); + } ``` ## 追踪流程视图 \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` 已重命名为 `logShowFlow`,现在接收一个 `AdaptyFlow` 参数。事件仍会记录在相同的变体下,因此现有的转化漏斗和 A/B 测试数据图表无需在看板中做任何更改即可继续正常使用。 ```diff showLineNumbers - await Adapty().logShowPaywall(paywall: paywall); + await Adapty().logShowFlow(flow: flow); ``` 与 v3 相同,当通过[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)渲染流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些页面的展示。 ## 显示流程 \{#displaying-flows\} ### createPaywallView → createFlowView 将方法重命名,并通过 `flow` 参数传入 `AdaptyFlow`。其他参数(`loadTimeout`、`preloadProducts`、`customTags`、`customTimers`、`customAssets`、`productPurchaseParams`)保持不变,视图方法 `present`、`dismiss` 和 `showDialog` 同样不变: ```diff showLineNumbers - final view = await AdaptyUI().createPaywallView(paywall: paywall); + final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); ``` ### AdaptyUIPaywallView → AdaptyUIFlowView 视图类型已重命名。其已废弃的 `paywallVariationId` 属性已移除——请改用 `variationId`: ```diff showLineNumbers - void flowViewDidAppear(AdaptyUIPaywallView view) { + void flowViewDidAppear(AdaptyUIFlowView view) { ``` ### AdaptyUIPaywallPlatformView → AdaptyUIFlowPlatformView 如果你将视图作为 widget 嵌入到 widget 树中,请重命名它并传入 `flow` 参数。事件回调(`onDidAppear`、`onDidFinishPurchase` 等)名称保持不变: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall: paywall, + AdaptyUIFlowPlatformView( + flow: flow, onDidFinishPurchase: (view, product, purchaseResult) { /* … */ }, ) ``` :::note 使用 `createFlowView` 创建的流程视图只能使用一次:调用 `dismiss()` 后,该视图会从内存中释放,无法再次展示——如需再次展示流程,请重新调用 `createFlowView`。 ::: ## 处理事件 \{#handling-events\} 观察者类已从 `AdaptyUIPaywallsEventsObserver` 更名为 `AdaptyUIFlowsEventsObserver`,其注册方法已从 `setPaywallsEventsObserver` 更名为 `setFlowsEventsObserver`,所有 `paywallViewDid*` 回调也已更名为 `flowViewDid*`: ```diff showLineNumbers - class MyObserver extends AdaptyUIPaywallsEventsObserver { + class MyObserver extends AdaptyUIFlowsEventsObserver { @override - void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { + void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { // … } } - AdaptyUI().setPaywallsEventsObserver(this); + AdaptyUI().setFlowsEventsObserver(this); ``` 现在有三个回调是**必须实现的**——缺少它们将导致编译错误: - **`flowViewDidFinishPurchase`**: 在 v3 中为可选项,默认行为是购买后关闭视图。现在由你决定后续操作:继续流程或调用 `view.dismiss()`。 - **`flowViewDidFinishRestore`**: 必填项,与 v3 相同。 - **`flowViewDidReceiveError`**: 替代 `paywallViewDidFailRendering`,同时还可接收其他视图错误。 另外两个小改动: - `setFlowsEventsObserver`(以及 `setOnboardingsEventsObserver`)现在接受 `null` 来解除之前设置的观察者,SDK 不再持有对它的引用。 - 新增的可选回调 `flowViewDidReceiveAnalyticEvent` 用于接收 flow 中的自定义分析事件。目前 flow 尚未向你的代码发送此类事件,因此无需实现该回调。 v4 还新增了一些可按需启用的功能: - `AdaptyUI().setObserverModeResolver(...)` 配合 `AdaptyUIObserverModeResolver` — 在 SDK 以[观察者模式](implement-observer-mode-flutter)运行时,处理从流程发起的购买和恢复操作。此前该功能仅在原生 iOS 和 Android SDK 中可用。请参阅[在观察者模式下展示流程](flutter-present-flows-in-observer-mode)。 - `AdaptyUI().setSystemRequestsHandler(...)` 配合 `AdaptyUISystemRequestsHandler` — 用于处理流程中的系统请求(系统权限提示和 App Store 评价请求)。目前流程尚未触发这些请求,因此无需注册处理器。 ## 已移除的 API \{#removed-apis\} 以下符号在 3.x 中已被标记为弃用,并在 v4 中正式移除: ### setFallbackPaywalls → setFallback ```diff showLineNumbers - await Adapty().setFallbackPaywalls(assetId); + await Adapty().setFallback(assetId); ``` ### withIdfaCollectionDisabled → withAppleIdfaCollectionDisabled ```diff showLineNumbers configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') - ..withIdfaCollectionDisabled(true), + ..withAppleIdfaCollectionDisabled(true), ``` ### 其他已移除的成员 \{#other-removed-members\} - **`AdaptyPurchaseResultSuccess.jwsTransaction`**:请使用 `appleJwsTransaction`。 - **`AdaptyUIFlowView.paywallVariationId`**:请使用 `variationId`。 - **`AdaptyUIObserver` 和 `AdaptyUI().setObserver(...)`**:请使用 `AdaptyUIFlowsEventsObserver` 和 `setFlowsEventsObserver(...)`。 ## 默认行为变更 \{#default-behavior-changes\} 这些变更不会导致编译错误,请在运行时进行测试: - **成功购买**:在 v3 中,默认的 `paywallViewDidFinishPurchase` 会关闭视图。在 v4 中,`flowViewDidFinishPurchase` 是必须实现的,且没有默认行为——如果你希望关闭视图,需要自行处理。 - **Android 系统返回按钮**:默认情况下,它不再关闭流程。该操作会以 `AndroidSystemBackAction` 的形式传递给 `flowViewDidPerformAction`——如果你希望返回按钮关闭流程,请在此处处理。 - **URL 打开**:默认的 `flowViewDidPerformAction` 现在会通过 `OpenUrlAction` 以原生方式打开 URL(遵循看板中的应用内或外部浏览器设置),同时在 `CloseAction` 时关闭视图。如需自行处理 URL,请覆盖此回调。 - **视图错误**:`flowViewDidReceiveError` 是必须实现的,是否关闭视图取决于你的实现。如果你的 v3 集成依赖于渲染错误时自动关闭视图的行为,请在此回调中调用 `view.dismiss()`。 - **视图生命周期**:关闭流程或用户引导视图后,该视图会从内存中释放。已关闭的视图无法再次显示——请重新创建一个新视图。 ## 用户引导 API 已弃用 \{#onboarding-api-deprecation\} 旧版用户引导 API 已在 v4.0 中弃用,请改用 [Flow Builder](adapty-flow-builder)。该 API 目前仍可正常使用,IDE 会通过 `@Deprecated` 注解标记已弃用的符号,不会产生任何运行时警告。这些符号将在未来版本中移除,请提前规划将你的用户引导迁移至 Flow Builder。 已废弃的符号:`getOnboarding`、`getOnboardingForDefaultAudience`、`createOnboardingView`、`presentOnboardingView`、`dismissOnboardingView`、`setOnboardingsEventsObserver`、`AdaptyOnboarding`、`AdaptyUIOnboardingView`、`AdaptyUIOnboardingPlatformView`、`AdaptyUIOnboardingsEventsObserver`,以及用户引导的状态、输入和分析模型。 --- # File: flutter-migration-guide-310 --- --- title: "迁移指南:Flutter Adapty SDK 3.10.0" description: "" --- Adapty SDK 3.10.0 是一个重大版本发布,带来了一些改进,但可能需要您执行以下迁移步骤: 1. 更新 `makePurchase` 方法,使用 `AdaptyPurchaseParameters` 替代单独的参数。 2. 在 `AdaptyPaywall` 模型中,将 `vendorProductIds` 替换为 `productIdentifiers`。 ## 更新 makePurchase 方法 \{#update-makepurchase-method\} `makePurchase` 方法现在使用 `AdaptyPurchaseParameters` 替代原有的 `subscriptionUpdateParams` 和 `isOfferPersonalized` 参数。这提供了更好的类型安全性,并为未来扩展购买参数提供了便利。 ```diff showLineNumbers - final purchaseResult = await adapty.makePurchase( - product: product, - subscriptionUpdateParams: subscriptionUpdateParams, - isOfferPersonalized: true, - ); + final parameters = AdaptyPurchaseParametersBuilder() + ..setSubscriptionUpdateParams(subscriptionUpdateParams) + ..setIsOfferPersonalized(true) + ..setObfuscatedAccountId('your-account-id') + ..setObfuscatedProfileId('your-profile-id'); + final purchaseResult = await adapty.makePurchase( + product: product, + parameters: parameters.build(), + ); ``` 如果不需要额外参数,可以直接使用: ```dart showLineNumbers final purchaseResult = await adapty.makePurchase( product: product, ); ``` ## 更新 AdaptyPaywall 模型的用法 \{#update-adaptypaywall-model-usage\} `vendorProductIds` 属性已被弃用,推荐使用 `productIdentifiers`。新属性返回 `AdaptyProductIdentifier` 对象而非简单字符串,提供了更结构化的产品信息。 ```diff showLineNumbers - paywall.vendorProductIds.map((vendorId) => - ListTextTile(title: vendorId) - ).toList() + paywall.productIdentifiers.map((productId) => + ListTextTile(title: productId.vendorProductId) + ).toList() ``` `AdaptyProductIdentifier` 对象通过 `vendorProductId` 属性提供对供应商产品 ID 的访问,在保持原有功能的同时,为未来的功能增强提供了更好的结构支持。 ## 向后兼容性 \{#backward-compatibility\} 两项更改均保持向后兼容: - `makePurchase` 中的旧参数已被弃用,但仍然可以正常使用 - `vendorProductIds` 属性已被弃用,但仍然可以访问 - 现有代码将继续正常运行,但您会看到弃用警告 我们建议更新您的代码以使用新的 API,以确保未来的兼容性,并充分利用改进后的类型安全性和可扩展性。 --- # File: flutter-migration-guide-38 --- --- title: "迁移 Adapty Flutter SDK 至 v3.8" description: "迁移至 Adapty Flutter SDK v3.8,获得更好的性能和新的货币化功能。" --- Adapty SDK 3.8.0 是一个重要版本,带来了一些改进,但可能需要你执行若干迁移步骤。 1. 更新 observer 类名和方法名。 2. 更新备用付费墙的方法名。 3. 更新事件处理方法中的 view 类名。 ## 更新观察者类和方法名称 \{#update-observer-class-and-method-names\} 观察者类及其注册方法已重命名: ```diff showLineNumbers - class MyObserver extends AdaptyUIObserver { + class MyObserver extends AdaptyUIPaywallsEventsObserver { @override void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { // Handle action } } // Register observer - AdaptyUI().setObserver(this); + AdaptyUI().setPaywallsEventsObserver(this); ``` ## 更新备用付费墙方法名称 \{#update-fallback-paywalls-method-name\} 设置备用付费墙的方法已简化: ```diff showLineNumbers try { - await Adapty.setFallbackPaywalls(assetId); + await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 在事件处理方法中更新视图类名 \{#update-view-class-name-in-event-handling-methods\} 所有事件处理方法现在使用新的 `AdaptyUIPaywallView` 类,替代原来的 `AdaptyUIView`: ```diff showLineNumbers - void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) + void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) - void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile) + void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile) - void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error) + void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) - void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile) + void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) - void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) - void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) + void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) - void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) ``` --- # File: migration-to-flutter-sdk-34 --- --- title: "迁移 Adapty Flutter SDK 至 v3.4" description: "迁移至 Adapty Flutter SDK v3.4,获得更好的性能和全新的变现功能。" --- Adapty SDK 3.4.0 是一个主要版本,引入了需要您进行迁移操作的改进。 ## 更新备用付费墙文件 \{#update-fallback-paywall-files\} 更新您的备用付费墙文件以确保与新 SDK 版本的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 用新文件[替换移动应用中现有的备用付费墙](flutter-use-fallback-paywalls)。 ## 更新 Observer Mode 的实现方式 \{#update-implementation-of-observer-mode\} 如果你正在使用 Observer Mode,请确保更新其实现方式。 此前,向 Adapty 上报交易时使用的是不同的方法。在新版本中,Android 和 iOS 应该统一使用 `reportTransaction` 方法来上报每笔交易,确保 Adapty 能够识别它。如果使用了付费墙,请传入 variation ID,以便将该交易与付费墙关联起来。 :::warning **不要跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成渠道。 ::: ```diff showLineNumbers - // every time when calling transaction.finish() - if (Platform.isAndroid) { - try { - await Adapty().restorePurchases(); - } on AdaptyError catch (adaptyError) { - // handle the error - } catch (e) { - } - } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter330 --- --- title: "迁移 Adapty Flutter SDK 至 v3.3" description: "迁移至 Adapty Flutter SDK v3.3,获得更佳性能与全新变现功能。" --- Adapty SDK 3.3.0 是一个重大版本更新,带来了一些改进,但可能需要你执行一些迁移步骤。 --- title: "迁移指南:从 Adapty iOS SDK v2.x 迁移至 v3.x" description: "将您的 iOS 应用从 Adapty SDK v2.x 无缝迁移至 v3.x,遵循我们的分步指南,了解关键变更,轻松完成集成升级。" metadataTitle: "iOS SDK v2.x 至 v3.x 迁移指南 | Adapty 文档" --- Adapty SDK v3.x 是一个重大版本更新,包含多项破坏性变更。本指南重点介绍这些变更,帮助您顺利完成迁移。 ## 公共 API 变更 \{#public-api-changes\} ### AdaptyUI 更名 \{#adaptui-renamed\} `AdaptyUI` 已重命名为 `AdaptyUI`——抱歉,只是开个玩笑😄 实际上没有变化,但我们确实针对新版付费墙编辑工具对 `AdaptyUI` 进行了大量更新。 ### 获取付费墙和产品 \{#fetching-paywalls-and-products\} 在 Adapty SDK v3.x 中,`getPaywall` 方法的加载策略机制已更新。 <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> 在 v3.x 中,我们移除了 `PaywallFetchPolicy`,转而采用更简洁的方案:默认加载已缓存的数据(若无缓存则从远端拉取),同时开放了直接从远端强制拉取的选项。 <Tabs> <TabItem value="v3" label="v3.x"> ```swift // Default: loads cached data or fetches from remote do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") } catch { // handle the error } // Force fetch from remote do { let paywall = try await Adapty.getPaywall( placementId: "YOUR_PLACEMENT_ID", locale: "en", fetchPolicy: .reloadRevalidatingCacheData ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en", fetchPolicy: .returnCacheDataElseLoad) { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en", fetchPolicy: .reloadRevalidatingCacheData) { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> ### 备用付费墙 \{#fallback-paywalls\} 在 v3.x 中,设置备用付费墙的方法已更新。 <Tabs> <TabItem value="v3" label="v3.x"> ```swift // Provide fallback paywalls from a file URL guard let url = Bundle.main.url(forResource: "fallback_paywalls", withExtension: "json") else { // handle the error return } do { try await Adapty.setFallbackPaywalls(fileURL: url) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift guard let urlPath = Bundle.main.path(forResource: "fallback_paywalls", ofType: "json"), let paywallsData = FileManager.default.contents(atPath: urlPath) else { // handle the error return } Adapty.setFallbackPaywalls(paywallsData) { error in if let error = error { // handle the error } } ``` </TabItem> </Tabs> ### 移除 `getProductsIntroductoryOfferEligibility` \{#removal-of-getproductsintroductoryoffereligibility\} `getProductsIntroductoryOfferEligibility` 方法已从 SDK 中移除。新用户优惠资格信息现在直接包含在 `AdaptyPaywallProduct` 对象中。 <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { let products = try await Adapty.getPaywallProducts(paywall: paywall) for product in products { let subscriptionOffer = product.subscriptionOffer // Use the offer directly } } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.getPaywallProducts(paywall: paywall) { result in switch result { case let .success(products): let productIds = products.map { $0.vendorProductId } Adapty.getProductsIntroductoryOfferEligibility(vendorProductIds: productIds) { result in switch result { case let .success(eligibilities): // use eligibilities case let .failure(error): // handle the error } } case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> #### `AdaptyPaywallProduct` 变更 \{#adaptpaywallproduct-changes\} `AdaptyPaywallProduct` 的结构已更新。在 v3.x 中,`subscriptionOffer` 属性会自动包含相应的优惠(新用户优惠或促销活动),无需额外的资格检查。 | v2.x 属性 | v3.x 属性 | | :-------------------------- | :------------------------------------- | | `introductoryDiscount` | 现已合并至 `subscriptionOffer` | | `promotionalDiscount` | 现已合并至 `subscriptionOffer` | | `promotionalOfferId` | 现已合并至 `subscriptionOffer` | `subscriptionOffer` 属性返回适合当前用户的最优优惠:若用户符合新用户优惠资格,则返回新用户优惠;若存在促销活动,则返回促销活动;其他情况返回 `nil`。 ### 第三方集成配置变更 \{#integration-configuration-changes\} 在 v3.x 中,多个第三方集成的配置方式已更新,从专有方法迁移至通用的 `setIntegrationIdentifier` 方法。 #### Adjust \{#adjust\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "adjust_device_id", value: adjustDeviceId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setAdjustId(adjustId) ``` </TabItem> </Tabs> #### AirBridge \{#airbridge\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "airbridge_device_id", value: airbridgeDeviceId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setAirbridgeId(airbridgeDeviceId) ``` </TabItem> </Tabs> #### Amplitude \{#amplitude\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "amplitude_user_id", value: amplitudeUserId ) try await Adapty.setIntegrationIdentifier( key: "amplitude_device_id", value: amplitudeDeviceId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setAmplitudeUserId(amplitudeUserId) Adapty.setAmplitudeDeviceId(amplitudeDeviceId) ``` </TabItem> </Tabs> #### AppMetrica \{#appmetrica\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_profile_id", value: appMetricaProfileId ) try await Adapty.setIntegrationIdentifier( key: "appmetrica_device_id", value: appMetricaDeviceId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setAppMetricaProfileId(appMetricaProfileId) Adapty.setAppMetricaDeviceId(appMetricaDeviceId) ``` </TabItem> </Tabs> #### AppsFlyer \{#appsflyer\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "appsflyer_id", value: appsFlyerId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setAppsFlyerId(appsFlyerId) ``` </TabItem> </Tabs> #### Branch \{#branch\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "branch_id", value: branchId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setBranchId(branchId) ``` </TabItem> </Tabs> #### Facebook Ads \{#facebook-ads\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "facebook_anonymous_id", value: facebookAnonymousId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setFacebookAnonymousId(facebookAnonymousId) ``` </TabItem> </Tabs> #### Firebase 与 Google Analytics \{#firebase-and-google-analytics\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "firebase_app_instance_id", value: firebaseAppInstanceId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setFirebaseAppInstanceId(firebaseAppInstanceId) ``` </TabItem> </Tabs> #### Mixpanel \{#mixpanel\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "mixpanel_user_id", value: mixpanelUserId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setMixpanelUserId(mixpanelUserId) ``` </TabItem> </Tabs> #### OneSignal \{#onesignal\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: oneSignalPlayerId ) try await Adapty.setIntegrationIdentifier( key: "one_signal_subscription_id", value: oneSignalSubscriptionId ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setOneSignalPlayerId(oneSignalPlayerId) ``` </TabItem> </Tabs> #### Pushwoosh \{#pushwoosh\} <Tabs> <TabItem value="v3" label="v3.x"> ```swift do { try await Adapty.setIntegrationIdentifier( key: "pushwoosh_hwid", value: pushwooshHWID ) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift Adapty.setPushwooshHWID(pushwooshHWID) ``` </TabItem> </Tabs> ### 观察者模式 \{#observer-mode\} 在 v3.x 中,观察者模式的实现方式已更新。 <Tabs> <TabItem value="v3" label="v3.x"> ```swift // Enable Observer mode during configuration let builder = AdaptyConfiguration .builder(withAPIKey: "YOUR_API_KEY") .with(observerMode: true) Adapty.activate(with: builder.build()) // Report transactions in Observer mode do { try await Adapty.reportTransaction(transaction) } catch { // handle the error } ``` </TabItem> <TabItem value="v2" label="v2.x"> ```swift // Enable Observer mode during configuration Adapty.activate("YOUR_API_KEY", observerMode: true) // Report transactions in Observer mode Adapty.setTransactionVariationId(transaction, variationId: variationId) { error in if let error = error { // handle the error } } ``` </TabItem> </Tabs> ## 更新备用付费墙的提供方式 \{#update-method-for-providing-fallback-paywalls\} 此前,该方法需要以 JSON 字符串(`jsonString`)的形式传入备用付费墙,现在改为传入本地备用文件的路径(`assetId`)。 ```diff showLineNumbers import 'dart:async' show Future; import 'dart:io' show Platform; -import 'package:flutter/services.dart' show rootBundle; -final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; -final jsonString = await rootBundle.loadString(filePath); +final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { - await adapty.setFallbackPaywalls(jsonString); + await adapty.setFallbackPaywalls(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` 完整的代码示例请参阅[使用备用付费墙](flutter-use-fallback-paywalls)页面。 ## 移除 `getProductsIntroductoryOfferEligibility` 方法 \{#remove-getproductsintroductoryoffereligibility-method\} 在 Adapty iOS SDK 3.3.0 之前,无论用户是否具有资格,产品对象始终包含优惠信息。您需要在使用优惠之前手动检查资格。 现在,只有当用户具有资格时,产品对象才会包含优惠信息。这意味着您不再需要检查资格——如果存在优惠,则用户具有资格。 ## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\} 为确保集成在 Adapty Flutter SDK 3.3.0 及更高版本中正常工作,请按照以下各节所述更新您的 SDK 配置。 ### Adjust 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。 ```diff showLineNumbers import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_config.dart'; try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } + await Adapty().setIntegrationIdentifier( + key: "adjust_device_id", + value: adid, + ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; - Adapty().updateAttribution( - attribution, - source: AdaptyAttributionSource.adjust, - networkUserId: adid, - ); + await Adapty().updateAttribution(attribution, source: "adjust"); } catch (e) { // handle the error } on AdaptyError catch (adaptyError) { // handle the error } ``` ### AirBridge 按照以下说明更新您的移动应用代码。完整代码示例请参阅 [AirBridge 集成的 SDK 配置](airbridge#connect-your-app-to-airbridge)。 ```diff showLineNumbers import 'package:airbridge_flutter_sdk/airbridge_flutter_sdk.dart'; final deviceUUID = await Airbridge.state.deviceUUID; try { - final builder = AdaptyProfileParametersBuilder() - ..setAirbridgeDeviceId(deviceUUID); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "airbridge_device_id", + value: deviceUUID, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Amplitude 按如下所示更新您的移动应用代码。完整代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。 ```diff showLineNumbers import 'package:amplitude_flutter/amplitude.dart'; final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); final deviceId = await amplitude.getDeviceId(); final userId = await amplitude.getUserId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setAmplitudeDeviceId(deviceId) - ..setAmplitudeUserId(userId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "amplitude_user_id", + value: userId, + ); + await Adapty().setIntegrationIdentifier( + key: "amplitude_device_id", + value: deviceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### AppMetrica 按照以下示例更新你的移动应用代码。完整代码示例请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。 ```diff showLineNumbers import 'package:appmetrica_plugin/appmetrica_plugin.dart'; final deviceId = await AppMetrica.deviceId; if (deviceId != null) { try { - final builder = AdaptyProfileParametersBuilder() - ..setAppmetricaDeviceId(deviceId) - ..setAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceId, + ); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID", + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` ### AppsFlyer 按照以下说明更新您的移动应用代码。完整代码示例请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。 ```diff showLineNumbers import 'package:appsflyer_sdk/appsflyer_sdk.dart'; AppsflyerSdk appsflyerSdk = AppsflyerSdk(<YOUR_OPTIONS>); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); - await Adapty().updateAttribution( - data, - source: AdaptyAttributionSource.appsflyer, - networkUserId: appsFlyerUID, - ); + await Adapty().setIntegrationIdentifier( + key: "appsflyer_id", + value: appsFlyerUID, + ); + + await Adapty().updateAttribution(data, source: "appsflyer"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` ### Branch 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。 ```diff showLineNumbers FlutterBranchSdk.initSession().listen((data) async { try { + await Adapty().setIntegrationIdentifier( + key: "branch_id", + value: <BRANCH_IDENTITY_ID>, + ); - await Adapty().updateAttribution(data, source: AdaptyAttributionSource.branch); + await Adapty().updateAttribution(data, source: "branch"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ); ``` ### Firebase 与 Google Analytics \{#firebase-and-google-analytics\} 按照以下示例更新你的移动应用代码。完整代码示例请参阅 [Firebase 与 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。 ```diff showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; try { - final builder = AdaptyProfileParametersBuilder() - ..setFirebaseAppInstanceId(appInstanceId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Mixpanel 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。 ```diff showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setMixpanelUserId(distinctId); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "mixpanel_user_id", + value: distinctId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### OneSignal 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。 ```diff showLineNumbers OneSignal.shared.setSubscriptionObserver((changes) { final playerId = changes.to.userId; if (playerId != null) { - final builder = - AdaptyProfileParametersBuilder() - ..setOneSignalPlayerId(playerId); - // ..setOneSignalSubscriptionId(playerId); try { - Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId, + ); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle error } } }); ``` ### Pushwoosh 按照以下说明更新你的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。 ```diff showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; - final builder = AdaptyProfileParametersBuilder() - ..setPushwooshHWID(hwid); try { - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: hwid, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 更新 Observer 模式实现 \{#update-observer-mode-implementation\} 更新付费墙与交易的关联方式。此前,你需要使用 `setVariationId` 方法来分配 `variationId`。现在,你可以在使用新的 `reportTransaction` 方法记录交易时直接传入 `variationId`。请参阅[在 Observer 模式下将付费墙与购买交易关联](report-transactions-observer-mode-flutter)中的完整代码示例。 :::warning 不要忘记使用 `reportTransaction` 方法记录交易。如果跳过此步骤,Adapty 将无法识别该交易,不会授予访问等级,不会将其纳入分析数据,也不会将其发送到集成渠道。此步骤至关重要! ::: ```diff showLineNumbers try { - await Adapty().setVariationId("YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID"); + // every time when calling transaction.finish() + await Adapty().reportTransaction( + "YOUR_TRANSACTION_ID", + variationId: "PAYWALL_VARIATION_ID", // optional + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter-sdk-v3 --- --- title: "迁移 Adapty Flutter SDK 至 v3.0" description: "迁移至 Adapty Flutter SDK v3.0,获得更好的性能和新的变现功能。" --- Adapty Flutter SDK v3.0 引入了重大变更,本指南将帮助你顺利完成迁移。 ## 主要变更 \{#main-changes\} ### 1. 公共 API 变更 \{#public-api-changes\} #### `AdaptyPaywall` \{#adaptypaywall\} `remoteConfig` 属性现在返回 `AdaptyRemoteConfig?` 而非 `Map<String, dynamic>?`。 `AdaptyRemoteConfig` 是一个包含以下属性的新类: - `id` — 远程配置的标识符 - `string` — 原始字符串值 - `dataValue` — 解析后的 `Map<String, dynamic>?` **迁移前:** ```dart final remoteConfig = paywall.remoteConfig; // Map<String, dynamic>? ``` **迁移后:** ```dart final remoteConfig = paywall.remoteConfig; // AdaptyRemoteConfig? final data = paywall.remoteConfig?.dataValue; // Map<String, dynamic>? ``` ### 2. `AdaptyPaywallProduct` \{#adaptypawallproduct\} `variationId` 属性已重命名为 `paywallVariationId`。 **迁移前:** ```dart final variationId = product.variationId; ``` **迁移后:** ```dart final variationId = product.paywallVariationId; ``` ### 3. Paywall Builder \{#paywall-builder\} SDK v3.0 支持新版付费墙编辑工具。如需了解如何展示用付费墙编辑工具创建的付费墙,请参阅[展示付费墙编辑工具付费墙](display-pb-paywalls)。 Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder)支持,这是一款全新版本的无代码、用户友好的付费墙创建工具。凭借其极高的灵活性和丰富的设计能力,您的付费墙将变得更加高效和盈利。 :::info 请注意,AdaptyUI 库已被弃用,现已作为 AdaptySDK 的一部分包含在内。 ::: ## 移除 AdaptyUI SDK \{#remove-adaptyu-sdk\} 1. AdaptyUI 已成为 Adapty SDK 的一个模块,请从您的 `pubspec.yaml` 文件中移除 `adapty_ui_flutter`: ```diff showLineNumbers dependencies: + adapty_flutter: ^3.2.1 - adapty_flutter: ^2.10.3 - adapty_ui_flutter: ^2.1.3 ``` 2. 运行: ```bash showLineNumbers title="Bash" flutter pub get ``` ## 配置 Adapty SDK \{#configure-adapty-sdks\} 此前,您需要使用 `Adapty-Info.plist` 和 `AndroidManifest.xml` 文件来配置 Adapty SDK。 现在无需使用额外的配置文件,您可以在激活时直接提供所有必要参数。 您只需配置一次 Adapty SDK,通常在应用程序生命周期开始时进行。 ### 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 1. 按如下方式从应用中移除 AdaptyUI SDK 的导入: ```diff showLineNumbers import 'package:adapty_flutter/adapty_flutter.dart'; - import 'package:adapty_ui_flutter/adapty_ui_flutter.dart'; ``` 2. 按如下方式更新 Adapty SDK 的激活代码: ```diff showLineNumbers try { - Adapty().activate(); + await Adapty().activate( + configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') + ..withLogLevel(AdaptyLogLevel.debug) + ..withObserverMode(false) + ..withCustomerUserId(null) + ..withIpAddressCollectionDisabled(false) + ..withIdfaCollectionDisabled(false), + ); } catch (e) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | ----------------------------------- | -------- | ------------------------------------------------------------ | | **PUBLIC_SDK_KEY** | 必填 | 可在 Adapty 应用设置的 **Public SDK key** 字段中找到该密钥:[**App settings** -> **General** 标签页 -> **API keys** 子部分](https://app.adapty.io/settings/general) | | **withLogLevel** | 可选 | Adapty 会记录错误及其他关键信息,帮助您了解应用的运行状况。可用的日志级别如下:<ul><li>error:仅记录错误。</li><li>warn:记录错误,以及 SDK 中不会导致严重错误但值得关注的消息。</li><li>info:记录错误、警告及重要信息,例如各模块生命周期的日志。</li><li>verbose:记录所有可能在调试时有用的附加信息,如函数调用、API 请求等。</li></ul> | | **withObserverMode** | 可选 | <p>一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。如果您自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请将其开启。</p><p>默认值为 `false`。</p><p></p><p>🚧 在观察者模式下,Adapty SDK 不会关闭任何交易,请确保您自行处理。</p> | | **withCustomerUserId** | 可选 | 您系统中的用户标识符。我们会在订阅和分析事件中发送该标识符,以便将事件归因到正确的用户画像。您也可以在 [**Profiles and Segments**](https://app.adapty.io/profiles/users) 菜单中通过 `customerUserId` 查找用户。 | | **withIdfaCollectionDisabled** | 可选 | <p>设为 `true` 可禁用 IDFA 的收集与共享。</p><p>以及用户 IP 地址的共享。</p><p>默认值为 `false`。</p><p>有关 IDFA 收集的更多详情,请参阅[分析集成](analytics-integration#disable-collection-of-advertising-identifiers)部分。</p> | | **withIpAddressCollectionDisabled** | 可选 | <p>设为 `true` 可禁用用户 IP 地址的收集与共享。</p><p>默认值为 `false`。</p> | ### 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 只有当你计划使用[付费墙编辑工具](adapty-paywall-builder)时,才需要配置 AdaptyUI 模块: ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer: <AdaptyUIObserver Implementation>, ); } catch (e) { // handle the error } ``` 请注意,AdaptyUI 配置是可选的,您可以在不使用配置的情况下激活 AdaptyUI 模块。但如果您使用配置,其中所有参数均为必填项。 参数: | 参数 | 是否必填 | 描述 | | :------------------------------ | :------- | :----------------------------------------------------------- | | **memoryStorageTotalCostLimit** | 必填 | 存储空间的总容量限制,单位为字节。 | | **memoryStorageCountLimit** | 必填 | 内存存储的最大条目数量。 | | **diskStorageSizeLimit** | 必填 | 存储在磁盘上的文件大小限制,单位为字节。0 表示不限制。 | --- # End of Documentation _Generated on: 2026-07-24T13:01:53.345Z_ _Successfully processed: 44/44 files_ # IOS - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.347Z Total files: 44 --- # File: sdk-installation-ios --- --- title: "安装与配置 iOS SDK" description: "在 iOS 上为订阅类应用安装 Adapty SDK 的分步指南。" --- Adapty SDK 包含两个核心模块,可无缝集成到您的移动应用中: - **Core Adapty**:这是 Adapty 正常运行所必需的核心 SDK。 - **AdaptyUI**:如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码、可轻松创建跨平台付费墙的可视化工具),则需要安装此可选模块。 :::tip 想看看 Adapty SDK 在真实移动应用中是如何集成的吗?欢迎查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples),其中展示了完整的接入流程,包括展示付费墙、完成购买以及其他基础功能。 ::: 有关完整的实现演示,您还可以观看以下视频: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="iOS (SwiftUI)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/cSChHc8k2zA?si=KhNFhqXccIzYwTcm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> <TabItem value="uikit" label="iOS (UIKit)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/WEUnlaAjSI0?si=sjXKVVb56tEHDKzJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> </Tabs> ## 系统要求 \{#requirements\} 虽然 SDK 核心模块在技术层面支持 iOS 13.0+,但实际使用中需要 iOS 15.0+,原因如下: - 所有 StoreKit 2 功能需要 iOS 15.0+ - AdaptyUI 模块仅支持 iOS 15.0+ :::important 使用 Xcode 26.4 或更高版本构建时,需要 Adapty SDK 3.15.7+。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-iOS.svg?style=flat&logo=apple)](https://github.com/adaptyteam/AdaptySDK-iOS/releases) Adapty SDK 通过 Swift Package Manager 安装。在 Xcode 中,依次点击 **File** -> **Add Package Dependency...**。请注意,不同版本的 Xcode 添加包依赖的步骤可能有所不同,如有需要请参阅 Xcode 文档。 1. 输入仓库 URL: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. 选择版本(推荐使用最新稳定版),然后点击 **Add Package**。 3. 在 **Choose Package Products** 窗口中,选择所需模块: - **Adapty**(核心模块) - **AdaptyUI**(可选 - 仅在计划使用付费墙编辑工具时选择) :::note 注意: - 若要在 SDK 3.x 中启用[儿童模式](kids-mode),请选择 **Adapty_KidsMode** 而非 **Adapty**。在 SDK 4.0 及更高版本中,选择常规模块即可——儿童模式通过 `KidsMode` package trait 来启用。 - 不要从列表中选择其他包——你不需要它们。 ::: 4. 点击 **Add Package** 完成安装。 5. **验证安装:** 在项目导航器中,你应该能在 **Package Dependencies** 下看到"Adapty"(以及"AdaptyUI",如果已选择的话)。 :::important Adapty iOS SDK 4.0 目前处于预发布阶段。Swift Package Manager 无法通过 **Up to Next Major Version**(`from:`)规则解析 beta 版本,因此你必须固定确切的版本号。在 Xcode 中,将 **Dependency Rule** 设置为 **Exact Version** 并输入 `4.0.0-beta.2`。在 `Package.swift` 中,使用 `.exact("4.0.0-beta.2")`。详见 [将 Adapty iOS SDK 迁移至 v4](migration-to-ios-sdk-v4)。 ::: ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 在应用代码中激活 Adapty SDK。 :::note Adapty SDK 在应用中只需激活一次。 ::: 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard Adapty.logLevel = .verbose // recommended for development and the first production release let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers // In your AppDelegate class: // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development and the first production release let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `activate` 完成。完整调用顺序请参阅 [iOS SDK 中的调用顺序](ios-sdk-call-order)。 ::: 现在在你的应用中配置付费墙: - 如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请先完成下方的[激活 AdaptyUI 模块](#activate-adaptyui-module-of-adapty-sdk),再参照[付费墙编辑工具快速入门](ios-quickstart-paywalls)操作。 - 如果你自行构建付费墙 UI,请参阅[自定义付费墙快速入门](ios-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果你计划使用[付费墙编辑工具](adapty-paywall-builder)并已[安装 AdaptyUI 模块](sdk-installation-ios#install-adapty-sdk),还需要激活 AdaptyUI。 :::important 在代码中,必须先激活核心 Adapty 模块,再激活 AdaptyUI。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers title="Swift" @main struct YourApp: App { init() { // ...ConfigurationBuilder steps // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } // main body... } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift showLineNumbers title="UIKit" // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development let config = configurationBuilder.build() try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::tip 激活 AdaptyUI 时,你可以选择[自定义付费墙的默认缓存设置](#set-up-media-cache-configuration-for-adaptyui)。 ::: ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误及其他重要信息,帮助你了解运行状态。以下是可用的日志级别: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | 仅记录错误日志 | | `warn` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息 | | `info` | 记录错误、警告和各类信息消息 | | `verbose` | 记录调试时可能有用的所有附加信息,例如函数调用、API 请求等 | ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(logLevel: .verbose) // recommended for development ``` #### 重定向日志系统消息 \{#redirect-the-logging-system-messages\} 如果你需要将 Adapty 的日志消息发送到你自己的系统或保存到文件,请使用 `setLogHandler` 方法并在其中实现自定义日志逻辑。该处理器会接收包含消息内容和严重级别的日志记录。 ```swift showLineNumbers title="Swift" Adapty.setLogHandler { record in writeToLocalFile("Adapty \(record.level): \(record.message)") } ``` ### 数据政策 \{#data-policies\} 除非您主动上传,Adapty 不会存储用户的个人数据。如有需要,您也可以启用额外的数据安全策略,以符合应用商店或所在国家/地区的合规要求。 #### 禁用 IDFA 收集与共享 \{#disable-idfa-collection-and-sharing\} 激活 Adapty 模块时,将 `idfaCollectionDisabled` 设置为 `true` 即可禁用 IDFA 的收集与共享。 使用此参数可符合 App Store 审核指南,或在应用不需要 IDFA 时避免触发 App Tracking Transparency 提示。默认值为 `false`。有关 IDFA 收集的更多详情,请参阅[分析集成](analytics-integration#disable-collection-of-advertising-identifiers)部分。 ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(idfaCollectionDisabled: true) ``` #### 禁用 IP 收集与共享 \{#disable-ip-collection-and-sharing\} 激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 可禁止收集和共享用户 IP 地址。默认值为 `false`。 当您需要保护用户隐私、遵守 GDPR 或 CCPA 等区域数据保护法规,或者您的应用不需要基于 IP 的功能时,可使用此参数减少不必要的数据收集。 ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(ipAddressCollectionDisabled: true) ``` #### 付费墙的媒体缓存配置(AdaptyUI)\{#media-cache-configuration-for-paywalls-in-adaptyui\} 请注意,AdaptyUI 配置是可选的。你可以在不提供配置的情况下激活 AdaptyUI 模块。但如果使用配置,则所有参数均为必填项。 ```swift showLineNumbers title="Swift" // Configure AdaptyUI let adaptyUIConfiguration = AdaptyUI.Configuration( mediaCacheConfiguration: .init( memoryStorageTotalCostLimit: 100 * 1024 * 1024, memoryStorageCountLimit: .max, diskStorageSizeLimit: 100 * 1024 * 1024 ) ) // Activate AdaptyUI AdaptyUI.activate(configuration: adaptyUIConfiguration) ``` 参数: | 参数 | 是否必填 | 描述 | | :-------------------------- | :------- | :----------------------------------------------------------- | | memoryStorageTotalCostLimit | required | 存储的总容量限制(字节)。 | | memoryStorageCountLimit | required | 内存存储的条目数量限制。 | | diskStorageSizeLimit | required | 存储的磁盘文件大小限制(字节)。0 表示不限制。 | ### 事务完成行为 \{#transaction-finishing-behavior\} :::info 此功能从 SDK 3.12.0 版本开始支持。 ::: 默认情况下,Adapty 会在事务验证成功后自动完成事务。但如果你需要进行高级事务验证(例如服务器端收据验证、欺诈检测或自定义业务逻辑),可以将 SDK 配置为手动完成事务。 ```swift showLineNumbers title="Swift" let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(transactionsFinishBehavior: .manual) // .auto is the default ``` 有关如何完成交易的更多详情,请参阅[指南](ios-transaction-management)。 ### 备份恢复时清除数据 \{#clear-data-on-backup\} 当 `clearDataOnBackup` 设置为 `true` 时,SDK 会检测应用是否从 iCloud 备份中恢复,并删除所有本地存储的 SDK 数据,包括已缓存的用户画像信息、产品详情和付费墙。之后 SDK 将以全新状态重新初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录以及 Adapty 服务器上的用户数据不受影响。 ::: ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(clearDataOnBackup: true) // default – false ``` ## 故障排查 \{#troubleshooting\} #### 使用 Tuist 时出现 Swift 6 并发错误 \{#swift-6-concurrency-error-with-tuist\} 使用 [Tuist](https://tuist.dev/) 构建时,可能会遇到 Swift 6 严格并发编译错误。常见症状包括 `AdaptyUIBuilderLogic` 中的 `@Sendable` 属性不匹配,或类似的跨模块 Sendability 错误。 这是因为 Tuist 从 SPM 包生成 Xcode 项目时,不会保留 `swift-tools-version: 6.0` 设置。因此,部分 Adapty targets(`Adapty`、`AdaptyUI`、`AdaptyUIBuilder`)会以 Swift 5 规则编译,而其他 targets 使用 Swift 6,导致跨模块的 `@Sendable` 不匹配。 **修复方案**:升级到 Adapty SDK **3.15.5** 或更高版本,无论 Swift 语言版本是否混用,均可解决此问题。 **临时解决方案**:如果暂时无法升级,请在 Tuist 配置中为所有三个 Adapty targets 显式指定 Swift 6: ```swift showLineNumbers targetSettings: [ "Adapty": .init().swiftVersion("6"), "AdaptyUI": .init().swiftVersion("6"), "AdaptyUIBuilder": .init().swiftVersion("6"), ] ``` --- # File: ios-quickstart-paywalls --- --- title: "在 iOS SDK 中使用 Flow Builder 启用内购" description: "使用 Adapty Flow Builder 启用应用内购买的快速入门指南。" --- 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 向用户呈现产品的屏幕序列,在无代码的 Flow Builder 中构建。SDK 通过 `getFlow` 获取流程。如果你更倾向于用自己的代码构建 UI,请改用付费墙 — 参见[手动实现付费墙](ios-quickstart-manual)。 - [**版位**](placements) – 在应用中展示流程的位置和时机(例如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 请求它们。这样可以轻松进行 A/B 测试,并向不同用户展示不同的流程。 Adapty 为您提供三种在应用中启用购买的方式,请根据应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |---|---|---| | Adapty Flow Builder | ✅ 简单 | 你在[无代码编辑工具中创建完整的、可立即购买的流程](quickstart-paywalls)。Adapty 自动完成渲染,并在后台处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 你在应用代码中自行实现付费墙 UI,但仍通过 Adapty 获取 flow 对象,从而保持产品组合的灵活性。参阅[指南](ios-quickstart-manual)。 | | 观察者模式 | 🔴 复杂 | 你已有自己的购买处理基础设施并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤展示如何在应用中实现通过 Adapty Flow Builder 创建的流程。** 如果你想自行构建付费墙 UI,请参阅[手动实现付费墙](ios-quickstart-manual)。 ::: 要在应用代码中展示通过 Adapty Flow Builder 创建的流程,你只需完成以下步骤: 1. **获取流程**:从 Adapty 获取。 2. **展示流程,Adapty 会自动处理购买逻辑**:在应用中显示该视图。 3. **处理按钮操作**:将用户交互与应用的响应逻辑绑定,例如在用户点击按钮时打开链接或关闭流程。 ## 开始之前 \{#before-you-start\} 在开始之前,请先完成以下步骤: 1. 在 Adapty 看板中[将你的应用连接到 App Store](initial_ios)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建流程并向其添加产品](create-paywall)。 4. [创建版位并将流程添加到其中](create-placement)。 5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-ios)。本指南使用 Adapty iOS SDK v4 API。 ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。版位允许你针对不同的目标受众运行不同的流程,或者运行 [A/B 测试](ab-tests)。 要获取在 Adapty Flow Builder 中创建的流程,你需要: 1. 通过 `getFlow` 方法,使用[版位](placements) ID 获取 `flow` 对象,并检查它是否包含视图配置。 2. 使用 `getFlowConfiguration` 方法获取视图配置。该配置包含显示流程所需的 UI 元素和样式信息。 ```swift func loadFlow() async { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } ``` ## 2. 展示流程 \{#display-the-flow\} 现在,当你获取到流程配置后,只需添加几行代码即可展示你的流程。 <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> 在 SwiftUI 中展示流程时,你还需要处理相关事件。`didFinishPurchase`、`didFailPurchase`、`didFinishRestore`、`didFailRestore` 和 `didReceiveError` 是必填项。在测试阶段,你可以直接复制下方代码片段来记录这些事件。 :::tip 流程在购买成功后不会自动关闭。在 `didFinishPurchase` 中,将你的展示绑定设置为 `false` 以关闭它,或者不做任何操作,让流程继续进入下一个界面。 ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in flowPresented = false print("Flow error: \(error)") } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift func presentFlow(with config: AdaptyUI.FlowConfiguration) { let flowController = try AdaptyUI.flowController( with: config, delegate: self ) present(flowController, animated: true) } ``` 实现 `AdaptyFlowControllerDelegate` 来处理事件。至少需要实现四个没有默认实现的方法。注意,控制器在购买成功后不会自动关闭——请在 `didFinishPurchase` 中手动关闭,或者不做任何操作以让流程继续跳转到后续页面: ```swift extension YourViewController: AdaptyFlowControllerDelegate { 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) { print("Purchase failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } } ``` </TabItem> </Tabs> :::info 有关如何展示流程的更多详情,请参阅我们的[指南](ios-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#3-handle-button-actions\} 当用户点击按钮时,iOS SDK 会自动处理购买、恢复、关闭流程以及打开链接等操作。 不过,其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作。你也可以根据需要覆盖这些按钮的默认行为。 例如,以下是处理关闭按钮的方式。在 UIKit 中,当 `.close` 触发时,SDK 会自动关闭控制器——只有在需要自定义行为时才需要覆盖。在 SwiftUI 中,你必须自行将 `isPresented` 绑定设置为 `false`。 :::tip 阅读我们的指南,了解如何处理按钮[操作](handle-paywall-actions)和[事件](ios-handling-events)。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow when the user taps close default: break } }, didFinishPurchase: { product, purchaseResult in flowPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift extension YourViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // default behavior — override only if needed default: break } } } ``` </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的流程已准备好在应用中展示。请[在沙盒模式下测试购买](test-purchases-in-sandbox),确保能够完成测试购买。 接下来,您需要[检查用户的访问等级](ios-check-subscription-status),以确保向正确的用户展示流程或开放付费功能的访问权限。 ## 完整示例 \{#full-example\} 以下是本指南中所有步骤在应用中整合的完整示例。 <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift struct ContentView: View { @State private var flowPresented = false @State private var flowConfiguration: AdaptyUI.FlowConfiguration? @State private var isLoading = false @State private var hasInitialized = false var body: some View { VStack { if isLoading { ProgressView("Loading...") } else { Text("Your App Content") } } .task { guard !hasInitialized else { return } await initializeFlow() hasInitialized = true } .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in print("Flow error: \(error)") flowPresented = false } ) } private func initializeFlow() async { isLoading = true defer { isLoading = false } await loadFlow() flowPresented = true } private func loadFlow() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } catch { print("Failed to load: \(error)") } } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift class ViewController: UIViewController { private var flowConfiguration: AdaptyUI.FlowConfiguration? override func viewDidLoad() { super.viewDidLoad() Task { await initializeFlow() } } private func initializeFlow() async { do { flowConfiguration = try await loadFlow() if let flowConfiguration { await MainActor.run { presentFlow(with: flowConfiguration) } } } catch { print("Error initializing: \(error)") } } private func loadFlow() async throws -> AdaptyUI.FlowConfiguration? { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return nil } return try await AdaptyUI.getFlowConfiguration(forFlow: flow) } private func presentFlow(with config: AdaptyUI.FlowConfiguration) { guard let flowController = try? AdaptyUI.flowController( with: config, delegate: self ) else { return } present(flowController, animated: true) } } extension ViewController: AdaptyFlowControllerDelegate { 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) { print("Purchase failed for \(product.vendorProductId): \(error)") guard error.adaptyErrorCode != .paymentCancelled else { return } let message = switch error.adaptyErrorCode { case .paymentNotAllowed: "Purchases are not allowed on this device." default: "Purchase failed. Please try again." } let alert = UIAlertController(title: "Purchase Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") controller.dismiss(animated: true) } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didReceiveError error: AdaptyUIError) { print("Flow error: \(error)") controller.dismiss(animated: true) } } ``` </TabItem> </Tabs> --- # File: ios-check-subscription-status --- --- title: "在 iOS SDK 中检查订阅状态" description: "了解如何使用 Adapty 在 iOS 应用中检查订阅状态。" --- 要决定用户是否可以访问付费内容或查看付费墙,您需要检查用户画像中的[访问等级](access-level)。 本文将介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您需要决定是向用户显示付费墙还是付费内容时,您需要检查其用户画像中的[访问等级](access-level)。您有两种选择: - 如果需要立即获取最新的用户画像数据(例如应用启动时)或想要强制更新,请调用 `getProfile`。 - 设置**自动用户画像更新**,以保留一份本地副本,当订阅状态发生变化时自动刷新。 :::important 默认情况下,`premium` 访问等级在 Adapty 中已经存在。如果您不需要设置多个访问等级,直接使用 `premium` 即可。 ::: ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方法是使用 `getProfile` 方法访问用户画像: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> ### 监听订阅更新 \{#listen-to-subscription-updates\} 如果您希望在应用中自动接收用户画像更新: 1. 在您选择的类型中遵循 `AdaptyDelegate` 协议并实现 `didLoadLatestProfile` 方法——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。在下面的示例中,我们使用 `SubscriptionManager` 类型来协助处理订阅工作流和用户画像。该类型可以作为依赖项注入,或在 UIKit 应用中设置为单例,也可以从应用主结构体添加到 SwiftUI 环境中。 2. 当此方法被调用时,存储更新后的用户画像数据,这样您就可以在整个应用中使用它,而无需进行额外的网络请求。 ```swift class SubscriptionManager: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { let hasAccess = profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false // Update UI, unlock content, etc. } } // Set delegate after Adapty activation Adapty.delegate = subscriptionManager ``` :::note Adapty 会在应用启动时自动调用 `didLoadLatestProfile`,即使设备处于离线状态也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑连接 \{#connect-profile-with-paywall-logic\} 当您需要立即决定是显示付费墙还是授予用户付费功能访问权限时,可以直接检查用户的用户画像。这种方式适用于以下场景:应用启动时、进入付费专区时,或在显示特定内容之前。 <Tabs> <TabItem value="swiftui" label="SwiftUI" default> ```swift private func checkAccessLevel() async -> Bool { do { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } catch { print("Error checking access level: \(error)") return false } } // In your initialization logic: let hasAccess = await checkAccessLevel() if !hasAccess { paywallPresented = true // Show paywall if no access } ``` </TabItem> <TabItem value="uikit" label="UIKit"> ```swift private func checkAccessLevel() async throws -> Bool { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } // In your initialization logic: let hasAccess = try await checkAccessLevel() if !hasAccess { presentPaywall(with: paywallConfiguration) } ``` </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} 现在您已了解如何追踪订阅状态,接下来请[了解如何使用用户画像](ios-quickstart-identify),以确保其与您现有的身份验证系统及付费访问共享权限保持一致。 如果您没有自己的身份验证系统,这完全没有问题,Adapty 会为您管理用户,但您仍然可以阅读该[指南](ios-quickstart-identify),了解 Adapty 如何处理匿名用户。 --- # File: ios-quickstart-identify --- --- title: "在 iOS SDK 中识别用户" description: "用于设置 Adapty 进行应用内订阅管理的快速入门指南。" --- :::important 如果您有自己的身份验证系统,本指南适合您。在这里,您将学习如何在 Adapty 中管理用户画像,以确保其与您现有的身份验证系统保持一致。 ::: 您如何管理用户购买行为取决于您应用的身份验证模式: - 如果您的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户相关章节](#anonymous-users)。 - 如果您的应用已有(或将有)后端身份验证,请参阅[已识别用户相关章节](#identified-users)。 **核心概念**: - **用户画像** 是 SDK 运行所需的实体,由 Adapty 自动创建。 - 用户画像可以是匿名的 **(不含 customer user ID)** 或已识别的 **(含 customer user ID)**。 - 您提供 **customer user ID** 以便将 Adapty 中的用户画像与您内部的身份验证系统进行关联。 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|---------------------------------------------------|-------------------------------------------------------------------------| | **购买管理** | 通过商店层面恢复购买 | 通过 customer user ID 跨设备维护购买历史 | | **用户画像管理** | 每次重新安装创建新用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用安装间持续保存 | ## 匿名用户 \{#anonymous-users\} 如果您没有后端身份验证,**无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时激活,Adapty 会**为用户创建一个新的用户画像**。 2. 当用户在应用内购买任何内容时,该购买会**与其 Adapty 用户画像和商店账户相关联**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建新的匿名用户画像**。 4. 如果用户之前在您的应用中有过购买,默认情况下,SDK 激活时会自动从 App Store 同步其购买记录。 :::note 备份恢复与重新安装的行为不同。默认情况下,当用户从备份恢复时,SDK 会保留缓存数据,不会创建新的用户画像。您可以通过 `clearDataOnBackup` 设置来配置此行为。[了解更多](sdk-installation-ios#clear-data-on-backup-restore)。 ::: 对于匿名用户,每次安装都会创建新的用户画像,但这不是问题,因为在 Adapty 分析中,您可以[配置什么被视为新安装](general#4-installs-definition-for-analytics)。 对于匿名用户,您需要通过**设备 ID** 来统计安装次数。在这种情况下,每次在设备上安装应用都会被计为一次安装,包括重新安装。 ## 已识别用户 \{#identified-users\} 您有两种方式在应用中识别用户: - [**在登录/注册时:**](#during-loginsignup) 如果用户在应用启动后才登录,请在其完成身份验证时调用 `identify()` 并传入 customer user ID。 - [**在 SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时您已有存储的 customer user ID,请在调用 `activate()` 时传入。 :::important 默认情况下,当 Adapty 收到来自某个 Customer User ID 的购买,而该 ID 当前已与另一个 Customer User ID 关联时,访问等级会被共享,即两个用户画像都具有付费访问权限。您可以将此设置配置为将付费访问从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[此文章](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 在登录/注册时 \{#during-loginsignup\} 如果您在应用启动后才识别用户(例如,在用户登录或注册后),请使用 `identify` 方法设置其 customer user ID。 - 如果您**之前未使用过此 customer user ID**,Adapty 会自动将其与当前用户画像关联。 - 如果您**之前已使用此 customer user ID 识别过该用户**,Adapty 将切换到与该 customer user ID 关联的用户画像。 :::important 客户用户 ID 对每个用户必须是唯一的。如果将该参数值硬编码,所有用户都会被视为同一人。 ::: 务必在调用其他 SDK 方法之前 `await` `identify`。并发调用会产生 `#3006 profileWasChanged` 错误或落到匿名用户画像上。详见 [iOS SDK 调用顺序](ios-sdk-call-order)。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // 每个用户的 ID 必须唯一 } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // 每个用户的 ID 必须唯一 Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> </Tabs> ### 在 SDK 激活时 \{#during-the-sdk-activation\} 如果您在激活 SDK 时已知晓 customer user ID,可以在 `activate` 方法中直接传入,而无需单独调用 `identify`。 如果您知道 customer user ID 但仅在激活后才设置,则意味着 Adapty 在激活时会创建一个新的匿名用户画像,只有在您调用 `identify` 之后才会切换到现有用户画像。 您可以传入现有的 customer user ID(之前使用过的)或新的 customer user ID。如果传入新的,激活时创建的新用户画像将自动与该 customer user ID 关联。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装次数基于设备 ID 统计。 设备 ID 代表从商店在某台设备上安装应用的单次安装,仅在应用重新安装后才会重新生成。 它不取决于这是首次还是重复安装,也不取决于是否使用了现有的 customer user ID。 创建用户画像(在 SDK 激活时或退出登录时)、登录或在不重新安装的情况下升级应用,均不会产生额外的安装事件。 如果您希望基于唯一用户而非设备来统计安装次数,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> ### 退出登录用户 \{#log-users-out\} 如果您有退出登录按钮,请使用 `logout` 方法。 :::important 退出登录会为用户创建一个新的匿名用户画像。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录时购买 \{#allow-purchases-without-login\} 如果您的用户在登录应用之前和之后都可以进行购买,您需要确保他们在登录后仍能保留访问权限: 1. 当未登录用户进行购买时,Adapty 会将其绑定到匿名用户画像 ID。 2. 当用户登录账户时,Adapty 会切换到其已识别的用户画像。 - 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 会将 customer user ID 分配给当前用户画像,从而保留所有购买历史。 - 如果是已有的 customer user ID(该 customer user ID 已与某个用户画像关联),您需要在用户画像切换后获取实际的访问等级。您可以在识别后立即调用 [`getProfile`](ios-check-subscription-status),或者[监听用户画像更新](ios-check-subscription-status)以自动同步数据。 ## 后续步骤 \{#next-steps\} 恭喜!您已在应用中实现了应用内支付逻辑!祝您的应用变现一切顺利! 要从 Adapty 获取更多价值,您可以探索以下主题: - [**测试**](test-purchases-in-sandbox):确保一切按预期运行 - [**用户引导**](ios-onboardings):通过用户引导吸引用户并提升留存 - [**集成**](configuration):只需一行代码即可与营销归因和分析服务集成 - [**设置自定义用户画像属性**](setting-user-attributes):为用户画像添加自定义属性并创建市场细分,以便启动 A/B 测试或向不同用户展示不同付费墙 --- # File: adapty-sdk-integration-skill --- --- title: "使用 SDK 集成技能将 Adapty 集成到你的 iOS 应用中" description: "使用 adapty-sdk-integration 技能,通过 AI 编码工具将 Adapty SDK 端到端集成到你的 iOS 应用中。" --- [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 :::important 该技能处于 Beta 阶段。如果卡住或出现意外行为,请改用[分步集成指南](adapty-cursor)——它会引导你的 AI 工具逐步完成每个阶段,并附有相应的文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor --- --- title: "借助 AI 将 Adapty 集成到 iOS 应用中" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具将 Adapty 集成到 iOS 应用的分步指南。" --- 本指南将带你一步步使用 AI 编码工具将 Adapty 集成到你的 iOS 应用中——只需按正确顺序向它提供正确的 Adapty 文档即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。你可以使用交互式 LLM 技能来完成,也可以通过 Adapty 控制台手动操作。 ### 技能方式(推荐) \{#skill-approach-recommended\} Adapty CLI 技能让你的 LLM 可以直接设置应用、产品、访问等级、付费墙和版位——无需为每个步骤打开 Adapty 控制台。你只需要在 Adapty 控制台中[连接你的商店](integrate-payments)。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在你的 agent 中运行 `/adapty-cli`。它将引导你完成每个步骤——包括何时打开 Adapty 控制台连接你的商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你更喜欢手动配置一切,以下是开始写代码前需要准备的内容。你的 LLM 无法自动查询看板中的值——需要你自己提供。 1. **连接应用商店**:在 Adapty 看板中,前往 **App settings → General**。这是购买功能正常运作的前提条件。 [连接 App Store](integrate-payments) 2. **复制你的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这就是你传给 `Adapty.activate("YOUR_PUBLIC_SDK_KEY")` 的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,前往 **Products** 页面。你无需在代码中直接引用产品——Adapty 会通过流程或付费墙将其分发给用户。 [添加产品](quickstart-products) 4. **创建流程或付费墙以及版位**:在 Adapty 看板中,创建一个流程(如果你要自己构建 UI,则创建付费墙),然后在 **Placements** 页面将其分配到一个版位。在代码中,版位 ID 就是你传入 `Adapty.getFlow("YOUR_PLACEMENT_ID")` 的字符串。 [创建流程](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板的 **Products** 页面中为每个产品进行配置。在代码中,通过 `profile.accessLevels["premium"]` 检查相应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户根据所购产品获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编写代码之前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五样东西后,就可以开始写代码了。告诉你的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",它就能生成正确的初始化和付费墙获取代码。 ::: ### 准备就绪后进行设置 \{#set-up-when-ready\} 这些不是开始编码的必要条件,但随着集成的深入,你会需要用到它们: - **A/B 测试**:在 **Placements** 页面进行配置,无需更改代码。 [A/B 测试](ab-tests) - **更多流程和版位**:使用不同的版位 ID 添加更多 `getFlow` 调用。 - **分析集成**:在 **Integrations** 页面进行配置,具体设置因集成而异。请参阅[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 向 LLM 提供 Adapty 文档 \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) [Context7](https://context7.com) 是一个 MCP 服务器,让你的 LLM 可以直接访问最新的 Adapty 文档。LLM 会根据你的提问自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 以及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the iOS SDK ``` :::warning 尽管 Context7 无需手动粘贴文档链接,但实现顺序仍然重要。请按照下方的[实现演练](#implementation-walkthrough)逐步操作,确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 你可以以纯文本 Markdown 格式访问任何 Adapty 文档。在 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor.md](https://adapty.io/docs/zh/adapty-cursor.md)。 下面[实现步骤](#implementation-walkthrough)中的每个阶段都包含一个"发送给 LLM"块,其中有可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下面的[索引文件和平台专属子集](#plain-text-doc-index-files)。 ## 实现步骤 \{#implementation-walkthrough\} 本指南的其余部分按实现顺序介绍 Adapty 集成。每个阶段包含需要发送给 LLM 的文档、完成后应看到的结果以及常见问题。 ### 规划集成方案 \{#plan-your-integration\} 在动手写代码之前,先让你的 LLM 分析项目结构,制定实施方案。如果你使用的 AI 工具支持规划模式(如 Cursor 或 Claude Code 的 plan 模式),建议先启用,让 LLM 在写代码前同时读取你的项目结构和 Adapty 文档。 告诉你的 LLM 你使用哪种购买方式——这会影响它需要参考的指南: - [**Adapty Flow Builder**](adapty-flow-builder):在 Adapty 的无代码编辑工具中创建流程,SDK 会自动渲染。 - [**手动创建付费墙**](ios-quickstart-manual):用代码构建自己的付费墙界面,但仍使用 Adapty 获取产品并处理购买。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅使用 Adapty 进行分析和集成。 不确定选哪个?请阅读[快速入门中的对比表格](ios-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 通过 Xcode 中的 Swift Package Manager 安装 Adapty SDK 包,并使用你的 Public SDK key 进行激活。这是一切的基础——没有它,其他功能都无法正常运行。 **指南:** [安装并配置 Adapty SDK](sdk-installation-ios) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-ios.md ``` :::tip[Checkpoint] - **预期结果:** 应用成功构建并运行,Xcode 控制台显示 Adapty 激活日志。 - **常见问题:** 出现 "Public API key is missing" → 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 展示流程或付费墙并处理购买 \{#show-flows-or-paywalls-and-handle-purchases\} 通过版位 ID 获取流程或付费墙、展示它,并处理购买事件。具体需要参考哪些指南,取决于你处理购买的方式。 每完成一个购买步骤后,都要在沙盒中测试一下——不要等到最后再统一测试。详见[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **指南:** - [使用 Flow Builder 启用购买(快速入门)](ios-quickstart-paywalls) - [获取流程及其配置](get-pb-paywalls) - [展示流程](ios-present-paywalls) - [处理流程事件](ios-handling-events) - [响应按钮操作](handle-paywall-actions) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/ios-quickstart-paywalls.md - https://adapty.io/docs/zh/get-pb-paywalls.md - https://adapty.io/docs/zh/ios-present-paywalls.md - https://adapty.io/docs/zh/ios-handling-events.md - https://adapty.io/docs/zh/handle-paywall-actions.md ``` :::tip[检查点] - **预期效果:** 流程界面显示你配置的产品,点击某个产品会触发沙盒购买对话框。 - **注意事项:** 流程为空或出现 `getFlow` 错误 → 请确认版位 ID 与看板中的配置完全一致,且该版位已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="手动付费墙"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](ios-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products) - [渲染通过远程配置设计的付费墙](present-remote-config-paywalls) - [发起购买](making-purchases) - [恢复购买](restore-purchase) 将以下内容发送给您的 LLM: ``` 在编写代码之前,请阅读以下 Adapty 文档: - https://adapty.io/docs/zh/ios-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products.md - https://adapty.io/docs/zh/present-remote-config-paywalls.md - https://adapty.io/docs/zh/making-purchases.md - https://adapty.io/docs/zh/restore-purchase.md ``` :::tip[检查点] - **预期效果:** 自定义付费墙能展示从 Adapty 获取的产品,点击产品会触发沙盒购买弹窗。 - **常见问题:** 产品数组为空 → 请确认付费墙在看板中已分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **指南:** - [Observer 模式概述](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode) - [在 Observer 模式中上报交易](report-transactions-observer-mode) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode.md - https://adapty.io/docs/zh/report-transactions-observer-mode.md ``` :::tip[检查点] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易会出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认你已向 Adapty 上报交易,并已配置 App Store Server Notifications。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买完成后,检查用户画像中的活跃访问等级以控制高级内容的访问权限。 **指南:** [检查订阅状态](ios-check-subscription-status) 发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/ios-check-subscription-status.md ``` :::tip[检查点] - **预期结果:** 沙盒购买后,`profile.accessLevels["premium"]?.isActive` 返回 `true`。 - **常见问题:** 购买后 `accessLevels` 为空 → 检查 Adapty 控制台中该产品是否已分配访问等级。 ::: ### 关联用户 \{#identify-users\} 将你的应用用户账号与 Adapty 用户画像关联,确保购买记录跨设备同步。 :::important 如果你的应用无需登录,请跳过此步骤。 ::: **指南:** [关联用户](ios-quickstart-identify) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/ios-quickstart-identify.md ``` :::tip[检查点] - **预期效果:** 调用 `Adapty.identify("your-user-id")` 后,看板 **Profiles** 页面会显示你的自定义用户 ID。 - **注意事项:** 请在激活之后、获取付费墙之前调用 `identify`,以避免用户画像被归因到匿名用户。 ::: ### 准备发布 \{#prepare-for-release\} 沙盒环境中的集成测试通过后,请按照发布检查清单逐项确认,确保一切已为生产环境做好准备。 **指南:** [发布检查清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[检查点] - **预期结果:** 所有清单项目均已确认:商店连接、服务器通知、购买流程、访问等级检查以及隐私要求。 - **注意事项:** 若未配置 App Store 服务器通知,请在 **App settings → iOS SDK** 中进行设置,否则事件将不会显示在看板中。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单个页面范围的更广泛上下文,我们提供了索引文件,列出或汇总了所有 Adapty 文档: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面的 `.md` 链接。这是一项[新兴标准](https://llmstxt.org/),旨在让网站对 LLM 更加友好。注意,对于某些 AI 助手(如 ChatGPT),你需要先下载 `llms.txt`,再作为文件上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站合并为单个文件。体积很大,仅在需要完整内容时使用。 - iOS 专属的 [`ios-llms.txt`](https://adapty.io/docs/zh/ios-llms.txt) 和 [`ios-llms-full.txt`](https://adapty.io/docs/zh/ios-llms-full.txt):平台专属子集,与完整站点相比可节省 token 用量。 --- # File: get-pb-paywalls --- --- title: "获取流程与付费墙 - iOS" description: "在 iOS 应用中从 Adapty 获取流程和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" /> 在[设计好流程或付费墙编辑工具付费墙](adapty-paywall-builder)之后,你可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体方法如下所示。 :::tip 想看看 Adapty SDK 是如何集成到移动应用中的真实示例?查看我们的[示例应用](sample-apps),其中展示了完整的集成流程,包括展示付费墙、发起购买以及其他基础功能。 ::: <details> <summary>开始之前</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品加入其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙加入其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-ios)。 </details> ## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已通过 Flow Builder 或付费墙编辑工具设计了流程或付费墙,无需在移动端代码中手动编写渲染逻辑来向用户展示它。这类流程或付费墙本身已包含展示内容和展示方式。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动端将其呈现出来。 尽可能早地获取流程或付费墙及其[视图配置](get-pb-paywalls#fetch-the-view-configuration)——最好在展示之前提前获取。一旦获取到视图配置,SDK 就会在后台开始下载并缓存相关图片。获取得越早,这些下载就有越充裕的时间完成。当你展示流程或付费墙时,其配置和图片可能已经缓存完毕、随时可用。 要获取流程或付费墙,请使用 `getFlow` 方法: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // 请求的流程/付费墙 } catch { // 处理错误 } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // 请求的流程/付费墙 case let .failure(error): // 处理错误 } } ``` </TabItem> </Tabs> 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。即你在 Adapty 看板创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时优先返回缓存数据。这样用户可能无法获取最新数据,但加载速度会更快,不受网络质量影响。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后依然保留,仅在应用重新安装或手动清理时才会清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加快付费墙的加载速度,并设有独立的备用服务器,以防 CDN 不可用。该系统旨在确保你始终获取最新版本的付费墙,同时在网络条件有限的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若达到超时时间,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能包含多个不同的请求。</p> | 响应参数: | 参数 | 描述 | | :-------- | :---------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、远程配置,以及 `hasViewConfiguration` 标志(用于指示该流程是否包含视图配置)。如需预加载产品、自定义 UI 或以编程方式进行检查,请调用 `getPaywallProducts(flow:)`。 | ## 获取视图配置 \{#fetch-the-view-configuration\} 获取流程或付费墙后,通过 `flow.hasViewConfiguration` 检查其是否包含视图配置。该标志用于区分版位在 Adapty 看板中的设计方式: - **`true`** — 该版位使用 **Flow Builder**(流程)或 **Paywall Builder**(付费墙)设计,Adapty 会自动为你渲染 UI。请继续执行以下步骤来获取视图配置,并[展示流程或付费墙](ios-present-paywalls)。 - **`false`** — 该版位是不含 Builder UI 的自定义付费墙。 使用 `getFlowConfiguration` 方法加载视图配置。 ```swift showLineNumbers guard flow.hasViewConfiguration else { // handle as remote config paywall return } let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) ``` 参数: | 参数 | 必填性 | 描述 | | :----------------------- | :------------- | :---------- | | **forFlow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | | **locale** | <p>可选</p><p>默认值:`nil`</p> | [付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。格式为语言代码,可包含一个或两个以 `-` 分隔的子标签(如 `en`、`pt-br`)。详见[本地化与语言代码](localizations-and-locale-codes)。 | | **loadTimeout** | 默认值:5 秒 | 该参数限制此方法的超时时间。超时后将返回缓存数据或本地备用数据。请注意,在极少数情况下,由于该方法底层可能包含多个请求,实际超时时间可能略晚于 `loadTimeout` 中设定的值。 | | **products** | 可选 | 提供 `AdaptyPaywallProduct` 对象数组,以优化产品在屏幕上的显示时机。若传入 `nil`,AdaptyUI 将自动获取所需产品。 | | **systemRequestsHandler** | 可选 | 符合 `AdaptySystemRequestsHandler` 协议的对象,用于处理流程操作触发的系统权限请求和评价请求。仅当流程中包含此类操作时才需要提供。 | | **assetsResolver** | 可选 | 类型为 `[String: AdaptyCustomAsset]` 的字典,用于覆盖流程/付费墙中的图片和视频资源。详见[自定义资源](#customize-assets)。 | | **timerResolver** | 可选 | 符合 `AdaptyTimerResolver` 协议的对象,用于为开发者自定义计时器提供结束时间。详见[设置开发者自定义计时器](#set-up-developer-defined-timers)。 | 加载完成后,[展示流程/付费墙](ios-present-paywalls)。 ## 为默认目标受众获取流程或付费墙以加快加载速度 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,流程和付费墙的获取速度几乎是即时的,无需担心性能问题。但如果你的版位和目标受众数量较多,而用户的网络状况又较差,获取流程或付费墙的时间可能会比预期更长。在这种情况下,你可能希望展示一个默认的流程或付费墙,以保证用户体验的流畅性,而不是让用户看到空白页面。 为了解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程或付费墙。但请务必了解,推荐的做法是使用 `getFlow` 方法获取流程或付费墙,详情请参阅上方的[获取付费墙信息](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)部分。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显缺点: - **潜在的向后兼容性问题**:如果你需要针对不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么设计兼容当前(旧版)版本的付费墙,要么接受当前(旧版)用户可能会遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义用户属性的定向)。 如果你愿意接受这些限制,以换取更快的流程或付费墙加载速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)介绍的 `getFlow`。 ::: ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符,即你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——有缓存时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有卸载重装或手动清除时才会被清空。</p> | ## 自定义资源 \{#customize-assets\} 要自定义付费墙/流程中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载时,先展示本地预览图。 - 在视频播放前展示预览图。 - 提供视频的像素分辨率,以便播放器在视频加载前预留布局空间(宽高比 = `width / height`)。传入 `nil` 可跳过此项。 下面是一个通过简单字典提供自定义资源的示例: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image and a known resolution "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!), resolution: CGSize(width: 1080, height: 1920) ) ), ] let flowConfig = try await AdaptyUI.getFlowConfiguration( forFlow: flow, assetsResolver: customAssets ) ``` :::note 如果找不到某个资源,付费墙/流程将回退到其默认外观。 ::: ## 设置开发者定义的计时器 \{#set-up-developer-defined-timers\} 要在移动应用中使用自定义计时器,请创建一个遵循 `AdaptyTimerResolver` 协议的对象。该对象定义每个自定义计时器的渲染方式。如果您愿意,也可以直接使用 `[String: Date]` 字典,因为它已经符合该协议。以下是一个示例: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` 在此示例中,`CUSTOM_TIMER_NY` 和 `CUSTOM_TIMER_6H` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 确保你的应用动态地为每个计时器更新正确的值。例如: - `CUSTOM_TIMER_NY`:距离计时器结束(例如元旦)的剩余时间。 - `CUSTOM_TIMER_6H`:从用户打开付费墙时开始计算的 6 小时倒计时的剩余时间。 </SDKv4> <SDKv3> 在 [Adapty 看板的付费墙编辑工具中完成付费墙的视觉设计](adapty-paywall-builder)之后,你可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体步骤如下。 请注意,本文介绍的是通过付费墙编辑工具自定义的付费墙。如果你是手动实现付费墙,请参阅[获取远程配置付费墙的付费墙和产品](fetch-paywalls-and-products)。 :::tip 在您开始在移动应用中展示付费墙之前,请确保已完成以下步骤: <details> <summary>在您开始在移动应用中展示付费墙之前</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-ios)。 </details> ## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果您已[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动应用代码中手动处理渲染逻辑来向用户展示它。此类付费墙同时包含应展示的内容及展示方式。尽管如此,您仍需通过版位获取其 ID、获取视图配置,然后在移动应用中进行展示。 为确保最佳性能,务必尽早获取付费墙及其[视图配置](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够的时间下载图片。 使用 `getPaywall` 方法获取付费墙: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 控制台中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为由一个或两个子标签通过减号(**-**)分隔的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及推荐使用方式的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。这样用户可能无法获得最新数据,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然有效,仅在应用卸载重装或手动清除时才会被清空。</p><p></p><p>Adapty SDK 通过两层方式在本地存储付费墙:上述定期更新的缓存和[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙获取,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的付费墙,同时在网络条件较差时也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>此值限制该方法的超时时间。超时后将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略长于 `loadTimeout` 中指定的时间,因为该操作在底层可能包含多个不同请求。</p> | 响应参数: | 参数 | 描述 | | :-------- | :---------- | | Paywall | 一个 [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中开启了 **Show on device** 开关。如果未开启该选项,将无法获取视图配置。 ::: 获取付费墙后,检查其是否包含视图配置——视图配置的存在表明该付费墙是通过付费墙编辑工具创建的。这将指导你如何展示付费墙。如果存在视图配置,则将其视为付费墙编辑工具付费墙;否则,[将其作为远程配置付费墙处理](present-remote-config-paywalls)。 使用 `getPaywallConfiguration` 方法加载视图配置。 ```swift showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, products: products ) // use loaded configuration } catch { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------------- | :---------- | | **paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 | | **loadTimeout** | 默认:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用内容。请注意,由于该方法底层可能包含多个请求,在极少数情况下实际超时时间可能略晚于 `loadTimeout` 中指定的值。 | | **products** | 可选 | 传入 `AdaptyPaywallProduct` 对象数组,以优化产品在屏幕上的展示时机。若传入 `nil`,AdaptyUI 将自动获取所需产品。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder)以及如何正确使用语言代码,详见[此处](localizations-and-locale-codes)。 ::: 加载完成后,[展示付费墙](ios-present-paywalls)。 ## 获取默认目标受众的付费墙以加速获取 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙几乎可以立即获取,因此无需担心加速此过程。但是,当您拥有大量目标受众和付费墙,且用户网络连接较弱时,获取付费墙可能需要较长时间。在这种情况下,您可能希望展示默认付费墙以确保流畅的用户体验,而不是不显示任何付费墙。 要解决这个问题,你可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的付费墙。但请务必注意,推荐的方式仍然是使用 `getPaywall` 方法获取付费墙,详见上方的[获取付费墙信息](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显缺点: - **潜在的向后兼容性问题**:如果您需要为不同版本的应用(当前版本和未来版本)展示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一付费墙,这意味着您将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请坚持使用[上文](#get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)描述的 `getPaywall`。 ::: ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` :::note `getPaywallForDefaultAudience` 方法从 iOS SDK 2.11.2 版本起可用。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 控制台中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个子标签通过减号(**-**)分隔的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言代码及推荐使用方式的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。这样用户可能无法获得最新数据,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然有效,仅在应用卸载重装或手动清除时才会被清空。</p> | ## 自定义资源 \{#customize-assets\} 要在付费墙中自定义图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 为部分用户显示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在视频播放前,先显示预览图。 :::important 要使用此功能,请将 Adapty iOS SDK 更新至 3.7.0 或更高版本。 ::: 以下是通过简单字典提供自定义资源的示例: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // 使用自定义 ID 显示本地图片 "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // 远程主图加载时显示本地预览图 "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // 显示带预览图的本地视频 "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!) ) ), ] let paywallConfig = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, assetsResolver: customAssets ) ``` :::note 如果找不到某个资源,付费墙将回退到其默认外观。 ::: ## 设置开发者自定义计时器 \{#set-up-developer-defined-timers\} 要在移动应用中使用自定义计时器,需创建一个遵循 `AdaptyTimerResolver` 协议的对象。该对象定义了每个自定义计时器的渲染方式。如果你更喜欢,也可以直接使用 `[String: Date]` 字典,因为它已经符合该协议。以下是示例: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` 在此示例中,`CUSTOM_TIMER_NY` 和 `CUSTOM_TIMER_6H` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 确保你的应用动态地为每个计时器更新正确的值。例如: - `CUSTOM_TIMER_NY`:距离计时器结束时间(如元旦)的剩余时间。 - `CUSTOM_TIMER_6H`:用户打开付费墙后开始的 6 小时倒计时的剩余时间。 </SDKv3> --- # File: ios-present-paywalls --- --- title: "展示流程和付费墙 - iOS" description: "在您的 iOS 应用中向用户展示流程和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" label="展示流程和付费墙" /> 如果您已经创建了流程或付费墙,无需在移动应用代码中手动处理渲染逻辑,它本身就包含了展示内容和展示方式的所有信息。 若要获取下文使用的 `AdaptyUI.FlowConfiguration` 对象,请参阅[获取流程和付费墙](get-pb-paywalls)。 ## 在 SwiftUI 中展示流程和付费墙 \{#present-flows-and-paywalls-in-swiftui\} ### 以模态视图形式展示 \{#present-as-a-modal-view\} 如需在设备屏幕上以模态视图的形式展示流程或付费墙,请在 SwiftUI 中使用 `.flow` 修饰符。最简调用需要 `isPresented`、`flowConfiguration` 以及五个必填回调: ```swift showLineNumbers title="SwiftUI" .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { _, _ in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { _, _ in /* handle the error */ }, didFinishRestore: { _ in /* check access level and dismiss */ }, didFailRestore: { _ in /* handle the error */ }, didReceiveError: { _ in flowPresented = false } ) ``` 如需更多控制,可添加可选回调,例如 `didPerformAction` 来处理按钮点击事件: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the flow or paywall var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didPerformAction: { action in switch action { case .close: flowPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) } ``` Parameters: | 参数 | 是否必填 | 描述 | |:----------------------------------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | 必填 | 一个绑定值,用于控制流程或付费墙页面是否显示。 | | **flowConfiguration** | 必填 | 包含流程或付费墙视觉信息的 `AdaptyUI.FlowConfiguration` 对象。使用 `AdaptyUI.getFlowConfiguration(forFlow:)` 方法获取。详情请参阅[获取流程和付费墙](get-pb-paywalls)。 | | **didFinishPurchase** | 必填 | 当 `Adapty.makePurchase()` 成功完成时触发。流程不会自动关闭——可在此处将展示绑定值设为 `false`,或不做任何操作以让流程在购买后继续。 | | **didFailPurchase** | 必填 | 当 `Adapty.makePurchase()` 失败时触发。 | | **didFinishRestore** | 必填 | 当 `Adapty.restorePurchases()` 成功完成时触发。 | | **didFailRestore** | 必填 | 当 `Adapty.restorePurchases()` 失败时触发。 | | **didReceiveError** | 必填 | 当发生渲染错误或流程脚本运行时错误(例如 JavaScript 异常,`AdaptyUIError` 错误码 `4105`)时触发。如遇渲染错误,请[联系 Adapty 支持](mailto:support@adapty.io)。 | | **fullScreen** | 可选 | 决定流程或付费墙以全屏模式还是以浮层方式呈现。默认值为 `true`。 | | **didAppear** | 可选 | 当流程或付费墙视图呈现时触发。 | | **didDisappear** | 可选 | 当流程或付费墙视图关闭时触发。 | | **didPerformAction** | 可选 | 当用户点击按钮时触发。预定义了两个动作 ID:`close` 和 `openURL`;其他为自定义动作,可在编辑器中设置。 | | **didSelectProduct** | 可选 | 当用户或系统选中某个产品准备购买时触发。 | | **didStartPurchase** | 可选 | 当用户开始购买流程时触发。 | | **didFinishWebPaymentNavigation** | 可选 | 当网页支付导航完成时触发。 | | **didStartRestore** | 可选 | 当用户开始恢复购买流程时触发。 | | **didFailLoadingProducts** | 可选 | 当产品加载出错时触发。返回 `true` 可重新尝试加载。 | | **didPartiallyLoadProducts** | 可选 | 当产品部分加载完成时触发。 | | **showAlertItem** | 可选 | 一个绑定值,用于控制流程或付费墙上方弹窗的显示。 | | **showAlertBuilder** | 可选 | 用于渲染弹窗视图的函数。 | | **placeholderBuilder** | 可选 | 用于在流程或付费墙加载时渲染占位视图的函数。默认为 `ProgressView`。 | 有关参数的更多详细信息,请参阅 [iOS - 处理事件](ios-handling-events) 主题。 ### 以非模态视图方式呈现 \{#present-as-a-non-modal-view\} 你也可以将流程和付费墙作为导航目标或内联视图嵌入到应用的导航流程中。直接在 SwiftUI 视图中使用 `AdaptyFlowView`: ```swift showLineNumbers title="SwiftUI" AdaptyFlowView( flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { product, purchaseResult in // Dismiss the view, or do nothing to let the flow continue }, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didReceiveError: { error in // Handle the error (rendering or JS exception from the flow script). } ) ``` ## 在 UIKit 中展示流程和付费墙 \{#present-flows-and-paywalls-in-uikit\} 如需在设备屏幕上展示流程或付费墙,请按以下步骤操作: 1. 使用 `AdaptyUI.flowController(with:delegate:)` 方法初始化要展示的可视化流程: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: <AdaptyUI.FlowConfiguration>, delegate: <AdaptyFlowControllerDelegate> ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :---------- | | **flowConfiguration** | 必填 | 一个 `AdaptyUI.FlowConfiguration` 对象,包含流程或付费墙的视觉详情。使用 `AdaptyUI.getFlowConfiguration(forFlow:)` 方法。详情请参阅 [获取流程和付费墙](get-pb-paywalls)。 | | **delegate** | 必填 | 一个 `AdaptyFlowControllerDelegate`,用于监听流程和付费墙事件。详情请参阅 [处理流程和付费墙事件](ios-handling-events)。 | 返回值: | 对象 | 描述 | | :---------------------- | :------------------------------------------------------- | | **AdaptyFlowController** | 表示所请求的流程或付费墙页面的对象。 | 2. 对象成功创建后,即可将其显示在设备屏幕上: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::tip 想看看 Adapty SDK 在移动应用中集成的真实案例?请查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、发起购买以及其他基本功能。 ::: </SDKv4> <SDKv3> 如果你已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示。此类付费墙已包含应展示的内容及其展示方式。 要获取下面使用的 `AdaptyUI.PaywallConfiguration` 对象,请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 ## 在 SwiftUI 中展示付费墙 \{#present-paywalls-in-swiftui\} ### 以模态视图形式展示 \{#present-as-a-modal-view\} 如需在设备屏幕上以模态视图的形式展示可视化付费墙,请在 SwiftUI 中使用 `.paywall` 修饰符: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the paywall var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, profile in paywallPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` I notice the input contains only "Parameters:" with no MDX content to translate. However, following the instructions, I should return whatever was given. Since there is only this minimal text: Parameters: | 参数 | 是否必填 | 描述 | |:----------------------------------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | 必填 | 用于控制付费墙页面是否显示的绑定值。 | | **paywallConfiguration** | 必填 | 包含付费墙视觉细节的 `AdaptyUI.PaywallConfiguration` 对象。使用 `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)` 方法。详情请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 | | **didFailPurchase** | 必填 | 当 `Adapty.makePurchase()` 失败时触发。 | | **didFinishRestore** | 必填 | 当 `Adapty.restorePurchases()` 成功完成时触发。 | | **didFailRestore** | 必填 | 当 `Adapty.restorePurchases()` 失败时触发。 | | **didFailRendering** | 必填 | 当渲染界面时发生错误时触发。如遇此情况,请[联系 Adapty 支持团队](mailto:support@adapty.io)。 | | **fullScreen** | 可选 | 决定付费墙以全屏模式还是弹窗模式显示,默认值为 `true`。 | | **didAppear** | 可选 | 当付费墙视图呈现时触发。 | | **didDisappear** | 可选 | 当付费墙视图关闭时触发。 | | **didPerformAction** | 可选 | 当用户点击按钮时触发。不同按钮对应不同的操作 ID,其中 `close` 和 `openURL` 是预定义的操作 ID,其他则为自定义 ID,可在编辑工具中设置。 | | **didSelectProduct** | 可选 | 当某个产品被用户或系统选中以进行购买时触发此回调。 | | **didStartPurchase** | 可选 | 当用户开始购买流程时触发。 | | **didFinishPurchase** | 可选 | 当 `Adapty.makePurchase()` 成功完成时触发。 | | **didFinishWebPaymentNavigation** | 可选 | 当网页支付导航完成时触发。 | | **didStartRestore** | 可选 | 当用户开始恢复购买流程时触发。 | | **didFailLoadingProducts** | 可选 | 当产品加载过程中出现错误时触发。返回 `true` 可重试加载。 | | **didPartiallyLoadProducts** | 可选 | 当产品仅部分加载完成时触发。 | | **showAlertItem** | 可选 | 用于管理付费墙上方提示项显示的绑定值。 | | **showAlertBuilder** | 可选 | 用于渲染提示视图的函数。 | | **placeholderBuilder** | 可选 | 用于在付费墙加载时渲染占位视图的函数。 | 有关参数的更多详细信息,请参阅 [iOS - 处理事件](ios-handling-events) 主题。 ### 以非模态视图形式呈现 \{#present-as-a-non-modal-view\} 你也可以将付费墙作为导航目标或内联视图嵌入应用的导航流程中。在 SwiftUI 视图中直接使用 `AdaptyPaywallView`: ```swift showLineNumbers title="SwiftUI" AdaptyPaywallView( paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didFailRendering: { error in // Handle rendering error } ) ``` ## 在 UIKit 中展示付费墙 \{#present-paywalls-in-uikit\} 要在设备屏幕上显示可视化付费墙,请按以下步骤操作: 1. 使用 `.paywallController(for:products:viewConfiguration:delegate:)` 方法初始化要显示的可视化付费墙: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :---------- | | **paywall configuration** | 必填 | 一个 `AdaptyUI.PaywallConfiguration` 对象,包含付费墙的视觉详情。使用 `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)` 方法。详情请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 | | **delegate** | 必填 | 一个 `AdaptyPaywallControllerDelegate`,用于监听付费墙事件。详情请参阅[处理付费墙事件](ios-handling-events)。 | 返回值: | 对象 | 描述 | | :---------------------- | :--------------------------------------------------- | | **AdaptyPaywallController** | 表示所请求付费墙页面的对象 | 2. 对象成功创建后,可以将其显示在设备屏幕上: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::tip 想看 Adapty SDK 在移动应用中集成的真实案例?查看我们的[示例应用](sample-apps),其中展示了完整的接入流程,包括显示付费墙、发起购买以及其他基本功能。 ::: </SDKv3> --- # File: handle-paywall-actions --- --- title: "响应流程操作 - iOS" description: "在 iOS 应用中处理付费墙和用户引导流程中的按钮操作及用户输入。" --- <SDKv4> 如果你正在使用 Adapty Flow Builder 或付费墙编辑工具构建流程或付费墙,请务必正确设置按钮: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配预置动作或创建自定义动作 ID。 2. 在应用代码中编写处理每个动作的逻辑。 本指南介绍如何在代码中处理自定义动作和预置动作。 :::warning **只有流程/付费墙关闭和 URL 打开会被自动处理。** 其他所有按钮动作都需要在应用代码中实现相应的响应逻辑。 ::: :::note iOS SDK 可以通过 `AdaptySystemRequestsHandler` 响应系统权限请求,例如推送通知或相机访问权限。目前 flow 尚不触发这些请求,因此暂时无需处理。 ::: ## 关闭流程与付费墙 \{#close-flows-and-paywalls\} 要添加一个可以关闭流程或付费墙的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,为 `close` 动作实现相应的处理逻辑。 :::info 在 iOS SDK 中,`close` 动作默认会触发关闭流程或付费墙。不过,如有需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。 ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow or paywall default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加 **Link** 元素,并像处理带有 **Open URL** 动作的按钮一样处理它。 ::: 要添加一个从流程或付费墙中打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在编辑工具中,添加一个按钮,为其指定 **Open URL** 动作,并输入你想打开的 URL。 2. 在应用代码中,为 `openURL` 动作实现一个处理程序,用于在浏览器中打开接收到的 URL。 :::info 在 iOS SDK 中,`openURL` 操作默认会触发打开对应 URL。不过,如有需要,你可以在代码中覆盖此行为。 ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## 处理自定义动作 \{#handle-custom-actions\} 要添加一个处理其他动作的按钮: 1. 在编辑工具中,添加一个按钮,为其分配 **Custom** 动作,并设置一个 ID。 2. 在应用代码中,为你创建的动作 ID 实现对应的处理逻辑。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮来展示另一个流程或付费墙: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .custom(id): if id == "openNewPaywall" { // Display another flow or paywall } default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </SDKv4> <SDKv3> 如果你正在使用 Adapty 付费墙编辑工具构建付费墙,正确设置按钮至关重要: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理每个已分配的操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **只有购买、恢复购买、关闭付费墙和打开 URL 会被自动处理。** 其他所有按钮操作都需要在应用代码中实现相应的响应逻辑。 ::: ## 关闭付费墙 \{#close-paywalls\} 要添加一个可关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在您的应用代码中,实现 `close` 操作的处理程序以关闭付费墙。 :::info 在 iOS SDK 中,`close` 操作默认会触发关闭付费墙。但如有需要,您可以在代码中覆盖此行为。例如,关闭一个付费墙可能会触发打开另一个付费墙。 ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // default behavior break } } ``` ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果您想添加一组链接(例如使用条款和恢复购买),可以在付费墙编辑工具中添加一个 **Link** 元素,并以与带有 **Open URL** 操作的按钮相同的方式进行处理。 ::: 要添加一个可从付费墙打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入您想要打开的 URL。 2. 在您的应用代码中,实现 `openUrl` 操作的处理程序,在浏览器中打开接收到的 URL。 :::info 在 iOS SDK 中,`openUrl` 操作默认会触发打开 URL。但如有需要,您可以在代码中覆盖此行为。 ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior break } } ``` ## 登录应用 \{#log-into-the-app\} 要添加一个可让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 操作。 2. 在您的应用代码中,实现 `login` 操作的处理程序以识别您的用户。 ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .login: // Show a login screen let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController") controller.present(loginVC, animated: true) } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他任意操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并指定一个 ID。 2. 在您的应用代码中,实现您所创建的操作 ID 的处理程序。 例如,如果您有另一组订阅套餐或一次性购买,可以添加一个按钮来展示另一个付费墙: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .custom(id): if id == "openNewPaywall" { // 展示另一个付费墙 } } break } } ``` </SDKv3> --- # File: ios-handling-events --- --- title: "处理流程与付费墙事件 - iOS" description: "在 iOS 应用中处理流程与付费墙事件。" --- <SDKv4> :::important 本指南涵盖购买、恢复、产品选择和付费墙渲染的事件处理。你还需要实现按钮处理(关闭付费墙、打开链接等)。详情请参阅[处理按钮操作指南](handle-paywall-actions)。 ::: 流程和付费墙无需额外代码即可完成购买和恢复购买操作。但它们会产生一些事件,你的应用可以对这些事件做出响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及与购买相关的操作通知。请参阅以下内容,了解如何响应这些事件。 :::tip 想查看 Adapty SDK 集成到移动应用的真实示例吗?欢迎参考我们的[示例应用](sample-apps),其中展示了完整的配置流程,包括显示付费墙、进行购买及其他基础功能。 ::: ## 在 SwiftUI 中处理事件 \{#handling-events-in-swiftui\} 要控制或监控移动应用中流程或付费墙页面上发生的操作,请在 SwiftUI 中使用 `.flow` 修饰符: ```swift showLineNumbers title="Swift" @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` 对象。详情请参阅[获取流程和付费墙](get-pb-paywalls)。 | | **didFinishPurchase** | 必填 | 当 `Adapty.makePurchase()` 成功完成时触发。流程不会自动关闭——可在此处将展示绑定设为 `false`,或不做任何操作以让流程在购买后继续进行。 | | **didFailPurchase** | 必填 | 当 `Adapty.makePurchase()` 失败时触发。 | | **didFinishRestore** | 必填 | 当 `Adapty.restorePurchases()` 成功完成时触发。 | | **didFailRestore** | 必填 | 当 `Adapty.restorePurchases()` 失败时触发。 | | **didReceiveError** | 必填 | 当流程遇到渲染错误或流程脚本运行时错误(例如 JavaScript 异常,`AdaptyUIError` 代码 `4105`)时触发。若为渲染错误,请[联系 Adapty 支持](mailto:support@adapty.io)。 | | **placeholderBuilder** | 可选 | 在流程或付费墙加载期间渲染占位视图的函数。默认为 `ProgressView`。 | | **fullScreen** | 可选 | 控制流程或付费墙是以全屏模式还是以 sheet 形式显示。默认为 `true`。 | | **didAppear** | 可选 | 当流程或付费墙视图出现在屏幕上时触发。 | | **didDisappear** | 可选 | 当流程或付费墙视图被关闭时触发。 | | **didPerformAction** | 可选 | 当用户点击按钮时触发。预定义了两个动作 ID:`close` 和 `openURL`;其他为自定义动作,可在编辑工具中设置。 | | **didSelectProduct** | 可选 | 当用户或系统选择某个产品进行购买时触发。 | | **didStartPurchase** | 可选 | 当用户开始购买流程时触发。 | | **didFinishWebPaymentNavigation** | 可选 | 当网页支付导航完成时触发。 | | **didStartRestore** | 可选 | 当用户开始恢复购买流程时触发。 | | **didFailLoadingProducts** | 可选 | 当产品加载发生错误时触发。返回 `true` 可重试加载。 | | **didPartiallyLoadProducts** | 可选 | 当产品仅部分加载完成时触发。 | | **showAlertItem** | 可选 | 控制在流程或付费墙上方显示提示项的绑定值。 | | **showAlertBuilder** | 可选 | 渲染提示视图的函数。 | ## 在 UIKit 中处理事件 \{#handling-events-in-uikit\} 对于 UIKit 应用,事件通过 `AdaptyFlowControllerDelegate` 协议来处理。请参阅[展示流程与付费墙 - iOS](ios-present-paywalls),了解如何配置带有 `AdaptyFlowControllerDelegate` 的 `AdaptyFlowController`。 该协议声明了 13 个方法。其中 4 个没有默认实现,必须在遵循协议时实现:`didFinishPurchase`、`didFailPurchase`、`didFinishRestoreWith` 和 `didFailRestoreWith`。其余方法提供默认的空操作实现,可在需要自定义行为时覆盖。以下按用途对方法进行分组。 ### 生命周期 \{#lifecycle\} ```swift showLineNumbers title="Swift" func flowControllerDidAppear(_ controller: AdaptyFlowController) { } func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } ``` 这两个方法分别在流程或付费墙视图显示和关闭时触发。 ### 用户操作 \{#user-actions\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action ) { } ``` `AdaptyUI.Action` 的枚举值: - `.close` — 默认行为是关闭当前控制器。可以重写此方法以保持控制器显示或执行额外的清理操作。 - `.openURL(url:)` — 默认行为是通过 `UIApplication.shared.open(...)` 打开 URL。 - `.custom(id:)` — 当用户点击在编辑工具中设置了自定义动作 ID 的按钮时触发。 ### 产品选择 \{#product-selection\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didSelectProduct product: AdaptyPaywallProduct ) { } ``` 当用户或系统选择某个产品进行购买时触发。该产品包含完整的优惠信息(v4 中资格会自动判断,不再有单独的 `AdaptyPaywallProductWithoutDeterminingOffer` 类型)。 ### 购买事件 \{#purchase-events\} ```swift showLineNumbers title="Swift" 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 ) { } ``` `didFinishPurchase` 和 `didFailPurchase` 没有默认实现,必须自行实现。控制器在购买成功后不会自动关闭——在适当时机调用 `controller.dismiss(animated:)`,或者不做任何处理,让多屏幕流程在购买完成后继续进行。 ### 恢复事件 \{#restore-events\} ```swift showLineNumbers title="Swift" func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } func flowController( _ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile ) { } func flowController( _ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError ) { } ``` `didFinishRestoreWith` 和 `didFailRestoreWith` 没有默认实现。在关闭控制器之前,请检查返回的 `AdaptyProfile` 是否包含所需的访问等级。 ### 流程错误与产品加载错误 \{#flow-errors-and-product-loading-errors\} ```swift showLineNumbers title="Swift" 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 支持](mailto:support@adapty.io)。对于加载错误,可在 `didFailLoadingProductsWith` 中返回 `true` 来重试——适用于处理瞬时网络故障。 ### Web 支付导航 \{#web-payment-navigation\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, error: AdaptyError? ) { } ``` Web 支付导航完成后触发,无论成功或失败。 </SDKv4> <SDKv3> :::important 本指南介绍如何处理购买、恢复、产品选择和付费墙渲染等相关事件。你还必须实现按钮的处理逻辑(关闭付费墙、打开链接等)。详情请参阅[处理按钮操作的指南](handle-paywall-actions)。 ::: 使用[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上购买相关操作的通知。请参阅下文了解如何响应这些事件。 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.0 或更高版本。 :::tip 想看 Adapty SDK 集成到移动应用中的真实示例?请查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、发起购买以及其他基本功能。 ::: ## 在 SwiftUI 中处理事件 \{#handling-events-in-swiftui\} 要在移动应用的付费墙界面中控制或监听相关流程,请在 SwiftUI 中使用 `.paywall` 修饰符: ```swift showLineNumbers title="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 } ) } ``` 你只需注册实际用到的闭包参数,不需要的参数可以直接省略。这样一来,未使用的闭包参数就不会被创建。 | 参数 | 是否必填 | 描述 | |:----------------------------------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | 必填 | 用于控制付费墙页面是否显示的绑定值。 | | **paywallConfiguration** | 必填 | 包含付费墙视觉信息的 `AdaptyUI.PaywallConfiguration` 对象。使用 `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)` 方法。详情请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 | | **didFailPurchase** | 必填 | 购买因错误失败时触发(例如,不允许支付、网络问题、无效产品)。用户取消或待处理付款时不触发。 | | **didFinishRestore** | 必填 | 购买成功完成时触发。 | | **didFailRestore** | 必填 | 恢复购买失败时触发。 | | **didFailRendering** | 必填 | 渲染界面时发生错误则触发。遇到此情况请[联系 Adapty 支持](mailto:support@adapty.io)。 | | **fullScreen** | 可选 | 决定付费墙以全屏模式还是弹窗模式显示。默认为 `true`。 | | **didAppear** | 可选 | 付费墙视图出现在屏幕上时触发。当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button)并在应用内浏览器中打开网页付费墙时,也会触发。 | | **didDisappear** | 可选 | 付费墙视图关闭时触发。从付费墙在应用内浏览器中打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)从屏幕上消失时,也会触发。 | | **didPerformAction** | 可选 | 用户点击按钮时触发。不同按钮对应不同的动作 ID。其中两个动作 ID 为预定义:`close` 和 `openURL`,其余为自定义 ID,可在编辑工具中设置。 | | **didSelectProduct** | 可选 | 当产品被选中购买(由用户或系统触发)时,此回调将被调用。 | | **didStartPurchase** | 可选 | 用户开始购买流程时触发。 | | **didFinishPurchase** | 可选 | 购买成功完成时触发。 | | **didFinishWebPaymentNavigation** | 可选 | 尝试打开[网页付费墙](web-paywall)进行购买后触发,无论成功与否。 | | **didStartRestore** | 可选 | 用户开始恢复流程时触发。 | | **didFailLoadingProducts** | 可选 | 产品加载过程中发生错误时触发。返回 `true` 可重试加载。 | | **didPartiallyLoadProducts** | 可选 | 产品部分加载完成时触发。 | | **showAlertItem** | 可选 | 用于管理付费墙上方提示项显示的绑定值。 | | **showAlertBuilder** | 可选 | 用于渲染提示视图的函数。 | | **placeholderBuilder** | 可选 | 付费墙加载期间用于渲染占位视图的函数。 | ## 在 UIKit 中处理事件 \{#handling-events-in-uikit\} 要控制或监听移动应用中付费墙页面上发生的事件,请实现 `AdaptyPaywallControllerDelegate` 的各个方法。 ### 用户生成的事件 \{#user-generated-events\} #### 产品选择 \{#product-selection\} 当用户选择某个产品进行购买时,将调用以下方法: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### 开始购买 \{#started-purchase\} 当用户发起购买流程时,将调用此方法: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didStartPurchase product: AdaptyPaywallProduct) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> 在 Observer 模式下不会触发此回调。详情请参阅 [iOS - 在 Observer 模式下展示付费墙编辑工具付费墙](ios-present-paywall-builder-paywalls-in-observer-mode)。 #### 通过网页付费墙发起购买 \{#started-purchase-using-a-web-paywall\} 如果用户通过[付费墙网页](web-paywall)发起购买流程,将会调用以下方法: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### 购买成功或取消 \{#successful-or-canceled-purchase\} 如果购买成功,将调用以下方法: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer, purchaseResult: AdaptyPurchaseResult ) { } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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" } } ``` </Details> 在这种情况下,我们建议关闭付费墙页面。 在 Observer 模式下,该回调不会被调用。详情请参阅 [iOS - 在 Observer 模式下展示付费墙编辑工具付费墙](ios-present-paywall-builder-paywalls-in-observer-mode)。 #### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,此方法将被调用。这包括 StoreKit 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `didFinishPurchase`(结果为已取消),待处理的支付不会触发此方法。 ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } } } ``` </Details> 在 Observer 模式下不会调用此方法。详情请参阅 [iOS - 在 Observer 模式下展示付费墙编辑工具构建的付费墙](ios-present-paywall-builder-paywalls-in-observer-mode)。 #### 通过网页付费墙购买失败 \{#failed-purchase-using-a-web-paywall\} 如果 `Adapty.openWebPaywall()` 失败,将调用以下方法: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailWebPaymentNavigation product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } } } ``` </Details> #### 恢复购买成功 \{#successful-restore\} 如果恢复购买成功,将调用以下方法: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishRestoreWith profile: AdaptyProfile ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } ] } } ``` </Details> 我们建议在用户拥有所需 `accessLevel` 时关闭该界面。请参阅[订阅状态](subscription-status)了解如何检查。 #### 恢复失败 \{#failed-restore\} 如果恢复购买失败,将调用以下方法: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRestoreWith error: AdaptyError ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果你在初始化时没有传入产品数组,AdaptyUI 会自动从服务器获取所需对象。如果此操作失败,AdaptyUI 会通过调用以下方法来上报错误: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailLoadingProductsWith error: AdaptyError ) -> Bool { return true } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> 如果返回 `true`,AdaptyUI 将在 2 秒后重新发起请求。 #### 渲染错误 \{#rendering-errors\} 如果在界面渲染过程中发生错误,将通过以下方法上报: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRenderingWith error: AdaptyError ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> 正常情况下不应出现此类错误,如果您遇到了,请告知我们。 </SDKv3> --- # File: ios-use-fallback-paywalls --- --- title: "iOS - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况" --- 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} 1. 将备用 JSON 文件添加到项目 bundle:打开 XCode 的 **File** 菜单,选择 **Add Files to "YourProjectName"** 选项。 2. 在获取目标流程、付费墙或用户引导**之前**,调用 `.setFallback` 方法。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { if let urlPath = Bundle.main.url(forResource: fileName, withExtension: "json") { try await Adapty.setFallback(fileURL: urlPath) } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if let url = Bundle.main.url(forResource: "ios_fallback", withExtension: "json") { Adapty.setFallback(fileURL: url) } ``` </TabItem> </Tabs> 参数: | 参数 | 描述 | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **fileURL** | 备用配置文件的路径。 | --- # File: localizations-and-locale-codes --- --- title: "在 iOS SDK 中使用本地化和区域代码" description: "管理应用本地化和区域代码,让您的 iOS 应用触达全球用户。" --- ## 为什么这很重要 \{#why-this-is-important\} 区域代码会在几种场景中发挥作用——例如,当您尝试为应用当前的本地化版本获取正确的付费墙时。 由于区域代码较为复杂,且在不同平台之间可能存在差异,我们为所有支持的平台统一采用内部标准。然而,正因为这些代码较为复杂,了解您究竟向我们的服务器发送了什么内容以获取正确的本地化版本,以及后续会发生什么,对您来说非常重要——这样您才能始终收到预期的结果。 ## Adapty 的区域代码标准 \{#locale-code-standard-at-adapty\} 对于区域代码,Adapty 使用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 发来的带有区域代码的请求,并开始查找对应的付费墙本地化版本时,将执行以下步骤: 1. 传入的区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. 随后查找与该区域代码完全匹配的本地化版本 3. 若未找到匹配项,则截取第一个连字符前的子字符串(例如 `pt-br` 对应 `pt`),并查找与之匹配的本地化版本 4. 若仍未找到匹配项,则返回默认的 `en` 本地化版本 这样一来,发送了 `'pt_BR'` 的 iOS 设备、发送了 `pt-BR` 的 Android 设备以及发送了 `pt-br` 的其他设备,都将获得相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果您正在考虑本地化问题,很可能您已经在项目中使用了本地化字符串文件。如果是这种情况,我们建议在每个对应本地化的文件中添加一个键值对,用于存储预期的 Adapty 区域代码。然后在调用我们的 SDK 时提取该键对应的值,如下所示: ```swift showLineNumbers // 1. Modify your Localizable.strings files /* Localizable.strings - Spanish */ adapty_paywalls_locale = "es"; /* Localizable.strings - Portuguese (Brazil) */ adapty_paywalls_locale = "pt-br"; // 2. Extract and use the locale code let locale = NSLocalizedString("adapty_paywalls_locale", comment: "") // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 通过这种方式,您可以完全掌控每位用户将获取哪个本地化版本。 ## 实现本地化:其他方式 \{#implementing-localizations-the-other-way\} 您也可以在不为每个本地化版本显式定义区域代码的情况下获得类似(但不完全相同)的结果。这意味着从您平台提供的其他对象中提取区域代码,如下所示: ```swift showLineNumbers let locale = Locale.current.identifier // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` 请注意,我们不推荐这种方式,原因如下: 1. 在 iOS 上,首选语言与当前区域设置并不相同。如果您希望正确选取本地化版本,要么依赖 Apple 的逻辑(如果您使用的是基于本地化字符串文件的推荐方式,该逻辑可开箱即用),要么自行重现该逻辑。 2. 很难预测 Adapty 服务器实际会收到什么内容。例如,在 iOS 上,设备可能会生成类似 `ar_OM@numbers='latn'` 的区域标识并发送给我们的服务器。对于此类请求,您得到的将不是您期望的 `ar-om` 本地化版本,而是 `ar`,这可能出乎意料。 如果您仍决定使用这种方式,请确保已覆盖所有相关的使用场景。 --- # File: ios-troubleshoot-paywall-builder --- --- title: "排查 iOS SDK 中的付费墙编辑工具问题" description: "排查 iOS SDK 中的付费墙编辑工具问题" --- 本指南帮助您解决在 iOS SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:`getPaywallConfiguration` 方法无法检索付费墙配置。 **原因**:该付费墙未在付费墙编辑工具中启用设备显示功能。 **解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。 <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 付费墙展示次数过多 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙的展示次数显示为预期值的两倍。 **原因**:你可能在代码中调用了 `logShowFlow`(iOS SDK v4+)/ `logShowPaywall`,如果你使用的是付费墙编辑工具或 Flow Builder,这样做会导致展示次数重复计算。通过这些工具构建的流程和付费墙会自动追踪数据分析,因此无需手动调用此方法。 **解决方案**:如果你使用的是付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(iOS SDK v4+)/ `logShowPaywall`。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。 **解决方案**:如有需要,请参考[迁移指南](ios-sdk-migration-guides)将 SDK 升级到最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: ios-present-paywall-builder-paywalls-in-observer-mode --- --- title: "在 iOS SDK 的观察者模式下展示付费墙编辑工具付费墙" description: "了解如何在观察者模式下展示付费墙编辑工具付费墙,以获取更深入的洞察。" --- 如果您已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中额外编写渲染逻辑来向用户展示它。此类付费墙已包含展示内容与展示方式的完整定义。 :::warning 本节仅适用于[观察者模式](observer-vs-full-mode)。如果您不使用观察者模式,请参阅 [iOS - 展示付费墙编辑工具付费墙](ios-present-paywalls)。 ::: <SDKv4> <details> <summary>开始展示流程前的准备工作(点击展开)</summary> 1. 在 Adapty 中完成与 [App Store](initial_ios) 的初始集成。 2. 安装并配置 Adapty SDK,确保将 `observerMode` 参数设置为 `true`。请参阅 [iOS SDK 安装指南](sdk-installation-ios#activate-adapty-module-of-adapty-sdk)。 3. 在 Adapty 看板中[创建产品](create-product)。 4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。 5. [创建版位并将流程或付费墙分配给对应版位](create-placement)。 6. 在移动应用代码中[获取流程及其配置](get-pb-paywalls)。 </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. 实现 `AdaptyObserverModeResolver` 对象。该协议与 SDK v3 中相同——观察者模式本身在 flow 和付费墙渲染之间不会发生变化: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // call onStartPurchase / onFinishPurchase to notify AdaptyUI about the purchase progress } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // call onStartRestore / onFinishRestore to notify AdaptyUI about the restore progress } ``` 2. 创建流程配置对象,将您的解析器作为 `observerModeResolver:` 参数传入: ```swift showLineNumbers title="Swift" do { let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------- | | **forFlow** | 必填 | 通过 `Adapty.getFlow(placementId:)` 获取的 `AdaptyFlow` 对象。详见[获取流程和付费墙](get-pb-paywalls)。 | | **observerModeResolver** | 必填 | 您在上方实现的 `AdaptyObserverModeResolver`。 | 3. 使用 `AdaptyUI.flowController(with:delegate:)` 初始化流程控制器: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: flowConfiguration, delegate: <AdaptyFlowControllerDelegate> ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | **flowConfiguration** | required | 一个 `AdaptyUI.FlowConfiguration` 对象,包含流程的视觉详情。参见[获取流程和付费墙](get-pb-paywalls)。 | | **delegate** | required | 一个 `AdaptyFlowControllerDelegate`,用于监听流程事件。参见[处理流程和付费墙事件](ios-handling-events)。 | 返回值: | 对象 | 描述 | | :------------------- | :----------------------------------------------------- | | AdaptyFlowController | 表示所请求流程界面的对象。 | 4. 展示控制器: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::warning 不要忘记[将付费墙与购买交易关联](report-transactions-observer-mode)。否则,Adapty 将无法确定购买行为来源的付费墙。 ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> 在 SwiftUI 中,使用 resolver 获取流程配置,并将其传递给 `.flow` 修饰符: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false @State var flowConfiguration: AdaptyUI.FlowConfiguration? var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) .task { flowConfiguration = try? await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } } ``` `getFlowConfiguration` 上的 `observerModeResolver:` 参数让渲染的流程遵循你的自定义购买逻辑——该修改器本身使用与完整模式相同的回调。 :::warning 别忘了[将付费墙关联到购买交易](report-transactions-observer-mode)。否则,Adapty 将无法确定购买来源的付费墙。 ::: </TabItem> </Tabs> </SDKv4> <SDKv3> <Tabs groupId="current-os" queryString> <TabItem value="sdk3" label="付费墙编辑工具 (SDK 3.x)" default> <details> <summary>开始展示付费墙之前(点击展开)</summary> 1. 在 Adapty 中完成与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。 2. 安装并配置 Adapty SDK,确保将 `observerMode` 参数设置为 `true`。请参阅针对 [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk) 的框架专属说明。 3. 在 Adapty 看板中[创建产品](create-product)。 4. 在 Adapty 看板中[配置付费墙、为其分配产品](create-paywall),并使用付费墙编辑工具进行自定义。 5. 在 Adapty 看板中[创建版位并为其分配付费墙](create-placement)。 6. 在移动端代码中[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. 实现 `AdaptyObserverModeResolver` 对象: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore } ``` `observerMode(didInitiatePurchase:onStartPurchase:onFinishPurchase:)` 事件会通知您用户已发起购买。您可以在此回调中触发自定义购买流程。 `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)` 事件会通知您用户已发起恢复购买操作。您可以在此回调中触发自定义恢复流程。 另外,请记得调用以下回调函数,以便将购买或恢复的进度通知 AdaptyUI。这对于正确的付费墙行为(例如显示加载中状态等)是必要的: | 回调 | 描述 | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | 调用此回调以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 调用此回调以通知 AdaptyUI 购买已完成。 | | onStartRestore() | 调用此回调以通知 AdaptyUI 恢复购买已开始。 | | onFinishRestore() | 调用此回调以通知 AdaptyUI 恢复购买已完成。 | 2. 创建付费墙配置对象: ```swift showLineNumbers title="Swift" do { let paywallConfiguration = try AdaptyUI.getPaywallConfiguration( forPaywall: <paywall object>, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 | | **ObserverModeResolver** | 必填 | 您在上一步中实现的 `AdaptyObserverModeResolver` 对象。 | 3. 使用 `.paywallController(for:products:viewConfiguration:delegate:)` 方法初始化你想展示的付费墙: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | 必填 | 一个包含付费墙视觉详情的 `AdaptyUI.PaywallConfiguration` 对象。请使用 `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)` 方法。详情请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 | | **Delegate** | 必填 | 用于监听付费墙事件的 `AdaptyPaywallControllerDelegate`。详情请参阅[处理付费墙事件](ios-handling-events)。 | 返回值: | 对象 | 描述 | | :---------------------- | :----------------------- | | AdaptyPaywallController | 表示所请求付费墙界面的对象 | 成功创建对象后,可以通过以下方式展示它: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning 别忘了[将付费墙与购买交易关联](report-transactions-observer-mode),否则 Adapty 将无法确定购买来源的付费墙。 ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> 如需在设备屏幕上展示可视化付费墙,请在 SwiftUI 中使用 `.paywall` 修饰符: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <paywall configuration object>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` Request parameters: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | 必填 | 一个 `AdaptyUI.PaywallConfiguration` 对象,包含付费墙的视觉详情。使用 `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)` 方法。详情请参阅[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 | | **Products** | 可选 | 提供一个 `AdaptyPaywallProduct` 对象数组,以优化产品在屏幕上的显示时机。如果传入 `nil`,AdaptyUI 将自动获取所需产品。 | | **TagResolver** | 可选 | 定义一个自定义标签及其解析值的字典。自定义标签在付费墙内容中充当占位符,可动态替换为特定字符串,实现付费墙内的个性化内容。详情请参阅付费墙编辑工具中的自定义标签主题。 | | **ObserverModeResolver** | 可选 | 你在上一步中实现的 `AdaptyObserverModeResolver` 对象 | 闭包参数: | 闭包参数 | 描述 | | :------------------- | :-------------------------------------------------------------------------------- | | **didFinishRestore** | 如果 Adapty.restorePurchases() 成功,此回调将被调用。 | | **didFailRestore** | 如果 Adapty.restorePurchases() 失败,此回调将被调用。 | | **didFailRendering** | 如果界面渲染过程中发生错误,此回调将被调用。 | 请参阅 [iOS - 处理事件](ios-handling-events) 主题,了解其他闭包参数。 :::warning 不要忘记[将付费墙关联到购买交易](report-transactions-observer-mode)。否则,Adapty 将无法确定购买来源的付费墙。 ::: </TabItem> </Tabs> </TabItem> <TabItem value="sdk2" label="Legacy Paywall Builder (SDK up to 2.x)" default> <details> <summary>Before you start presenting paywalls (Click to Expand)</summary> 1. 在 Adapty 看板中完成与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。 1. 安装并配置 Adapty SDK。确保将 `observerMode` 参数设置为 `true`。请参阅各框架的具体说明:[iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) 和 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk)。 2. 在 Adapty 看板中[创建产品](create-product)。 3. 在 Adapty 看板中[配置付费墙并为其分配产品](create-paywall),然后使用付费墙编辑工具对其进行自定义。 4. 在 Adapty 看板中[创建版位并将付费墙分配给它们](create-placement)。 5. 在移动端代码中[获取付费墙编辑工具的付费墙及其配置](get-pb-paywalls)。 </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. 实现 `AdaptyObserverModeDelegate` 对象: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } ``` `paywallController(_:didInitiatePurchase:onStartPurchase:onFinishPurchase:)` 事件会通知你用户已发起购买。你可以在响应该事件时触发自定义购买流程。 此外,请记得调用以下回调,以便将购买进度通知给 AdaptyUI。这对于正确的付费墙行为(例如显示加载动画等)是必要的: | 回调函数 | 描述 | | :--------------- | :------------------------------------------------------------------------------- | | onStartPurchase | 调用此回调以通知 AdaptyUI 购买已开始。 | | onFinishPurchase | 调用此回调以通知 AdaptyUI 购买已完成。 | 2. 使用 `.paywallController(for:products:viewConfiguration:delegate:observerModeDelegate:)` 方法初始化要展示的付费墙视图: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( for: <paywall object>, products: <paywall products array>, viewConfiguration: <LocalizedViewConfiguration>, delegate: <AdaptyPaywallControllerDelegate> observerModeDelegate: <AdaptyObserverModeDelegate> ) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :----------------------- | :------- | :----------------------------------------------------------- | | **Paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 | | **Products** | 可选 | 提供 `AdaptyPaywallProduct` 对象数组,以优化产品在屏幕上的显示时机。如果传入 `nil`,AdaptyUI 将自动获取所需产品。 | | **ViewConfiguration** | 必填 | 包含付费墙视觉详情的 `AdaptyUI.LocalizedViewConfiguration` 对象。请使用 `AdaptyUI.getViewConfiguration(paywall:locale:)` 方法。详情请参阅[获取付费墙编辑工具付费墙及其配置](get-pb-paywalls)主题。 | | **Delegate** | 必填 | 用于监听付费墙事件的 `AdaptyPaywallControllerDelegate`。详情请参阅[处理付费墙事件](ios-handling-events)主题。 | | **ObserverModeDelegate** | 必填 | 您在上一步中实现的 `AdaptyObserverModeDelegate` 对象。 | | **TagResolver** | 可选 | 定义自定义标签及其解析值的字典。自定义标签在付费墙内容中充当占位符,会被动态替换为特定字符串,以在付费墙中实现个性化内容。详情请参阅付费墙编辑工具中的自定义标签主题。 | 返回值: | 对象 | 描述 | | :---------------------- | :--------------------------- | | AdaptyPaywallController | 表示所请求付费墙界面的对象 | 成功创建对象后,可以通过以下方式展示它: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning 不要忘记[将付费墙与购买交易关联](report-transactions-observer-mode)。否则,Adapty 将无法确定购买的来源付费墙。 ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> 要在设备屏幕上显示可视化付费墙,请在 SwiftUI 中使用 `.paywall` 修饰符: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywall: <paywall object>, configuration: <LocalizedViewConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false }, observerModeDidInitiatePurchase: { product, onStartPurchase, onFinishPurchase in // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase }, ) } ``` Request parameters: | 参数 | 是否必填 | 描述 | | :---------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | 必填 | 用于获取所需付费墙控制器的 `AdaptyPaywall` 对象。 | | **Product** | 选填 | 提供 `AdaptyPaywallProduct` 对象数组,以优化产品在屏幕上的显示时机。若传入 `nil`,AdaptyUI 将自动获取所需产品。 | | **Configuration** | 必填 | 包含付费墙视觉详情的 `AdaptyUI.LocalizedViewConfiguration` 对象。请使用 `AdaptyUI.getViewConfiguration(paywall:locale:)` 方法。详情请参阅[获取付费墙编辑工具付费墙及其配置](get-pb-paywalls)。 | | **TagResolver** | 选填 | 定义自定义标签及其解析值的字典。自定义标签在付费墙内容中充当占位符,会被动态替换为特定字符串,从而实现付费墙内容的个性化展示。详情请参阅付费墙编辑工具中的自定义标签相关内容。 | 闭包参数: | 闭包参数 | 说明 | | :---------------------------------- | :-------------------------------------------------------------------------------- | | **didFinishRestore** | 如果 Adapty.restorePurchases() 执行成功,该回调将被触发。 | | **didFailRestore** | 如果 Adapty.restorePurchases() 执行失败,该回调将被触发。 | | **didFailRendering** | 如果界面渲染过程中发生错误,该回调将被触发。 | | **observerModeDidInitiatePurchase** | 当用户发起购买时,该回调将被触发。 | 请参阅 [iOS - 处理事件](ios-handling-events) 主题,了解其他闭包参数。 :::warning 不要忘记[将付费墙与购买交易关联](report-transactions-observer-mode)。否则,Adapty 将无法确定购买来源的付费墙。 ::: </TabItem> </Tabs> </TabItem> </Tabs> </SDKv3> --- # File: ios-quickstart-manual --- --- title: "在 iOS SDK 的自定义付费墙中启用购买功能" description: "将 Adapty SDK 集成到自定义 iOS 付费墙中,以启用应用内购买功能。" --- 本指南介绍如何将 Adapty 集成到自定义付费墙中。你可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。 :::important **本指南面向实现自定义付费墙的开发者。** 如果你想以最简单的方式开通购买功能,请使用 [Adapty Flow Builder](ios-quickstart-paywalls)。使用 Flow Builder,你可以在无代码可视化编辑器中创建流程,Adapty 自动处理所有购买逻辑,并且无需重新发布应用即可测试不同设计。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,这种设计让你无需修改代码即可调整产品、价格和优惠。 - [**版位**](placements) – 在应用中展示付费墙的位置和时机(例如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求。这让 A/B 测试和向不同用户展示不同付费墙变得非常简单。 即使你使用自定义付费墙,也需要了解这些概念。简单来说,它们就是你管理应用内销售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到**版位**中。这样你才能获取产品信息。如需了解在看板中的具体操作步骤,请参考[快速入门指南](quickstart)。 ### 管理用户 \{#manage-users\} 您可以选择在有或没有后端身份验证的情况下使用 Adapty。 Adapty SDK 对匿名用户和已识别用户的处理方式有所不同。请阅读[身份识别快速入门指南](ios-quickstart-identify),以了解其中的细节,并确保您能正确处理用户信息。 ## 步骤 1. 获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,你需要: 1. 通过将[版位](placements) ID 传入 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该 flow 的产品数组。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func loadPaywall() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let products = try await Adapty.getPaywallProducts(flow: flow) // Use products to build your custom paywall UI } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func loadPaywall() { Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // Use products to build your custom paywall UI case let .failure(error): // Handle the error } } case let .failure(error): // Handle the error } } } ``` </TabItem> </Tabs> ## 步骤 2. 处理购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,使用所选产品调用 `makePurchase` 方法。该方法将处理购买流程并返回更新后的用户画像。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) async { do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // 用户取消了购买 break case .pending: // 购买待处理(例如,等待家长批准) break case let .success(profile, transaction): // 购买成功,用户画像已更新 break } } catch { // 处理错误 } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) { Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // 用户取消了购买 break case .pending: // 购买待处理(例如,等待家长批准) break case let .success(profile, transaction): // 购买成功,用户画像已更新 break } case let .failure(error): // 处理错误 } } } ``` </TabItem> </Tabs> ## 第三步:恢复购买 \{#step-3-restore-purchases\} Apple 要求所有含订阅的应用提供让用户恢复购买的途径。虽然用户使用 Apple ID 登录时购买记录会自动恢复,但你仍需在应用中实现一个恢复按钮。 当用户点击恢复按钮时,调用 `restorePurchases` 方法。这将把用户的购买记录与 Adapty 同步,并返回更新后的用户画像。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func restorePurchases() async { do { let profile = try await Adapty.restorePurchases() // Restore successful, profile updated } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func restorePurchases() { Adapty.restorePurchases { result in switch result { case let .success(profile): // 恢复成功,用户画像已更新 case let .failure(error): // 处理错误 } } } ``` </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。[在沙盒模式下测试您的购买](test-purchases-in-sandbox),确保您可以从付费墙完成测试购买。 接下来,[检查用户是否已完成购买](ios-check-subscription-status),以确定是否应展示付费墙或授予付费功能的访问权限。 --- # File: fetch-paywalls-and-products --- --- title: "在 iOS SDK 中获取远程配置付费墙的付费墙和产品" description: "通过 Adapty iOS SDK 获取付费墙和产品,提升用户变现效果。" --- <SDKv4> 在展示远程配置和自定义付费墙之前,你需要先获取相关信息。请注意,本主题针对的是远程配置和自定义付费墙。如需了解如何获取在 **Flow Builder** 或 **Paywall Builder** 中配置的流程或付费墙,请参阅 <InlineTooltip tooltip="关于如何在应用中获取流程和付费墙的指南">[iOS](get-pb-paywalls)、[Android](android-get-pb-paywalls)、[React Native](react-native-get-pb-paywalls)、[Flutter](flutter-get-pb-paywalls) 和 [Unity](unity-get-pb-paywalls)</InlineTooltip>。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在开始获取流程和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程或付费墙,并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位,并将流程或付费墙添加到版位中](create-placement)。 4. 在移动应用中[安装 Adapty SDK](sdk-installation-ios)。 </details> ## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被整合到流程和付费墙中,让你可以在移动应用的特定版位中展示它们。 要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。流程是远程配置的,产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果某个流程今天返回两个产品,明天返回三个,无需修改代码即可全部展示。 ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用这种方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存数据存在时优先返回缓存。这样用户获取到的数据可能不是最新的,但无论网络状况如何,都能获得更快的加载速度。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是完全安全的。</p><p></p><p>请注意,重启应用后缓存仍然保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两个层级存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 来加速流程和付费墙的加载,并在 CDN 不可用时提供独立的备用服务器。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。一旦超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在少数情况下,此方法的实际超时时间可能比 `loadTimeout` 中指定的值略长,因为该操作在底层可能由多个不同的请求组成。</p> | :::note 在 v4 中,`locale` 参数已从 `getFlow` 移至 `getFlowConfiguration`(仅在使用 AdaptyUI 渲染时使用)。对于自定义付费墙,所有可用的语言区域会一并通过 `flow.remoteConfigs` 返回——请选取与用户设备或应用设置相匹配的语言区域。 ::: 不要硬编码产品 ID!由于流程是远程配置的,可用的产品、产品数量以及特殊优惠(如免费试用)可能会随时间变化。请确保你的代码能处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果后续获取到 3 个产品,应用无需修改代码即可显示全部 3 个。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、`remoteConfigs` 数组(每个已配置的语言区域对应一条记录)以及 `hasViewConfiguration` 标志。如需获取该流程对应的产品,请调用 `getPaywallProducts(flow:)`。 | ## 获取产品 \{#fetch-products\} 获取流之后,你可以查询与其对应的产品数组: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(flow: flow) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // the requested products array case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) 对象的这些属性。以下列出了最常用的属性,完整属性列表请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title(标题)** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户选择的商店国家/地区,而非设备语言环境。 | | **Price(价格)** | 要显示本地化价格,请使用 `product.localizedPrice`。本地化基于设备的语言环境信息。您也可以通过 `product.price` 以数字形式访问价格,该值以本地货币表示。要获取对应的货币符号,请使用 `product.currencySymbol`。 | | **Subscription Period(订阅周期)** | 要显示周期(如周、月、年等),请使用 `product.localizedSubscriptionPeriod`。本地化基于设备语言环境。要以编程方式获取订阅周期,请使用 `product.subscriptionPeriod`。您可以通过 `unit` 枚举获取时长(即天、周、月、年或未知)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `.month`,`numberOfUnits` 属性值为 `3`。 | | **Introductory Offer(新用户优惠)** | 要显示表示订阅包含新用户优惠的徽章或其他指示器,请查看 `product.subscriptionOffer` 属性。该对象包含以下实用属性:<br/>• `offerType`:枚举,值为 `introductory`、`promotional` 和 `winBack`。免费试用和初始折扣订阅属于 `introductory` 类型。<br/>• `price`:折扣价格(数字形式)。对于免费试用,此处值为 `0`。<br/>• `localizedPrice`:针对用户语言环境格式化的折扣价格。<br/>• `localizedNumberOfPeriods`:使用设备语言环境本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `3 days`。<br/>• `subscriptionPeriod`:您也可以通过此属性获取优惠周期的各项详情,其用法与前一节描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言环境格式化的折扣订阅周期。 | :::note 在 v4 中,`getPaywallProducts(flow:)` 返回的所有产品已包含优惠资格信息。v3 中单独的 `getPaywallProductsWithoutDeterminingOffer` 调用已被移除。 ::: ## 通过默认目标受众流加速流的获取 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流的获取几乎是即时完成的,无需特别关注。但如果你有大量目标受众和版位,且用户的网络连接较差,获取流的时间可能会比预期更长。在这种情况下,你可能希望先展示一个默认流,以确保良好的用户体验,而不是让用户面对空白页面。 要解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程。但请务必了解,推荐的方式是通过 `getFlow` 方法获取流程,详见上方的[获取流程信息](fetch-paywalls-and-products#fetch-flow-information)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显缺陷: - **潜在的向后兼容性问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的流程,可能会遇到挑战。要么设计出同时兼容当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能无法正常渲染流程的风险。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的流程获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products#fetch-flow-information)介绍的 `getFlow`。 ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // 请求的流程 case let .failure(error): // 处理错误 } } ``` </TabItem> </Tabs> | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐这种方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时优先返回缓存数据。这种情况下,用户看到的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用不会清除缓存,只有在卸载重装应用或手动清理时,缓存才会被清除。</p> | </SDKv4> <SDKv3> 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。有关获取付费墙编辑工具自定义付费墙的指南,请参阅 <InlineTooltip tooltip="关于如何在应用中获取付费墙编辑工具付费墙的指南">[iOS](get-pb-paywalls)、[Android](android-get-pb-paywalls)、[React Native](react-native-get-pb-paywalls)、[Flutter](flutter-get-pb-paywalls) 和 [Unity](unity-get-pb-paywalls)</InlineTooltip>。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在开始获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位中](create-placement)。 4. 在移动应用中[安装 Adapty SDK](sdk-installation-ios)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合体。这些跨平台产品被整合到付费墙中,使您可以在移动应用的特定版位中展示它们。 要展示产品,您需要使用 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 :::important **不要将产品 ID 硬编码。** 唯一需要硬编码的 ID 是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能变化。您的应用必须动态处理这些变化——如果今天付费墙返回两个产品,明天返回三个,则应全部显示,无需修改代码。 ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 控制台创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关区域设置代码及我们推荐的使用方式,请参阅[本地化与区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。这样用户获取的数据可能不是最新的,但加载速度更快,无论网络状况如何。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,仅在卸载重装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 以两层方式存储付费墙:上述定期更新的缓存以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可访问时使用独立的备用服务器。该系统旨在确保您始终获取最新版本的付费墙,同时在网络稀缺的情况下保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的超时可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个请求组成。</p> | 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。 例如,如果最初获取到 2 个产品,你的应用应显示这 2 个产品;如果后来获取到 3 个产品,应用应在无需修改代码的情况下显示全部 3 个。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,您可以查询与之对应的产品数组: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(paywall: paywall) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(paywall: paywall) { result in switch result { case let .success(products): // 请求到的产品数组 case let .failure(error): // 处理错误 } } ``` </TabItem> </Tabs> 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) 对象中的这些属性。下面列出了最常用的属性,完整的属性列表请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **标题** | 使用 `product.localizedTitle` 显示产品标题。请注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 | | **价格** | 使用 `product.localizedPrice` 显示本地化价格,本地化基于设备的语言区域信息。你也可以通过 `product.price` 以数字形式获取价格,值以本地货币表示。若要获取对应的货币符号,请使用 `product.currencySymbol`。 | | **订阅周期** | 使用 `product.localizedSubscriptionPeriod` 显示周期(如周、月、年等),本地化基于设备的语言区域设置。若要以编程方式获取订阅周期,请使用 `product.subscriptionPeriod`。通过该属性可访问 `unit` 枚举,获取时长单位(即 day、week、month、year 或 unknown)。`numberOfUnits` 表示周期单位的数量。例如,对于季度订阅,`unit` 属性为 `.month`,`numberOfUnits` 为 `3`。 | | **新用户优惠** | 若要显示徽标或其他标识,表明某个订阅包含新用户优惠,请查看 `product.subscriptionOffer` 属性。该对象包含以下实用属性:<br/>• `offerType`:枚举类型,取值为 `introductory`、`promotional` 和 `winBack`。免费试用和初始折扣订阅属于 `introductory` 类型。<br/>• `price`:折扣价格的数字形式。免费试用时该值为 `0`。<br/>• `localizedPrice`:针对用户语言区域格式化后的折扣价格。<br/>• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `3 days`。<br/>• `subscriptionPeriod`:也可以通过该属性获取优惠周期的各项详细信息,其使用方式与前一节中描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言区域格式化后的折扣订阅周期。 | ## 在 iOS 上检查新用户优惠资格 \{#check-intro-offer-eligibility-on-ios\} 默认情况下,`getPaywallProducts` 方法会检查新用户优惠、促销活动和赢回优惠的资格。如果您需要在 SDK 确定优惠资格之前展示产品,请改用 `getPaywallProductsWithoutDeterminingOffer` 方法。 :::note 展示初始产品后,请务必调用常规 `getPaywallProducts` 方法,以获取包含准确优惠资格信息的产品。 ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) // the requested products array without subscriptionOffer } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) { result in switch result { case let .success(products): // the requested products array without subscriptionOffer case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> ## 使用默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙几乎可以立即获取,因此您无需担心加速此过程。但是,在您拥有大量目标受众和付费墙、且用户网络连接较弱的情况下,获取付费墙可能需要比预期更长的时间。在这种情况下,您可能希望显示默认付费墙,以确保流畅的用户体验,而不是完全不显示付费墙。 为解决此问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的付费墙。但请务必了解,推荐的方式是通过 `getPaywall` 方法获取付费墙,详见上方[获取付费墙信息](fetch-paywalls-and-products#fetch-paywall-information)部分。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在一些重大缺陷: - **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)显示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户将看到为**所有用户**目标受众设计的相同付费墙,这意味着您将失去个性化定向能力(包括基于国家/地区、营销归因或您自定义属性的定向)。 如果您愿意接受这些缺陷以获得更快的付费墙获取速度,请按以下方式使用 `getPaywallForDefaultAudience` 方法。否则,请坚持使用[上述](fetch-paywalls-and-products#fetch-paywall-information) `getPaywall` 方法。 ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> :::note `getPaywallForDefaultAudience` 方法从 iOS SDK 2.11.2 版本开始提供。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络状况不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在卸载重装应用或手动清理时才会被清除。</p> | </SDKv3> --- # File: present-remote-config-paywalls --- --- title: "在 iOS SDK 中渲染通过远程配置设计的付费墙" description: "了解如何在 Adapty 中展示远程配置付费墙,以个性化用户体验。" --- <SDKv4> 如果你通过远程配置自定义了付费墙,则需要在移动应用代码中实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,付费墙的内容和外观都取决于你的设计。Adapty 提供了获取远程配置的方法,让你能够自主展示自定义付费墙。 不要忘记[在 iOS 中检查用户是否有资格享受新用户优惠](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios),并相应调整付费墙视图以处理用户有资格时的情况。 ## 获取流程远程配置并展示 \{#get-flow-remote-config-and-present-it\} 在 v4 版本中,每个流程在 `remoteConfigs` 数组中为每个已配置的语言区域携带一条 `AdaptyRemoteConfig` 条目。选取与用户偏好匹配的语言区域,然后读取所需的值。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in let flow = try? result.get() let config = flow?.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow?.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> 到这一步,你已经获取了所有必要的值,接下来就可以将它们渲染并组合成一个美观的页面。请确保设计能够适配各种手机屏幕尺寸和横竖屏方向,为不同设备的用户提供流畅、友好的体验。 :::warning 请务必按照以下说明[记录付费墙展示事件](present-remote-config-paywalls#track-paywall-view-events),以便 Adapty 分析系统采集漏斗和 A/B 测试所需的数据。 ::: 显示付费墙后,继续设置购买流程。当用户发起购买时,只需用流程中的产品调用 `.makePurchase()` 方法。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](making-purchases)。 我们建议[创建一个备用付费墙](fallback-paywalls)。当用户没有网络连接或缓存不可用时,将向用户展示此备用付费墙,确保在这些情况下仍能提供流畅的体验。 ## 追踪付费墙展示事件 \{#track-paywall-view-events\} Adapty 可以帮助你衡量付费墙的表现。购买数据会自动收集,但付费墙的展示事件需要你手动记录,因为只有你知道用户何时看到了付费墙。 要记录付费墙展示事件,只需调用 `.logShowFlow(flow)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是由 [Flow Builder](adapty-flow-builder) 或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙,则无需调用 `.logShowFlow(flow)`。Adapty 在这些情况下会自动追踪展示。 ::: ```swift showLineNumbers try await Adapty.logShowFlow(flow) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:-----------------------------------------------------------------------------------------| | **flow** | 必填 | 通过 `Adapty.getFlow(placementId:)` 获取的 `AdaptyFlow` 对象。 | </SDKv4> <SDKv3> 如果你使用远程配置自定义了付费墙,则需要在移动应用代码中实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全取决于你的需求,付费墙的内容和样式都由你掌控。我们提供了获取远程配置的方法,让你可以自主展示通过远程配置设置的自定义付费墙。 请不要忘记[在 iOS 中检查用户是否符合新用户优惠的资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios),并相应调整付费墙视图以处理用户符合资格的情况。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") let headerText = paywall.remoteConfig?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") { result in let paywall = try? result.get() let headerText = paywall?.remoteConfig?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> 此时,一旦您获取了所有必要的值,就可以将它们渲染并组合成一个视觉上美观的页面。请确保设计能够适配各种移动手机屏幕尺寸和方向,在不同设备上提供无缝且友好的用户体验。 :::warning 请务必按照下文所述[记录付费墙查看事件](present-remote-config-paywalls#track-paywall-view-events),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 展示完付费墙后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请阅读[发起购买](making-purchases)。 我们建议[创建一个备用付费墙](fallback-paywalls)。当用户没有网络连接或无可用缓存时,该备用付费墙将自动展示,确保用户在这些情况下依然能获得流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 可以帮助你衡量付费墙的性能表现。购买数据会自动收集,但付费墙浏览记录需要你手动上报,因为只有你知道用户何时看到了付费墙。 要记录付费墙浏览事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果你展示的是通过[付费墙编辑工具](adapty-paywall-builder)创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```swift showLineNumbers Adapty.logShowPaywall(paywall) ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:------------------------------------------------------------------------------------------------| | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象。 | </SDKv3> --- # File: making-purchases --- --- title: "在 iOS SDK 中进行应用内购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙,是向用户提供高级内容或服务访问权限的关键步骤。不过,如果你使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,仅仅展示付费墙就足以支持购买流程了。 如果您不使用付费墙编辑工具,则需要调用一个名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成交易的入口。 如果付费墙中针对用户所购产品设置了有效的促销活动,Adapty 会在购买时自动应用该优惠。 :::warning 请注意,新用户优惠仅在您使用付费墙编辑工具搭建的付费墙时才会自动应用。 在其他情况下,您需要[在 iOS 上验证用户是否符合新用户优惠资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布审核时被拒。此外,还可能对本应享受新用户优惠的用户收取全额费用。 ::: 请确保已完成[初始配置](quickstart)且没有跳过任何步骤。没有初始配置,我们将无法验证购买。 ## 发起购买 \{#make-purchase\} :::note **正在使用[付费墙编辑工具](adapty-paywall-builder)?** 购买流程会自动处理,可跳过此步骤。 **需要分步骤的操作指引?** 请查阅[快速入门指南](ios-implement-paywalls-manually),其中包含完整的端到端实现说明。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } } catch { // Handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } case let .failure(error): // Handle the error } } ``` </TabItem> </Tabs> 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | 必填 | 从付费墙获取的 [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) 对象。 | 响应参数: | 参数 | 描述 | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>请求成功时,响应中会包含此对象。[AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile) 对象提供了用户在应用内的访问等级、订阅及一次性购买的完整信息。</p><p>请检查访问等级状态,以确认用户是否具备访问应用所需的权限。</p> | :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。此方法目前已被 Apple 废弃。 ::: ## 来自 App Store 的应用内购买 \{#in-app-purchases-from-the-app-store\} 当用户在 App Store 发起购买,且该交易被传递到您的应用时,您有两种处理方式: - **立即处理交易:** 在 `shouldAddStorePayment` 中返回 `true`,Apple 购买系统界面将立即弹出。 - **保存产品对象以便稍后处理:** 在 `shouldAddStorePayment` 中返回 `false`,之后再使用保存的产品调用 `makePurchase`。如果您需要在触发购买前向用户展示自定义内容,这种方式会很有用。 完整代码片段如下: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. The Apple purchase system screen will show automatically. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` when the timing is appropriate func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 要在您的应用中显示优惠码兑换界面: ```swift showLineNumbers Adapty.presentCodeRedemptionSheet() ``` :::danger 根据我们的观察,部分应用中的优惠码兑换界面可能无法稳定运行。我们建议直接将用户重定向至 App Store。 为此,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: restore-purchase --- --- title: "在 iOS SDK 中恢复移动应用内购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 恢复购买是一项功能,允许用户重新获取之前已购买的内容(例如订阅或应用内购买),而无需再次付款。此功能对于那些可能已卸载并重新安装应用程序,或切换到新设备并希望无需再次付款即可访问之前购买内容的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您使用的是这种方式,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.restorePurchases() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.restorePurchases { [weak self] result in switch result { case let .success(profile): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> 响应参数: | 参数 | 描述 | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>[`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile) 对象。该模型包含访问等级、订阅及非订阅购买的相关信息。</p><p>请检查**访问等级状态**,以确定用户是否有权访问该应用。</p> | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: ios-transaction-management --- --- title: "iOS SDK 中的高级事务管理" description: "使用 Adapty SDK 在 iOS 应用中手动完成事务。" --- :::note 高级事务管理在 Adapty iOS SDK 3.12 版本起开始支持。 ::: Adapty 中的高级事务管理让您能够更精细地控制事务的处理、验证和完成方式。 高级事务管理引入了三个可选功能,它们协同工作: | 功能 | 用途 | |-------------------------------------------------------------|------| | [`appAccountToken`](#assign-appaccounttoken) | 将 Apple 事务与您的内部用户 ID 关联 | | [`jwsTransaction`](#access-the-jws-representation) | 提供 Apple 的已签名事务载荷以供验证 | | [手动完成](#control-transaction-finishing-behavior) | 允许您仅在后端确认成功后才完成事务 | 这些工具结合使用,可让您在 Adapty 继续与其后端同步事务的同时,构建稳健的自定义验证流程。 :::important 大多数应用不需要此功能。 默认情况下,Adapty 会自动验证并完成 StoreKit 事务。 仅当您运行自己的后端验证或希望完全控制购买生命周期时,才需参考本指南。 ::: ## 分配 `appAccountToken` \{#assign-appaccounttoken\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,可让您将 App Store 事务与您的内部用户身份关联。 StoreKit 会将此令牌与每笔事务关联,以便您的后端能够将 App Store 数据与您的用户匹配。 请为每位用户生成稳定的 UUID,并在同一账户的不同设备上复用它。 这样可确保购买记录和 App Store 通知始终正确关联。 您可以通过两种方式设置令牌——在 SDK 激活时或在识别用户时。 :::important 您必须始终将 `appAccountToken` 与 `customerUserId` 一起传递。 如果仅传递令牌,则该令牌不会包含在事务中。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } // Or when identifying a user: Adapty.identify("YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) { error in if let error { // handle the error } } ``` </TabItem> </Tabs> ## 访问 JWS 表示形式 \{#access-the-jws-representation\} 当您进行购买时,结果中会包含 Apple 以 [JWS 紧凑序列化格式](https://developer.apple.com/documentation/storekit/verificationresult/jwsrepresentation-21vgo)返回的事务。 您可以将此值转发给您的后端进行独立验证或日志记录。 ```swift let result = try await Adapty.makePurchase(product: paywallProduct) let jwsRepresentation = result.jwsTransaction ``` ## 控制事务完成行为 \{#control-transaction-finishing-behavior\} 默认情况下,Adapty 在验证后会自动完成 StoreKit 事务。 如果您需要延迟完成直到后端确认成功,请将完成行为设置为手动模式。 在此模式下: - Adapty 仍会验证购买并将其同步到其后端。 - 事务将保持未完成状态,直到您显式调用 `finish()`。 ```swift var configBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_API_KEY") .with(transactionFinishBehavior: .manual) try await Adapty.activate(with: configBuilder.build()) ``` 使用手动事务完成时,您需要实现 `onUnfinishedTransaction` 代理方法来处理未完成的事务: ```swift showLineNumbers title="Swift" extension YourApp: AdaptyDelegate { func onUnfinishedTransaction(_ transaction: AdaptyUnfinishedTransaction) async { // Perform your custom validation logic here // When ready, finish the transaction await transaction.finish() } } ``` 要获取所有当前未完成的事务,请使用 `getUnfinishedTransactions()` 方法: ```swift let unfinishedTransactions = try await Adapty.getUnfinishedTransactions() ``` --- # File: implement-observer-mode --- --- title: "在 iOS SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以便在 iOS SDK 中追踪用户订阅事件。" --- 如果您已拥有自己的购买基础设施,暂时不打算完全切换到 Adapty,可以了解[观察者模式](observer-vs-full-mode)。在基础形态下,观察者模式提供高级分析功能,以及与归因和分析系统的无缝集成。 如果这已满足您的需求,您只需: 1. 在配置 Adapty SDK 时,将 `observerMode` 参数设置为 `true` 以启用该模式。 2. 将您现有购买基础设施中的交易[上报给 Adapty](report-transactions-observer-mode)。 如果您还需要付费墙和 A/B 测试功能,则需要按照以下说明进行额外配置。 ## 观察者模式设置 \{#observer-mode-setup\} 如果您自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请启用观察者模式。 :::important 在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保您自行处理交易关闭。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: configurationBuilder) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> 参数: | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | observerMode | 布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在观察者模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还希望使用 Adapty 的付费墙和 A/B 测试功能,也可以实现——但需要在观察者模式下进行额外配置。除上述步骤外,您还需要完成以下操作: 1. 按照常规方式展示[远程配置付费墙](present-remote-config-paywalls)。对于付费墙编辑工具付费墙,请参考 [iOS](ios-present-paywall-builder-paywalls-in-observer-mode) 的专项配置指南。 3. 将付费墙与购买交易进行[关联](report-transactions-observer-mode)。 --- # File: report-transactions-observer-mode --- --- title: "在 iOS SDK 中以观察者模式上报交易" description: "在 Adapty 观察者模式下上报购买交易,用于用户洞察和收入追踪(iOS SDK)。" --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+(当前版本)" default> 在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的交易。您需要手动从应用商店上报交易。请务必在发布应用**之前**完成此配置,以避免分析数据出现错误。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别。 :::warning **请勿跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,该交易不会出现在分析数据中,也不会被发送到集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时附带 `variationId`。这会将购买行为与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` 参数说明: | 参数 | 是否必填 | 描述 | | --------------- | -------- | ------------------------------------------------------------ | | **transaction** | 必填 | <ul><li>StoreKit 1:SKPaymentTransaction。</li><li>StoreKit 2:Transaction。</li></ul> | | **variationId** | 可选 | 付费墙实验变体的唯一 ID。从 [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象的 `variationId` 属性中获取。 | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x(旧版)" default> 在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的交易。您需要手动从应用商店上报交易或恢复交易。请务必在发布应用**之前**完成此配置,以避免分析数据出现错误。 使用 `reportTransaction` 将交易数据发送给 Adapty。 :::warning **请勿跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,该交易不会出现在分析数据中,也不会被发送到集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时附带 `withVariationId`。这会将购买行为与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` 参数说明: | 参数 | 是否必填 | 描述 | | --------------- | -------- | ------------------------------------------------------------ | | **transaction** | 必填 | <ul><li>StoreKit 1:SKPaymentTransaction。</li><li>StoreKit 2:Transaction。</li></ul> | | **variationId** | 可选 | 付费墙实验变体的唯一 ID。从 [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象的 `variationId` 属性中获取。 | </TabItem> <TabItem value="old2" label="Adapty SDK 3.2.x 及以下版本(旧版)" default> **上报交易** - 3.1.x 及以下版本会自动监听 App Store 中的交易,无需手动上报。 - 3.2 版本不支持观察者模式。 **将付费墙与交易关联** 由于购买由您自行处理,Adapty SDK 无法判断购买来源。因此,如果您打算在观察者模式下使用付费墙和/或 A/B 测试,您需要在移动应用代码中将来自应用商店的交易与对应的付费墙关联起来。在发布应用前务必正确完成此设置,否则会导致分析数据出现错误。 ```swift let variationId = paywall.variationId // There are two overloads: for StoreKit 1 and StoreKit 2 Adapty.setVariationId(variationId, forPurchasedTransaction: transactionId) { error in if error == nil { // successful binding } } ``` 请求参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | variationId | 必填 | 实验变体的字符串标识符。可通过 [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall) 对象的 `variationId` 属性获取。 | | transactionId | 必填 | <p>StoreKit 1:[SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</p><p>StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</p> | </TabItem> </Tabs> --- # File: ios-troubleshoot-purchases --- --- title: "排查 iOS SDK 中的购买问题" description: "排查 iOS SDK 中的购买问题" --- 本指南帮助您解决在 iOS SDK 中手动实现购买时遇到的常见问题。 ## 观察者模式下的 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时出现 `AdaptyError.cantMakePayments` 错误。 **原因**:在观察者模式下,您应自行处理购买逻辑,而不应使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 处理购买,请关闭观察者模式。您需要选择其中之一:使用 `makePurchase`,或在观察者模式下自行处理购买。详情请参阅[实现观察者模式](implement-observer-mode)。 ## 未找到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:遇到 `makePurchasesCompletionHandlers` 无法找到的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建一个新的沙盒用户并重试。此操作通常可以解决与沙盒相关的购买完成回调问题。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述内容未涵盖的其他购买相关问题。 **解决方案**:如有需要,请参考[迁移指南](ios-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: ios-web-paywall --- --- title: "在 iOS SDK 中实现 Web 付费墙" description: "设置 Web 付费墙,无需支付 App Store 费用和审核即可收款。" --- :::important 在开始之前,请确保您已[在控制台中配置了 Web 付费墙](web-paywall),并安装了 Adapty SDK 3.6.1 或更高版本。 ::: ## 打开 Web 付费墙 \{#open-web-paywalls\} 如果您使用的是自行开发的付费墙,需要通过 SDK 方法来处理 Web 付费墙。`.openWebPaywall` 方法会: 1. 生成一个唯一 URL,让 Adapty 能够将展示给特定用户的付费墙与其跳转到的网页关联起来。 2. 跟踪用户返回应用的时机,并以短时间间隔调用 `.getProfile`,以判断用户画像的访问权限是否已更新。 这样,一旦支付成功并且访问权限完成更新,订阅几乎立即就会在应用中激活。 ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product) } catch { print("Failed to open web paywall: \(error)") } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall(product)` 根据付费墙生成 URL,并将产品数据附加到 URL 中。 2. `openWebPaywall(paywall)` 根据付费墙生成 URL,但不附加产品数据。当 Adapty 付费墙中的产品与 Web 付费墙中的产品不一致时,请使用此版本。 ::: ## 处理错误 \{#handle-errors\} | 错误 | 描述 | 建议操作 | |-----------------------------------------|--------------------------------------------------------|---------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | 付费墙未配置网页购买 URL | 检查付费墙是否已在 Adapty 看板中正确配置 | | AdaptyError.productWithoutPurchaseUrl | 产品没有网页购买 URL | 在 Adapty 看板中验证产品配置 | | AdaptyError.failedOpeningWebPaywallUrl | 无法在浏览器中打开该 URL | 检查设备设置,或提供其他购买方式 | | AdaptyError.failedDecodingWebPaywallUrl | 无法正确编码 URL 中的参数 | 验证 URL 参数是否有效且格式正确 | ## 实现示例 \{#implementation-example\} ```swift showLineNumbers title="Swift" class SubscriptionViewController: UIViewController { var paywall: AdaptyPaywall? @IBAction func purchaseButtonTapped(_ sender: UIButton) { guard let paywall = paywall, let product = paywall.products.first else { return } Task { await offerWebPurchase(for: product) } } func offerWebPurchase(for paywallProduct: AdaptyPaywallProduct) async { do { // Attempt to open web paywall try await Adapty.openWebPaywall(for: paywallProduct) } catch let error as AdaptyError { switch error { case .paywallWithoutPurchaseUrl, .productWithoutPurchaseUrl: showAlert(message: "Web purchase is not available for this product.") case .failedOpeningWebPaywallUrl: showAlert(message: "Could not open web browser. Please try again.") default: showAlert(message: "An error occurred: \(error.localizedDescription)") } } catch { showAlert(message: "An unexpected error occurred.") } } // Helper methods private func showAlert(message: String) { /* ... */ } } ``` :::note 用户返回应用后,请刷新 UI 以反映用户画像的更新。`AdaptyDelegate` 将接收并处理用户画像更新事件。 ::: ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} :::important 从 Adapty SDK v3.15 起,支持在应用内浏览器中打开网页付费墙。 ::: 默认情况下,网页付费墙会在外部浏览器中打开。 为了提供流畅的用户体验,你可以在应用内浏览器中打开网页付费墙。这样,网页购买页面会直接在你的应用内显示,用户无需切换应用即可完成交易。 要启用此功能,请将 `in` 参数设置为 `.inAppBrowser`: ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product, in: .inAppBrowser) // default – .externalBrowser } catch { print("Failed to open web paywall: \(error)") } ``` --- # File: identifying-users --- --- title: "在 iOS SDK 中识别用户" description: "在 Adapty 中识别用户,以改善个性化订阅体验。" --- Adapty 会为每位用户创建一个内部用户画像 ID。不过,如果你有自己的认证系统,建议设置你自己的 Customer User ID。你可以在[用户画像](profiles-crm)部分通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 会同步发送到所有集成渠道。 ## 在配置时设置客户用户 ID \{#set-customer-user-id-on-configuration\} 如果在配置阶段已有用户 ID,只需将其作为 `customerUserId` 参数传入 `.activate()` 方法: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 配置完成后设置客户用户 ID \{#set-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以随时通过 `.identify()` 方法来设置。最常见的使用场景是在注册或登录之后,即用户从匿名状态切换为已认证状态时。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> </Tabs> 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重新提交重要用户数据 在某些情况下,例如用户再次登录账户时,Adapty 服务器上已经存有该用户的信息。此时,Adapty SDK 会自动切换到新用户。如果你之前向匿名用户传递了任何数据(例如自定义属性或来自第三方网络的归因信息),则需要为已识别的用户重新提交这些数据。 另外需要注意,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ## 登出与登录 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法使用户登出: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> 之后,您可以使用 `.identify()` 方法让用户登录。 ## 设置 appAccountToken \{#set-appaccounttoken\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 UUID,帮助 Apple 的 StoreKit 2 跨应用安装和设备识别用户。 从 Adapty iOS SDK 3.10.2 开始,您可以在配置 SDK 或识别用户时传入 `appAccountToken`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } // Or when identifying a user: Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) { error in if let error { // handle the error } } ``` </TabItem> </Tabs> 之后,您可以使用 `.identify()` 方法登录用户。 ## 跨设备识别用户 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: setting-user-attributes --- --- title: "在 iOS SDK 中设置用户属性" description: "了解如何在 Adapty 中设置用户属性,以实现更精准的目标受众细分。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。随后,您可以使用这些属性创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) Adapty.updateProfile(params: builder.build()) { error in if error != nil { // handle the error } } ``` </TabItem> </Tabs> 请注意,之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 允许的键 `<Key>` 及其对应的值 `<Value>` 如下所示: | 键 | 值 | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | 枚举类型,允许的值为:`female`、`male`、`other` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性,这些属性通常与您的应用使用情况相关。例如,对于健身应用,自定义属性可以是每周锻炼次数;对于语言学习应用,则可以是用户的知识水平,等等。您可以在市场细分中使用自定义属性来创建有针对性的付费墙和优惠,也可以在分析中使用它们来找出哪些产品指标对收益影响最大。 ```swift showLineNumbers do { builder = try builder.with(customAttribute: "value1", forKey: "key1") } catch { // handle key/value validation error } ``` 要移除已有的键,请使用 `.withRemoved(customAttributeForKey:)` 方法: ```swift showLineNumbers do { builder = try builder.withRemoved(customAttributeForKey: "key2") } catch { // handle error } ``` 有时您需要了解此前已设置了哪些自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能并非最新状态,因为用户属性可以随时从不同设备发送,服务器上的属性可能在上次同步后已发生变更。 ::: ### 限制 \{#limits\} - 每个用户最多可设置 30 个自定义属性 - 键名最长为 30 个字符,可包含字母数字字符及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,长度不超过 50 个字符 --- # File: subscription-status --- --- title: "在 iOS SDK 中查看订阅状态" description: "在 Adapty 中追踪和管理用户订阅状态,提升用户留存率。" --- 借助 Adapty,追踪订阅状态变得轻而易举。您无需在代码中手动插入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可轻松确认其订阅状态。 在开始检查订阅状态之前,请先配置 [App Store 服务器通知](enable-app-store-server-notifications)。 ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile) 对象的属性。建议在应用启动时(例如[识别用户](identifying-users#set-customer-user-id-on-configuration)时)获取用户画像,并在发生变更时及时更新。这样,您便可以直接使用用户画像对象,而无需反复请求。 如需在用户画像更新时收到通知,请按照下方[监听订阅状态更新](subscription-status#listening-for-subscription-status-updates)章节所述,监听用户画像变更事件。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 使用 `.getProfile()` 方法从服务器获取访问等级: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> 响应参数: | 参数 | 描述 | | --------- |------| | Profile | <p>[AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile) 对象。通常情况下,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。</p><p></p><p>`.getProfile` 方法始终尝试查询 API,因此可提供最新的结果。如果由于某种原因(例如无网络连接)Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。值得注意的是,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的最新状态。</p> | `.getProfile()` 方法返回用户画像,您可以从中获取访问等级状态。每个应用可以设置多个访问等级。例如,如果您有一款新闻应用,并对不同主题单独销售订阅,您可以创建"sports"和"science"两个访问等级。但在大多数情况下,您只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。 以下是检查默认"premium"访问等级的示例: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() let isPremium = profile.accessLevels["premium"]?.isActive ?? false // grant access to premium features } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get(), profile.accessLevels["premium"]?.isActive ?? false { // grant access to premium features } } ``` </TabItem> </Tabs> ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化时,Adapty 都会触发一个事件。 要接收来自 Adapty 的消息,您需要进行一些额外配置: ```swift showLineNumbers Adapty.delegate = self // To receive subscription updates, extend `AdaptyDelegate` with this method: nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { // handle any changes to subscription state } ``` Adapty 也会在应用启动时触发一个事件,此时将传递缓存的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 实现的缓存存储了用户画像的订阅状态。这意味着即使服务器不可用,也可以访问缓存数据以获取用户画像的订阅状态信息。 但需要注意的是,无法直接从缓存请求数据。SDK 每分钟会定期向服务器查询,以检查用户画像是否有任何更新或变更。如有任何修改(例如新的交易或其他更新),这些变更将同步到缓存数据,以保持其与服务器的一致性。 --- # File: ios-deal-with-att --- --- title: "在 iOS SDK 中处理 ATT" description: "在 iOS 上开始使用 Adapty,简化订阅的设置与管理。" --- 如果您的应用程序使用了 AppTrackingTransparency 框架,并向用户呈现了应用追踪授权请求,则您应将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if #available(iOS 14, macOS 11.0, *) { let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) Adapty.updateProfile(params: builder.build()) { [weak self] error in if error != nil { // handle the error } } } ``` </TabItem> </Tabs> :::warning 我们强烈建议您在该值发生变化时尽早发送,只有这样,数据才能及时传送到您已配置的集成渠道。 ::: --- # File: kids-mode --- --- title: "iOS SDK 中的儿童模式" description: "轻松启用儿童模式,符合 Apple 政策。iOS SDK 不收集 IDFA 或广告数据。" --- <SDKv4> 如果你的 iOS 应用面向儿童,必须遵守 [Apple](https://developer.apple.com/kids/) 的相关政策。使用 Adapty SDK 时,只需几个简单步骤即可完成配置,满足这些政策要求并顺利通过应用商店审核。 ## 需要配置哪些内容?\{#whats-required\} 您需要配置 Adapty SDK,禁止收集以下信息: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用客户用户 ID。格式为 `<FirstName.LastName>` 的用户 ID 或使用电子邮件的情况,都会被视为收集个人数据。在儿童模式下,最佳实践是使用随机或匿名标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行设置 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要关闭 IP 地址收集功能。请前往 [App settings](https://app.adapty.io/settings/general),在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 移动端代码变更 \{#updates-in-your-mobile-app-code\} 从 SDK 4.0 起,Kids Mode 以 Swift package trait 的形式实现,名为 `KidsMode`。启用该 trait 后,整个 SDK 中的 IDFA 和 AdSupport 将在编译期被移除——你仍保留常规的 **Adapty** 和 **AdaptyUI** 模块,以及原有的 `import Adapty` / `import AdaptyUI` 语句。 :::note `KidsMode` trait 从 SDK 4.0 版本起可用。从 SDK 4.0 起,SDK 仅支持通过 Swift Package Manager 安装——不再支持 CocoaPods。 ::: <Tabs> <TabItem value="xcode" label="Xcode" default> 1. 按常规方式[安装 Adapty SDK](sdk-installation-ios),选择标准的 **Adapty** 和 **AdaptyUI** 模块。 2. 在 Xcode 26.4 或更高版本中,打开项目设置,进入 **Package Dependencies** 视图,为 AdaptySDK-iOS 依赖项启用 **KidsMode** trait。 :::note 早于 26.4 的 Xcode 版本无法通过 UI 为 Xcode 项目启用 traits。在这种情况下,请添加一个依赖 Adapty 并启用了 `KidsMode` trait 的本地 Swift 包(参见 **Package.swift** 标签页),然后让你的 app target 依赖该包。 ::: </TabItem> <TabItem value="spm" label="Package.swift"> 如果你在 `Package.swift` 中将 Adapty 添加为依赖项,请在包声明中启用该 trait。Traits 需要 `swift-tools-version` 6.1 或更高版本。 ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> 如果您的 iOS 应用面向儿童,则必须遵守 [Apple](https://developer.apple.com/kids/) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单的步骤即可完成配置,使其符合这些政策并通过应用商店审核。 ## 需要配置什么?\{#whats-required\} 你需要配置 Adapty SDK,以禁用以下数据的收集: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用 customer user ID。格式为 `<FirstName.LastName>` 的用户 ID 以及电子邮件地址,都会被视为收集个人数据。对于儿童模式,最佳实践是使用随机或匿名标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### Adapty 看板中的更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集功能。请前往 [App settings](https://app.adapty.io/settings/general),在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 更新移动应用代码 \{#updates-in-your-mobile-app-code\} 为遵守相关政策,请禁用收集用户 IDFA 和 IP 地址的功能。 <Tabs> <TabItem value="spm" label="Swift Package Manager" default> 如果你使用 Swift Package Manager,可以在 Xcode 安装 SDK 时选择 **Adapty_KidsMode** 模块来启用儿童模式。 在 Xcode 中,依次点击 **File** -> **Add Package Dependency...**。请注意,添加软件包依赖项的步骤可能因 Xcode 版本不同而有所差异,如有需要请参阅 Xcode 文档。 1. 输入仓库 URL: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. 选择版本(推荐使用最新稳定版),然后点击 **Add Package**。 3. 在 **Choose Package Products** 窗口中,选择所需模块: - **Adapty_KidsMode**(核心模块) - **AdaptyUI_KidsMode**(可选 - 仅在计划使用付费墙编辑工具时需要) 其他包无需选择。 4. 点击 **Add Package** 完成安装。 5. 在代码中,将 `import Adapty` 替换为 `import Adapty_KidsMode`,将 `import AdaptyUI` 替换为 `import AdaptyUI_KidsMode`: ```swift ``` </TabItem> <TabItem value="cocoapods" label="CocoaPods"> 1. 更新你的 Podfile: - 如果你**没有** `post_install` 代码段,请添加下方完整代码块。 - 如果你**已有** `post_install` 代码段,请将高亮部分的代码合并进去。 ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. 运行以下命令以应用更改: ```sh showLineNumbers title="Shell" pod install ``` </TabItem> </Tabs> </SDKv3> --- # File: get-onboardings --- --- title: "获取用户引导及其配置" description: "了解如何在 Adapty 中获取用户引导。" --- :::tip **从 SDK v4 开始**,你可以将[流程](get-pb-paywalls)作为用户引导的更强大替代方案。与在 WebView 中运行的用户引导不同,流程在设备上原生渲染——带来更流畅的动画、一致的 iOS 外观体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](get-pb-paywalls)和[展示流程与付费墙](ios-present-paywalls)以开始使用。 ::: 在 Adapty 控制台中[使用编辑工具设计好用户引导的视觉部分](design-onboarding)后,您可以在移动应用中展示它。此过程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。 开始之前,请确保: 1. 您已安装 [Adapty iOS、Android、React Native 或 Flutter SDK](installation-of-adapty-sdks) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 ## 获取用户引导 \{#fetch-onboarding\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器的形式存储,其中包含应用需要获取并展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(如测验答案或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。 为获得最佳性能,请尽早获取用户引导配置,以便在向用户展示之前有足够的时间下载图片。 要获取用户引导,请使用 `getOnboarding` 方法: ```swift showLineNumbers do { let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // the requested onboarding } catch { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 控制台创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及其推荐用法的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然存在,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存以及备用用户引导。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的用户引导,同时在网络连接有限的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的超时可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个不同的请求组成。</p> | 响应参数: | 参数 | 描述 | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://swift.adapty.io/documentation/adapty/adaptyonboarding) 对象,包含:用户引导标识符和配置、远程配置以及其他几个属性。 | ## 通过默认目标受众用户引导加快获取速度 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常,用户引导几乎可以立即获取,因此您无需担心加速此过程。但是,如果您有大量目标受众和用户引导,且用户的网络连接较弱,获取用户引导可能比预期耗时更长。在这种情况下,您可能希望展示默认用户引导,以确保流畅的用户体验,而不是完全不显示用户引导。 为此,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的用户引导。但请务必了解,推荐的方式仍然是使用 `getOnboarding` 方法获取用户引导,详情请参阅上方的[获取用户引导](#fetch-onboarding)部分。 :::warning 建议使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者有以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要设计向后兼容的方案,否则旧版本可能显示异常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向。 如果对您的使用场景而言,更快的获取速度足以抵消上述缺点,请按以下方式使用 `getOnboardingForDefaultAudience`。否则,请按[上文](#fetch-onboarding)所述使用 `getOnboarding`。 ::: ```swift showLineNumbers Adapty.getOnboardingForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(onboarding): // the requested onboarding case let .failure(error): // handle the error } } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 控制台创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及其推荐用法的更多信息,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然存在,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存以及备用用户引导。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的用户引导,同时在网络连接有限的情况下也能保证可靠性。</p> | --- # File: ios-present-onboardings --- --- title: "Present onboardings in iOS SDK" description: "Discover how to present onboardings on iOS to boost conversions and revenue." --- :::tip **从 SDK v4 开始**,你可以构建[流程](get-pb-paywalls),作为用户引导更强大的替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 iOS 观感、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](get-pb-paywalls)和[展示流程与付费墙](ios-present-paywalls)以开始使用。 ::: 如果你已通过编辑工具自定义了用户引导,则无需在移动端代码中手动处理其渲染逻辑——该用户引导已包含展示内容与展示方式的完整配置。 在开始之前,请确保: 1. 已安装 [Adapty iOS SDK](sdk-installation-ios) 3.8.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 ## 在 Swift 中展示用户引导 \{#present-onboardings-in-swift\} 要在设备屏幕上显示可视化用户引导,请按以下步骤操作: 1. 使用 `.getOnboardingConfiguration` 方法获取用户引导视图配置。 2. 使用 `.onboardingController` 方法初始化要显示的可视化用户引导: 请求参数: | 参数 | 是否必填 | 描述 | |:-----------------------------|:---------|:------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding configuration** | 必填 | 一个 `AdaptyUI.OnboardingConfiguration` 对象,包含所有用户引导属性。使用 `AdaptyUI.getOnboardingConfiguration` 方法获取该对象。 | | **delegate** | 必填 | 一个 `AdaptyOnboardingControllerDelegate`,用于监听用户引导事件。 | 返回值: | 对象 | 描述 | |:-------------------------------|:----------------------------------------| | **AdaptyOnboardingController** | 表示所请求的用户引导界面的对象 | 3. 成功创建对象后,您可以将其显示在设备屏幕上: ```swift showLineNumbers title="Swift" import Adapty import AdaptyUI // 0. Get an onboarding if you haven't done it yet let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Create Onboarding View Controller let onboardingController = try AdaptyUI.onboardingController( with: configuration, delegate: <AdaptyOnboardingControllerDelegate> ) // 3. Present it to the user present(onboardingController, animated: true) ``` ## 在 SwiftUI 中展示用户引导 \{#present-onboardings-in-swiftui\} 要在 SwiftUI 中在设备屏幕上显示可视化用户引导: ```swift showLineNumbers title="SwiftUI" // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Display the Onboarding View within your view hierarchy AdaptyOnboardingView( configuration: configuration, placeholder: { Text("Your Placeholder View") }, onCloseAction: { action in // hide the onboarding view }, onError: { error in // handle the error } ) ``` ## 在启动页与用户引导之间添加平滑过渡 \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} 默认情况下,在启动页与用户引导之间,会显示加载页面,直到用户引导完全加载完成。如果你希望让过渡更流畅,可以对其进行自定义,选择延长启动页的显示时间,或展示其他内容。 为此,需要定义一个占位视图(即用户引导加载期间显示的内容)。定义占位视图后,用户引导将在后台加载,加载完成后自动显示。 <Tabs> <TabItem value="swift" label="UIKit"> ```swift showLineNumbers extension YourOnboardingManagerClass: AdaptyOnboardingControllerDelegate { func onboardingsControllerLoadingPlaceholder( _ controller: AdaptyOnboardingController ) -> UIView? { // instantiate and return the UIView which will be presented while onboarding is being loaded } } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers AdaptyOnboardingView( configuration: configuration, placeholder: { // define your placeholder view, which will be presented while onboarding is being loaded }, // the rest of the implementation ) ``` </TabItem> </Tabs> ## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接的打开方式需要 Adapty SDK v3.15.1 及以上版本。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开。这样用户无需切换应用即可浏览网页,从而获得更流畅的使用体验。 如果你希望改为在外部浏览器中打开链接,可以将 `externalUrlsPresentation` 参数设置为 `.externalBrowser` 来自定义此行为: ```swift showLineNumbers let configuration = try AdaptyUI.getOnboardingConfiguration( forOnboarding: onboarding, externalUrlsPresentation: .externalBrowser // default – .inAppBrowser ) ``` --- # File: ios-handling-onboarding-events --- --- title: "在 iOS SDK 中处理用户引导事件" description: "在 iOS 中使用 Adapty 处理用户引导相关事件。" --- :::tip **从 SDK v4 开始**,您可以构建[流程](get-pb-paywalls)作为用户引导的更强大替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 iOS 外观与体验、更快的加载速度,以及无 WebView 运行时依赖。请参阅[获取流程与付费墙](get-pb-paywalls)和[展示流程与付费墙](ios-present-paywalls)以开始使用。 ::: 在开始之前,请确保: 1. 已安装 [Adapty iOS SDK](sdk-installation-ios) 3.8.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 通过编辑工具配置的用户引导会生成各种事件,你的应用可以对这些事件作出响应。请参阅以下内容了解如何处理这些事件。 如需控制或监听移动应用中用户引导界面上发生的流程,请实现 `AdaptyOnboardingControllerDelegate` 方法。 ## 自定义操作 \{#custom-actions\} 在付费墙编辑工具中,你可以为按钮添加**自定义**操作并为其分配一个 ID。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 然后,您可以在代码中使用这个 ID,并将其作为自定义操作来处理。例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,代理方法 `onboardingController` 将以 `.custom(id:)` case 被触发,其中 `actionId` 参数即为编辑工具中设置的 **Action ID**。您可以自定义 ID,例如 "allowNotifications"。 ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCustomAction action: AdaptyOnboardingsCustomAction) { if action.actionId == "allowNotifications" { // Request notification permissions } } func onboardingController(_ controller: AdaptyOnboardingController, didFailWithError error: AdaptyUIError) { // Handle errors } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了 **Close** 动作的按钮时,用户引导即视为已关闭。 <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,你需要自行处理用户关闭用户引导后的逻辑,例如停止显示用户引导界面。 ::: 示例如下: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCloseAction action: AdaptyOnboardingsCloseAction) { controller.dismiss(animated: true) } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## 打开付费墙 \{#opening-a-paywall\} :::tip 如果你希望在用户引导内部打开付费墙,请处理此事件。如果你想在付费墙关闭后再打开另一个付费墙,有一种更直接的方式——处理 [`AdaptyOnboardingsCloseAction`](#closing-onboarding) 事件,无需依赖事件数据即可打开付费墙。 ::: 在用户引导中使用付费墙的最流畅方式,是将 action ID 设置为付费墙版位 ID。这样,在收到 `AdaptyOnboardingsOpenPaywallAction` 后,你可以直接用该版位 ID 获取并打开对应的付费墙。 请注意,同一时间屏幕上只能显示一个视图(付费墙或用户引导)。如果在用户引导上方展示付费墙,则无法通过代码控制后台的用户引导。此时尝试关闭用户引导,实际上会关闭付费墙,导致用户引导仍然可见。为避免此问题,请始终在展示付费墙之前先关闭用户引导视图。 ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onPaywallAction action: AdaptyOnboardingsOpenPaywallAction) { // 在展示流程前关闭用户引导 controller.dismiss(animated: true) { Task { do { // 使用 action 中的版位 ID 获取流程 let flow = try await Adapty.getFlow(placementId: action.actionId) // 获取流程配置 let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow ) // 创建并展示流程控制器 let flowController = try AdaptyUI.flowController( with: flowConfiguration, delegate: self ) // 从根视图控制器展示流程 if let rootVC = UIApplication.shared.windows.first?.rootViewController { rootVC.present(flowController, animated: true) } } catch { // 处理流程加载期间发生的任何错误 print("Failed to present flow: \(error)") } } } } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## 用户引导加载完成 \{#finishing-loading-onboarding\} 当用户引导加载完成时,将调用此方法: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, didFinishLoading action: OnboardingsDidFinishLoadingAction) { // Handle loading completion } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## 跟踪导航 \{#tracking-navigation\} `onAnalyticsEvent` 方法会在用户引导流程中发生各种分析事件时被调用。 `event` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `onboardingStarted` | 用户引导加载完成时触发 | | `screenPresented` | 任意屏幕显示时触发 | | `screenCompleted` | 屏幕完成时触发。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的回复)。当用户执行任意操作退出该屏幕时触发。 | | `secondScreenPresented` | 第二个屏幕显示时触发 | | `userEmailCollected` | 通过输入框收集到用户邮箱时触发 | | `onboardingCompleted` | 当用户到达 ID 为 `final` 的屏幕时触发。如需使用此事件,请[将 `final` ID 分配给最后一个屏幕](design-onboarding)。 | | `unknown` | 用于任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据) | 每个事件都包含 `meta` 信息,其中含有以下字段: | 字段 | 描述 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前屏幕的标识符 | | `screenIndex` | 当前屏幕在流程中的位置 | | `screensTotal` | 流程中的屏幕总数 | 以下是如何使用分析事件进行追踪的示例: ```swift func onboardingController(_ controller: AdaptyOnboardingController, onAnalyticsEvent event: AdaptyOnboardingsAnalyticsEvent) { switch event { case .onboardingStarted(let meta): // Track onboarding start trackEvent("onboarding_started", meta: meta) case .screenPresented(let meta): // Track screen presentation trackEvent("screen_presented", meta: meta) case .screenCompleted(let meta, let elementId, let reply): // Track screen completion with user response trackEvent("screen_completed", meta: meta, elementId: elementId, reply: reply) case .onboardingCompleted(let meta): // Track successful onboarding completion trackEvent("onboarding_completed", meta: meta) case .unknown(let meta, let name): // Handle unknown events trackEvent(name, meta: meta) // Handle other cases as needed } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: ios-onboarding-input --- --- title: "在 iOS SDK 中处理用户引导数据" description: "使用 Adapty SDK 在 iOS 应用中保存并使用用户引导的数据。" --- :::tip **从 SDK v4 开始**,你可以构建[流程](get-pb-paywalls),作为用户引导的更强大替代方案。与在 WebView 中运行的用户引导不同,流程在设备上原生渲染——带来更流畅的动画、一致的 iOS 视觉风格、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](get-pb-paywalls)和[展示流程与付费墙](ios-present-paywalls)以开始使用。 ::: 当用户回答测验问题或在输入字段中输入数据时,`onStateUpdatedAction` 方法将被调用。你可以在代码中保存或处理字段类型。 例如: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Store user preferences or responses switch action.params { case .select(let params): // Handle single selection case .multiSelect(let params): // Handle multiple selections case .input(let params): // Handle text input case .datePicker(let params): // Handle date selection } } ``` `action` 对象包含: | 参数 | 描述 | |----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | 输入元素的唯一标识符。可用于在保存时将问题与答案关联起来。 | | `params` | 用户输入数据对象,包含 type 和 value 属性。 | | `params.type` | 输入元素的类型,可选值为:<br/>• `"select"` - 从选项中单选<br/>• `"multiSelect"` - 从选项中多选<br/>• `"input"` - 文本输入框<br/>• `"datePicker"` - 日期选择 | | `params.value` | 用户选择或输入的值,结构取决于类型:<br/>• `select`:包含 `id`、`value`、`label` 的对象<br/>• `multiSelect`:由包含 `id`、`value`、`label` 的对象组成的数组<br/>• `input`:包含 `type`、`value` 的对象<br/>• `datePicker`:包含 `day`、`month`、`year` 的对象 | <Details> <summary>已保存数据示例(实际实现可能有所不同)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用户画像数据补全 \{#enrich-user-profiles-with-data\} 如果你希望立即将用户输入的数据与其用户画像关联,避免重复询问同样的信息,可以在处理操作时用输入数据[更新用户画像](setting-user-attributes)。 例如,你让用户在 ID 为 `name` 的文本框中输入姓名,并希望将该字段的值设置为用户的名字;同时让用户在 `email` 字段中输入邮箱。在你的应用代码中,实现方式如下: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // 存储用户偏好或响应 switch action.params { case .input(let params): // 处理文本输入 let builder = AdaptyProfileParameters.Builder() // 将 elementId 映射到相应的用户画像字段 switch action.elementId { case "name": builder.with(firstName: params.value.value) case "email": builder.with(email: params.value.value) default: break } // 委托方法是同步的;在 Task 中启动异步更新。 Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // 处理错误 } } default: break } } ``` ### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\} 通过在用户引导中使用测验,你还可以根据用户完成引导后的情况,为他们展示定制化的付费墙。 例如,你可以询问用户的运动经验,并向不同用户群体展示不同的行动号召文案和产品。 1. 在用户引导编辑器中[添加测验](onboarding-quizzes),并为各选项设置有意义的 ID。 2. 根据 ID 处理测验响应,并为用户[设置自定义属性](setting-user-attributes)。 ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Handle quiz responses and set custom attributes switch action.params { case .select(let params): // Handle quiz selection let builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes switch action.elementId { case "experience": // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) try? builder.with(customAttribute: params.value.value, forKey: "experience") default: break } // Delegate methods are synchronous; kick off the async update in a Task. Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } } default: break } } ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为你创建的每个市场细分添加[目标受众](audience)。 5. 在你的应用代码中为该版位[展示付费墙](ios-paywalls)。如果你的用户引导中有一个打开付费墙的按钮,请将付费墙代码实现为[响应该按钮操作](ios-handling-onboarding-events#opening-a-paywall)的逻辑。 --- # File: ios-sdk-call-order --- --- title: "iOS SDK 调用顺序" description: "通过按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、缺失归因数据以及间歇性 #2002 错误。" --- `Adapty.activate()` 必须完成后,才能调用任何其他 Adapty SDK 方法。在其完成之前,SDK 没有任何状态。在 `activate()` 之前或与其并行发出的任何调用都会失败,并返回 [`#2002 notActivated`](ios-sdk-error-handling#network-errors) 错误。 如果您的应用需要用户登录,并在启动后才能获取到 customer user ID,请在获取到时调用 `Adapty.identify()`。在 `identify` 完成之前,不要调用任何需要用户操作的方法。与其并发的调用要么以 [`#3006 profileWasChanged`](ios-sdk-error-handling#general-errors) 错误告终,要么落在激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属不一定会同步到已识别的用户画像。如果您的应用无需用户登录,跳过 `identify`,直接使用匿名用户画像即可。 MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循相同的规则。请先初始化它们,并等待其 UID 回调完成后,再调用 `Adapty.activate`。否则,MMP ID 会被绑定到一个短暂的匿名用户画像上,而不一定会转移到已识别的用户画像中。有关 AppsFlyer 的详细信息,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的路径取决于两件事:何时获取客户用户 ID,以及是否使用 MMP 或分析 SDK。 - **步骤 2 和 5**:每个应用都必须执行。先激活 SDK,再调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。 - **步骤 4**:仅在应用需要用户认证、且在启动后才能获取客户用户 ID 时需要。 如果在应用启动时已知客户用户 ID,可直接将其传入 `activate()`(步骤 2a)。这种方式不会创建匿名用户画像,因此步骤 4 无需执行。 | 步骤 | 调用 | 时机 | 说明 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动时,最先执行 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `Adapty.activate(with: config)`,且在 config 中设置了 `customerUserId` | 应用启动,步骤 1 之后,如果你已有 customer user ID | 推荐方式。不会创建任何匿名用户画像。 | | 2b | `Adapty.activate(with: config)`,不设置 `customerUserId` | 应用启动,步骤 1 之后,如果你没有 customer user ID(或从不收集) | Adapty 会创建一个匿名用户画像。 | | 3 | 为每个 MMP 调用 `Adapty.setIntegrationIdentifier(...)` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,以确保 MMP ID 关联到正确的用户画像。 | | 4 | `try await Adapty.identify("YOUR_USER_ID")` | 步骤 3 之后(如无 MMP 则步骤 2 之后),步骤 5 之前——仅适用于路径 2b 且需要身份验证时 | 必须使用 `await`。在 `identify` 期间并发调用会产生 `#3006 profileWasChanged` 错误。 | | 5 | `getPaywall`、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 如果调用了 `identify`,则在步骤 4 之后;否则在步骤 3 之后(如无 MMP 则步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回访用户丢失高级访问权限、用户画像缺少 `appsflyer_id`,以及付费墙匹配到错误的目标受众。 ::: ## Web2app 与 Web 漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在 Web 结账页面(Stripe、Paddle、FunnelFox)完成购买后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与 Web 用户画像关联。如果你能在应用启动前(通过认证流程或安装引荐来源)获取到 customer user ID,请直接将其传入 `activate()`。否则,在你调用 `identify("YOUR_USER_ID")` 并执行 `restorePurchases` 之前,设备端将无法看到 Web 端的购买记录。 关于每次 Web 结账时需要传递的元数据,请参阅: - [Stripe](stripe) - [Paddle](paddle) --- # File: ios-optimize-paywall-fetching --- --- title: "在 iOS SDK 中优化付费墙加载" description: "可靠地获取 Adapty 付费墙:iOS 的时机、缓存与备用方案。" --- 在 iOS 上可靠地获取付费墙需要做到三点:快速渲染、返回面向目标受众的付费墙,以及在网络较慢时优雅地回退到备用方案。以下规则涵盖了实现这些目标所需的时机、缓存与备用方案。 :::tip 以下规则假设 `Adapty.activate()` 和 `Adapty.identify()` 已经执行完毕。请参阅 [iOS SDK 中的调用顺序](ios-sdk-call-order)。 ::: ## 规则与注意事项 \{#rules-and-pitfalls\} | 推荐做法 | 避免做法 | 原因 | |---|---|---| | 在即将展示版位时再拉取它。 | 启动时并发预拉取所有版位。 | 批量预拉取会阻塞主线程,并在请求峰值期间出现黑屏。 | | 在归因有机会完成之后再调用 `getPaywall`——例如在 `activate` 之后等待 1–2 秒,或等 `onProfileUpdate` 触发后再调用。 | 在 `App.init()` 时调用 `getPaywall`。 | 此时归因数据尚未到达,付费墙会按默认目标受众解析,悄然跳过市场细分和 ASA 个性化逻辑。 | | 为每个版位设置 `loadTimeout` 并配置[备用付费墙](fallback-paywalls)。 | 无限等待 `getPaywall` 返回。 | 没有超时限制时,网络状况差的用户会一直看到空白屏幕,直到网络恢复——或者直接关掉应用。 | 请参阅[获取付费墙和产品](fetch-paywalls-and-products)了解 `fetchPolicy` 和 `loadTimeout` 参数说明,以及[版位](placements)了解如何选择合适的版位。 ## 针对网络状况较差的情况进行调优 \{#tune-for-poor-connectivity\} 针对网络连接持续不稳定的市场(如农村地区、交通途中、受路由影响的地区): - 除首次请求外,所有获取操作均设置 `fetchPolicy: .returnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeout` 设置为 3–5 秒,超时后直接使用备用付费墙。 - 不要将付费墙的显示依赖于 `getProfile()` 的结果。独立调用 `getPaywall`,避免因 profile 响应慢而阻塞 UI。 --- # File: ios-show-aa-targeted-paywall --- --- title: "在 iOS SDK 中首次启动时展示 AA 定向付费墙" description: "在 iOS 上使用 AdaptyProfile.appliedAttributionSources 等待 Apple Ads 归因后再请求付费墙。" --- Apple Ads (AA) 归因数据在 `Adapty.activate()` 之后异步到达。如果过早调用 `getPaywall`,归因数据往往尚未到位,Adapty 会根据默认目标受众来解析版位——从而绕过你基于 AA 市场细分的付费墙。`AdaptyProfile.appliedAttributionSources` 让应用能够检测 AA 归因何时已应用到用户画像,以便付费墙请求等到 AA 市场细分正确解析后再发出。 ## 开始之前 \{#before-you-start\} 你需要: - Adapty iOS SDK **3.17.1** 或更高版本。 - 在 Adapty 中为应用配置 Apple Ads。请参阅 [Apple Ads](apple-search-ads)。 ## 工作原理 \{#how-it-works\} 调用 `Adapty.activate()` 后,SDK 会在后台向 Apple 请求 Apple Ads 归因数据,并将结果转发至 Adapty 后端。当 AA 成为该用户画像的有效归因来源时,SDK 会返回一个更新后的 `AdaptyProfile`,其 `appliedAttributionSources` 数组中包含 `.appleAds`。 数组为空可能意味着以下任一情况: - 该用户画像的 Apple Ads 归因尚未处理完成。 - 归因数据尚未到达。 即使传入空数组,调用 `getPaywall` 也是安全的——Adapty 会根据当前用户画像状态匹配对应的目标受众来处理请求,通常为默认目标受众。 :::important 这个等待仅适用于**首次启动**。一旦 Apple Ads 归因数据被记录,它会永久保存在用户画像中。在后续每次启动时,缓存的用户画像已包含 `.appleAds`(位于 `appliedAttributionSources` 中),`didLoadLatestProfile` 会立即触发并返回该值,`getPaywall` 也会直接返回针对 Apple Ads 市场细分的付费墙,无需任何等待。 ::: ## 实现 \{#implementation\} 首次启动时,监听用户画像中的 `.appleAds` 字段,并设置一个硬超时——即便 Apple Ads 归因数据始终未到达,这些用户也需要看到付费墙。 1. **激活 SDK。** 请参阅[安装并配置 iOS SDK](sdk-installation-ios)。 2. **订阅用户画像更新**,方法是遵循 `AdaptyDelegate` 协议并实现 `didLoadLatestProfile`。如果尚未设置代理,请参阅[监听订阅更新](ios-check-subscription-status#listen-to-subscription-updates)。 3. **监听 `appliedAttributionSources` 中的 `.appleAds`。** 一旦该值出现,立即请求付费墙——Adapty 将返回经 AA 细分的实验变体: ```swift extension <YourAdaptyDelegateImpl>: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { if profile.appliedAttributionSources.contains(where: { $0 == .appleAds }) { // 通过 Adapty.getPaywall(placementId:) 加载付费墙 } } } ``` 4. **同步启动一个 3–5 秒的计时器。** 如果计时器触发时 `.appleAds` 尚未出现,直接请求付费墙: 无论哪条路径先触发,都应加载付费墙;另一条路径应被跳过。使用一个状态标志(例如 `hasLoadedPaywall`)进行去重,避免付费墙被重复获取。为该版位配置[备用付费墙](fallback-paywalls),确保网络请求失败时用户不会陷入等待。 ## 完整示例 \{#complete-example\} 以下实现将归因与超时进行竞争,同时预取默认受众的付费墙,并返回合适的付费墙。调用方只需等待一个异步函数——无需在调用处管理代理或状态标志。 `ProfileObserver` 是一个可复用的单例,用于发布来自 `AdaptyDelegate` 的用户画像更新。`PaywallLoader.getPaywallOrDefault` 使用结构化并发 `TaskGroup` 执行竞争逻辑: - 如果归因数据在 `timeout` 时间内到达,则通过 `getPaywall(placementId:)` 返回按目标受众细分的付费墙。 - 如果 `timeout` 先超时,则通过 `getPaywallForDefaultAudience(placementId:)` 返回预取的默认受众付费墙。 ```swift title="PaywallLoader.swift" /// 演示如何获取一个依赖归因数据的付费墙, /// 若归因数据未能及时到达,则回退到默认受众付费墙。 /// /// 无状态且自包含:每次调用都会发起独立的默认受众预请求, /// 并与"等待归因 + 获取细分付费墙"的流程竞速。 enum PaywallLoader { static func getPaywallOrDefault( placementId: String, timeout: TimeInterval ) async throws -> AdaptyPaywall { struct TimedOut: Error {} // 立即发起默认受众请求,使其拥有完整的 `timeout` 窗口来完成加载。 // 若细分付费墙成功获取则取消它,若超时则等待其结果——绝不发起重复的网络请求。 let defaultPaywallTask = Task { try await Adapty.getPaywallForDefaultAudience(placementId: placementId) } do { // 让两个子任务竞速,谁先完成谁赢。 let result = try await withThrowingTaskGroup(of: AdaptyPaywall.self) { group in // 1. 等待归因数据就绪,然后向 Adapty 请求细分付费墙。 group.addTask { await waitForAttribution() return try await Adapty.getPaywall(placementId: placementId) } // 2. 定时炸弹:经过 `timeout` 秒后抛出 `TimedOut`。 group.addTask { try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) throw TimedOut() } guard let value = try await group.next() else { throw CancellationError() } group.cancelAll() // 取消落败方(定时器或归因等待任务)。 return value } // 细分付费墙胜出——默认受众预请求不再需要,直接取消。 defaultPaywallTask.cancel() return result } catch is TimedOut { // 归因数据未能在规定时间内就绪——返回预请求的默认付费墙 // (若已完成则立即返回,否则等待进行中的请求)。 return try await defaultPaywallTask.value } } /// 挂起协程,直到观察到包含所需归因来源的用户画像。 /// `@Published.values` 在订阅时会立即发出当前值, /// 因此若归因已就绪,第一次迭代时即可返回。 @MainActor private static func waitForAttribution() async { for await profile in ProfileObserver.shared.$profile.values { if profile?.appliedAttributionSources.contains(.appleAds) == true { return } } } } @MainActor final class ProfileObserver: AdaptyDelegate { static let shared = ProfileObserver() @Published private(set) var profile: AdaptyProfile? nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { Task { @MainActor [weak self] in self?.profile = profile } } } ``` 在 `Adapty.activate()` 完成后,将 `ProfileObserver` 注册到 `AdaptyDelegate` 一次即可: ```swift Adapty.delegate = ProfileObserver.shared ``` 在启动屏中调用: ```swift do { let paywall = try await PaywallLoader.getPaywallOrDefault( placementId: "YOUR_PLACEMENT_ID", timeout: 5 ) // present the paywall } catch { // handle the error or show a fallback paywall } ``` 如果你的应用已经在使用 `AdaptyDelegate` 处理其他事务(例如[监听订阅更新](ios-check-subscription-status#listen-to-subscription-updates)),请不要将 `Adapty.delegate = ProfileObserver.shared` 直接设置,而是在现有的 delegate 中将 `didLoadLatestProfile` 转发给 `ProfileObserver.shared`。 --- # File: ios-test --- --- title: "iOS SDK 测试与发布" description: "了解如何在 iOS 应用中使用 Adapty 检查订阅状态。" --- 如果您已在 iOS 应用中集成了 Adapty SDK,接下来需要测试所有配置是否正确,以及购买功能是否按预期运行。这包括测试 SDK 集成和实际购买流程。 ## 测试您的应用 \{#test-your-app\} 有关应用内购买的全面测试指南,包括沙盒测试和 TestFlight 验证,请参阅我们的[测试指南](test-purchases-in-sandbox)。 ## 发布前准备 \{#prepare-for-release\} 在将应用提交至应用商店之前,请按照[发布清单](release-checklist)确认以下事项: - 已配置应用商店连接和服务器通知 - 购买流程正常完成且已上报至 Adapty - 访问等级可正常解锁和恢复 - 已满足隐私政策和审核要求 --- # File: InvalidProductIdentifiers --- --- title: "修复 Code-1000 noProductIDsFound 错误" description: "解决在 Adapty 中管理订阅时出现的无效产品标识符错误。" --- 1000 代码错误 `noProductIDsFound` 表示你在付费墙中请求的产品在 App Store 中无法购买,尽管这些产品已在 App Store 中列出。此错误有时会附带 `InvalidProductIdentifiers` 警告。如果只出现警告而没有错误,可以直接忽略。 如果你遇到了 `noProductIDsFound` 错误,请按以下步骤排查解决: ## 步骤 1. 检查 Bundle ID \{#step-2-check-bundle-id\} 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 第二步:检查产品 \{#step-2-check-products\} 1. 前往 **App Store Connect**,在左侧菜单中导航至 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions)。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,你会在 **Subscriptions** 部分看到你的产品列表。 3. 确认你要测试的产品已标记为 **Ready to Submit**。如果没有,请按照 [App Store 产品](app-store-products) 页面上的说明操作。 <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将表格中的产品 ID 与 Adapty 看板中 [**Products**](https://app.adapty.io/products) 标签页里的产品 ID 进行比对。如果 ID 不匹配,请从表格中复制产品 ID,并在 Adapty 看板中[创建产品](create-product)。 <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 3. 检查产品可用性 \{#step-4-check-product-availability\} 1. 返回 **App Store Connect**,打开同一个 **Subscriptions** 页面。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,查看你的产品。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 滚动到 **Availability** 部分,确认所有所需的国家和地区均已列出。 <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第四步:检查产品价格 \{#step-5-check-product-prices\} 1. 再次前往 **App Store Connect** 中的 **Monetization** → **Subscriptions** 部分。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动至 **Subscription Pricing**,展开 **Current Pricing for New Subscribers** 部分。 <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 确认所有必要的价格均已填写。 <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 5. 检查应用付费状态、银行账户和税务表格是否处于有效状态 \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. 在 **App Store Connect**](https://appstoreconnect.apple.com/) 主页,点击 **Business**。 <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择你的公司名称。 3. 向下滚动,确认您的 **Paid Apps Agreement**、**Bank Account** 和 **Tax forms** 均显示为 **Active**。 按照以上步骤操作,您应该能够解决 `InvalidProductIdentifiers` 警告,并让您的产品在应用商店中正常上线。 ## 第六步:如果产品卡住了,重新创建它 \{#step-6-recreate-the-product-if-its-stuck\} 前五步可能全部通过——`Approved` 状态、Bundle ID 匹配、API 密钥有效——但 SDK 仍然返回 `1000 noProductIDsFound`。这种情况下,该产品可能卡在了 Apple 的注册表中。Apple 的产品注册表偶尔会进入一种状态:产品在 App Store Connect 的界面中存在,但无法通过 StoreKit 的查询路径访问。 在 App Store Connect 中删除该产品,然后用相同的产品 ID 重新创建。重新创建后,等待最长 24 小时以完成数据同步。 --- # File: cantMakePayments --- --- title: "修复 Code-1003 cantMakePayment 错误" description: "解决在 Adapty 中管理订阅时出现的支付错误。" --- 1003 错误 `cantMakePayments` 表示该设备无法进行应用内购买。 如果你遇到了 `cantMakePayments` 错误,通常是由以下原因之一导致的: - 设备限制:该错误与 Adapty 无关。请参阅下方的修复方法。 - 观察者模式配置:`makePurchase` 方法与观察者模式不能同时使用。请参阅下方相关章节。 ## 问题:设备限制 \{#issue-device-restrictions\} | 问题 | 解决方案 | |---------------------------|---------------------------------------------------------| | 屏幕使用时间限制 | 在 [Screen Time](https://support.apple.com/en-us/102470) 中关闭应用内购买限制 | | 账户被暂停 | 联系 Apple 支持以解决账户问题 | | 地区限制 | 使用受支持地区的 App Store 账户 | ## 问题:同时使用观察者模式和 makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} 如果你使用 `makePurchases` 来处理购买,则无需启用观察者模式。[观察者模式](observer-vs-full-mode) 仅在你自行实现购买逻辑时才需要使用。 因此,如果你正在使用 `makePurchase`,可以安全地从 SDK 激活代码中移除对观察者模式的启用。 --- # File: migration-to-ios-sdk-v4 --- --- title: "迁移 Adapty iOS SDK 至 v4.0" description: "通过将付费墙 API 替换为流程 API,迁移至 Adapty iOS SDK v4.0,兼容流程编辑工具和付费墙编辑工具。" --- Adapty iOS SDK 4.0 引入了流程概念,并相应地对付费墙 API 进行了重命名。新 API 同时兼容全新的流程编辑工具和现有的付费墙编辑工具——无需在 Adapty 看板侧进行任何配置变更。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId:locale:)` | `Adapty.getFlow(placementId:)` | | `AdaptyUI.getPaywallConfiguration(forPaywall:)` | `AdaptyUI.getFlowConfiguration(forFlow:locale:)` | | `Adapty.getPaywallProducts(paywall:)` | `Adapty.getPaywallProducts(flow:)` | | `Adapty.logShowPaywall(_:)` | `Adapty.logShowFlow(_:)` | | `AdaptyPaywallController` | `AdaptyFlowController` | | `AdaptyPaywallControllerDelegate` | `AdaptyFlowControllerDelegate` | | `AdaptyUI.paywallController(with:delegate:)` | `AdaptyUI.flowController(with:delegate:)` | | `.paywall()` (SwiftUI modifier) | `.flow()` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `didFailRenderingWith:` / `didFailRendering:` | `didReceiveError:` | | `didFinishPurchase`(可选,成功后自动关闭) | `didFinishPurchase`(必选,不自动关闭) | | `Adapty_KidsMode` / `AdaptyUI_KidsMode` 包产品 | `KidsMode` 包特性 | | `Adapty.updateAttribution(_:source:)`(`source: String`) | `Adapty.updateAttribution(_:source:)`(`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key:value:)` | `Adapty.setIntegrationIdentifier(_:)`(`AdaptyIntegrationIdentifier`) | ## 最低 iOS 版本要求 \{#minimum-ios-version\} Adapty iOS SDK 4.0 将最低部署目标从 iOS 13.0 提升至 **iOS 15.0**。在升级之前,请将项目的 iOS Deployment Target 设置为 15.0 或更高版本。 ## 安装:不再支持 CocoaPods \{#installation-cocoapods-no-longer-supported\} Adapty iOS SDK 4.0 已放弃对 CocoaPods 的支持。请改用 [Swift Package Manager](sdk-installation-ios#install-adapty-sdk) 安装 SDK。 如果你的项目仍在使用 CocoaPods,请从 `Podfile` 中移除 `Adapty` 和 `AdaptyUI` pods,运行 `pod install` 将其清理,然后在 Xcode 中通过 **File → Add Package Dependency**,使用 `https://github.com/adaptyteam/AdaptySDK-iOS.git` 添加该包。 ## 儿童模式:独立产品替换为 Package Trait \{#kids-mode-separate-products-replaced-by-a-package-trait\} 在 v3 中,启用[儿童模式](kids-mode)需要选择独立的 **Adapty_KidsMode** 和 **AdaptyUI_KidsMode** 包产品并重命名导入。在 v4.0 中,这些产品已被移除。儿童模式现在是常规 Adapty 包中名为 `KidsMode` 的 Swift Package Trait——启用后,整个 SDK 中的 IDFA 和 AdSupport 都会被编译排除。 迁移步骤: 1. 在 **Choose Package Products** 窗口中,选择常规的 **Adapty** 和 **AdaptyUI** 产品,而非 **Adapty_KidsMode** 和 **AdaptyUI_KidsMode**。 2. 启用 `KidsMode` trait。在 Xcode 26.4 或更高版本中,在项目的 **Package Dependencies** 视图里为 AdaptySDK-iOS 依赖项启用该 trait。如果你在 `Package.swift` 中添加 Adapty 作为依赖项(需要 `swift-tools-version` 6.1 或更高版本),可以在此处启用: ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` 3. 将 import 改回常规模块: ```diff showLineNumbers - import Adapty_KidsMode - import AdaptyUI_KidsMode + import Adapty + import AdaptyUI ``` :::note 早于 26.4 版本的 Xcode 无法通过 UI 为 Xcode 项目启用 traits。此时,请添加一个本地 Swift 包,让其依赖启用了 `KidsMode` trait 的 Adapty,然后让你的应用目标依赖该包。 ::: ## 已移除的 API \{#removed-apis\} - **`Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:)`** — 已移除。所有产品现在均包含优惠信息,因此不再需要单独的资格验证步骤。 - **`AdaptyPaywallProductWithoutDeterminingOffer`** — 已移除。之前传递此类型的回调(例如 `didSelectProduct`)现在改为传递 `AdaptyPaywallProduct`。 ## App Store 促销应用内购买功能暂时移除 \{#app-store-promoted-in-app-purchases-temporarily-removed\} 作为 StoreKit 2 迁移的一部分,Adapty iOS SDK 4.0 移除了对 App Store 促销应用内购买的支持。`shouldAddStorePayment(for:)` 代理方法及其接收的 `AdaptyDeferredProduct` 类型在 4.0 中不再可用。 :::warning 此功能的移除是暂时的——促销应用内购买支持将在后续的 4.x 版本中回归。如果您的应用依赖促销应用内购买,请继续使用 iOS SDK 3.x,直到该功能恢复。 ::: ## 获取付费墙 \{#fetching-paywalls\} ### getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration 返回类型从 `AdaptyPaywall` / `AdaptyUI.PaywallConfiguration` 变更为 `AdaptyFlow` / `AdaptyUI.FlowConfiguration`。`locale` 参数从 fetch 调用中移出,改为在 `getFlowConfiguration` 中传入: ```diff showLineNumbers - let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") - let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall) + let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") + let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow, locale: "en") ``` ### getPaywallProducts(paywall:) → getPaywallProducts(flow:) `getPaywallProducts` 现在接受由 `Adapty.getFlow` 返回的 `AdaptyFlow`: ```diff showLineNumbers - let products = try await Adapty.getPaywallProducts(paywall: paywall) + let products = try await Adapty.getPaywallProducts(flow: flow) ``` ## 追踪付费墙展示 \{#tracking-paywall-views\} ### logShowPaywall(_:) → logShowFlow(_:) `logShowPaywall` 已重命名为 `logShowFlow`,现在接受 `AdaptyFlow` 而非 `AdaptyPaywall`。事件仍会记录在相同的实验变体下,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。 ```diff showLineNumbers - try await Adapty.logShowPaywall(paywall) + try await Adapty.logShowFlow(flow) ``` 与 v3 一样,在显示由[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙时,无需调用此方法——Adapty 会自动追踪这些浏览记录。 ## didFinishPurchase 现在是必须实现的 \{#didfinishpurchase-is-now-required\} 在 v3 中,`didFinishPurchase` 是可选的:如果你没有实现它,付费墙会在购买成功后自动关闭。在 v4.0 中,这个默认的自动关闭行为已被移除,以便流程在购买成功后可以继续——例如,展示流程的后续页面。现在由你决定购买完成后的行为:关闭页面,或者什么都不做以让流程继续。 - **UIKit**:`AdaptyFlowControllerDelegate` 的实现者必须实现 `didFinishPurchase` —— 该方法不再提供默认实现。 - **SwiftUI**:`.flow(...)` 和 `AdaptyFlowView(...)` 的 `didFinishPurchase` 闭包现在为非可选类型,与 `didFailPurchase` 和 `didFinishRestore` 保持一致。 如需保持 v3 的行为,请自行关闭页面: ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) } } ``` ## UIKit \{#uikit\} ### AdaptyPaywallController → AdaptyFlowController 重命名控制器类型和工厂方法: ```diff showLineNumbers - let controller = try AdaptyUI.paywallController( - with: paywallConfiguration, - delegate: self - ) + let controller = try AdaptyUI.flowController( + with: flowConfiguration, + delegate: self + ) ``` ### AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate 重命名该协议并更新所有方法签名。请注意,`didSelectProduct` 现在接收 `AdaptyPaywallProduct` 而非已移除的 `AdaptyPaywallProductWithoutDeterminingOffer`,且 `didFinishPurchase` [现在必须实现](#didfinishpurchase-is-now-required) —— 它不再有默认实现。 ```diff showLineNumbers - class YourClass: AdaptyPaywallControllerDelegate { + class YourClass: AdaptyFlowControllerDelegate { - func paywallControllerDidAppear(_ controller: AdaptyPaywallController) { } + func flowControllerDidAppear(_ controller: AdaptyFlowController) { } - func paywallControllerDidDisappear(_ controller: AdaptyPaywallController) { } + func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didPerform action: AdaptyUI.Action) { } + func flowController(_ controller: AdaptyFlowController, + didPerform action: AdaptyUI.Action) { } - func paywallController(_ controller: AdaptyPaywallController, - didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer) { } + func flowController(_ controller: AdaptyFlowController, + didSelectProduct product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didStartPurchase product: AdaptyPaywallProduct) { } + func flowController(_ controller: AdaptyFlowController, + didStartPurchase product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishPurchase product: AdaptyPaywallProduct, - purchaseResult: AdaptyPurchaseResult) { } + func flowController(_ controller: AdaptyFlowController, + didFinishPurchase product: AdaptyPaywallProduct, + purchaseResult: AdaptyPurchaseResult) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailPurchase product: AdaptyPaywallProduct, - error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailPurchase product: AdaptyPaywallProduct, + error: AdaptyError) { } - func paywallControllerDidStartRestore(_ controller: AdaptyPaywallController) { } + func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishRestoreWith profile: AdaptyProfile) { } + func flowController(_ controller: AdaptyFlowController, + didFinishRestoreWith profile: AdaptyProfile) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRestoreWith error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailRestoreWith error: AdaptyError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRenderingWith error: AdaptyUIError) { } + func flowController(_ controller: AdaptyFlowController, + didReceiveError error: AdaptyUIError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailLoadingProductsWith error: AdaptyError) -> Bool { } + func flowController(_ controller: AdaptyFlowController, + didFailLoadingProductsWith error: AdaptyError) -> Bool { } - func paywallController(_ controller: AdaptyPaywallController, - didPartiallyLoadProducts failedIds: [String]) { } + func flowController(_ controller: AdaptyFlowController, + didPartiallyLoadProducts failedIds: [String]) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, - error: AdaptyError?) { } + func flowController(_ controller: AdaptyFlowController, + didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, + error: AdaptyError?) { } } ``` ## SwiftUI \{#swiftui\} ### .paywall() 修饰符 → .flow() \{#paywall-modifier--flow\} 重命名修饰符,更新配置参数名称,并添加[现在必需的](#didfinishpurchase-is-now-required) `didFinishPurchase` 闭包: ```diff showLineNumbers @State var flowPresented = false // rename freely — the variable name is your choice var body: some View { Text("Hello, AdaptyUI!") - .paywall( + .flow( isPresented: $flowPresented, - paywallConfiguration: paywallConfiguration, + flowConfiguration: flowConfiguration, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in flowPresented = false } + didReceiveError: { error in flowPresented = false } ) } ``` 重命名后的回调触发时机与原来的 `didFailRendering` 相同,同时新增了流程脚本产生的运行时错误(`AdaptyUIError` 错误码 `4105`——`.jsException` 对应的 JavaScript 异常)。现有的处理器代码无需修改——只需重命名参数即可。 ### AdaptyPaywallView → AdaptyFlowView 重命名视图,更新配置参数,添加[现在必需的](#didfinishpurchase-is-now-required) `didFinishPurchase` 闭包,并更新所有 `didSelectProduct` 闭包——它现在接收 `AdaptyPaywallProduct`,而不是已移除的 `AdaptyPaywallProductWithoutDeterminingOffer`: ```diff showLineNumbers - AdaptyPaywallView( - paywallConfiguration: paywallConfiguration, - didSelectProduct: { product: AdaptyPaywallProductWithoutDeterminingOffer in /* handle */ }, + AdaptyFlowView( + flowConfiguration: flowConfiguration, + didSelectProduct: { product: AdaptyPaywallProduct in /* handle */ }, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in /* handle the error */ } + didReceiveError: { error in /* handle the error */ } ) ``` ## AdaptyUI 自定义资源 \{#adaptyui-custom-assets\} ### AdaptyUICustomVideoAsset 以下两项变更会影响所有已有的调用位置: - `.player` 现在接受 `AVPlayer` 而非 `AVQueuePlayer`。 - 每个 case 新增了末尾参数 `resolution: CGSize?`。传入 `nil` 可保持现有行为;传入实际像素尺寸后,播放器可在视频加载前预留布局空间(宽高比 = `width / height`)。 ```diff showLineNumbers - case file(url: URL, preview: AdaptyUICustomImageAsset?) - case remote(url: URL, preview: AdaptyUICustomImageAsset?) - case player(item: AVPlayerItem, player: AVQueuePlayer, preview: AdaptyUICustomImageAsset?) + case file(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case remote(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case player(item: AVPlayerItem, player: AVPlayer, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) ``` ## 归因与集成标识符 \{#attribution-and-integration-identifiers\} ### updateAttribution(_:source:) `source` 参数的类型从 `String` 改为新的 `AdaptyAttributionSource` 类型,原来嵌套的 `AdaptyProfile.AttributionSource` 被重命名为顶层的 `AdaptyAttributionSource`。可以使用预定义的来源之一,也可以传入字符串字面量表示其他来源——`AdaptyAttributionSource` 遵循 `ExpressibleByStringLiteral`,因此现有的字符串字面量调用无需修改即可继续编译。 ```diff showLineNumbers - try await Adapty.updateAttribution(attribution, source: "adjust") + try await Adapty.updateAttribution(attribution, source: .adjust) ``` 预定义来源:`.appleAds`、`.adjust`、`.appsflyer`、`.branch`、`.tenjin`。如果来源存储在 `String` 变量中,请用 `AdaptyAttributionSource(rawValue: yourSource)` 包装。 ### setIntegrationIdentifier(_:) `setIntegrationIdentifier(key:value:)` 已被替换为一个可变参数方法,支持传入一个或多个 `AdaptyIntegrationIdentifier` 值。请使用预定义的工厂方法,而非原始字符串键: ```diff showLineNumbers - try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + try await Adapty.setIntegrationIdentifier(.appsflyerId(uid)) ``` 你可以在单次调用中设置多个标识符: ```swift showLineNumbers try await Adapty.setIntegrationIdentifier( .appsflyerId(uid), .adjustDeviceId(adid) ) ``` 将每个旧的键字符串替换为其工厂方法: | v3 key | v4 factory | |---|---| | `"adjust_device_id"` | `.adjustDeviceId(_:)` | | `"airbridge_device_id"` | `.airbridgeDeviceId(_:)` | | `"amplitude_user_id"` | `.amplitudeUserId(_:)` | | `"amplitude_device_id"` | `.amplitudeDeviceId(_:)` | | `"appmetrica_device_id"` | `.appmetricaDeviceId(_:)` | | `"appmetrica_profile_id"` | `.appmetricaProfileId(_:)` | | `"appsflyer_id"` | `.appsflyerId(_:)` | | `"branch_id"` | `.branchId(_:)` | | `"facebook_anonymous_id"` | `.facebookAnonymousId(_:)` | | `"firebase_app_instance_id"` | `.firebaseAppInstanceId(_:)` | | `"mixpanel_user_id"` | `.mixpanelUserId(_:)` | | `"one_signal_subscription_id"` | `.oneSignalSubscriptionId(_:)` | | `"one_signal_player_id"` | `.oneSignalPlayerId(_:)` | | `"posthog_distinct_user_id"` | `.posthogDistinctUserId(_:)` | | `"pushwoosh_hwid"` | `.pushwooshHWID(_:)` | | `"tenjin_analytics_installation_id"` | `.tenjinAnalyticsInstallationId(_:)` | --- # File: migration-to-ios-315 --- --- title: "迁移 Adapty iOS SDK 至 v3.15" description: "迁移至 Adapty iOS SDK v3.15,获得更好的性能和新的变现功能。" --- 如果你在[观察者模式](observer-vs-full-mode)下使用[付费墙编辑工具](adapty-paywall-builder),从 iOS SDK 3.15 开始,你需要实现一个新方法 `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)`。该方法为恢复逻辑提供了更精细的控制,让你可以在自定义流程中处理购买恢复操作。完整的实现细节,请参阅[在观察者模式下展示付费墙编辑工具付费墙](ios-present-paywall-builder-paywalls-in-observer-mode)。 ```diff showLineNumbers func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } + func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, + onFinishRestore: @escaping () -> Void) { + // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore + } ``` --- # File: migration-to-ios-sdk-34 --- --- title: "将 Adapty iOS SDK 迁移至 v3.4" description: "迁移至 Adapty iOS SDK v3.4,享受更优性能与全新变现功能。" --- Adapty SDK 3.4.0 是一个重大版本更新,引入了若干改进,需要你在项目中执行相应的迁移操作。 ## 更新 Adapty SDK 激活方式 \{#update-adapty-sdk-activation\} <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") - Adapty.activate(with: configurationBuilder) { error in + Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` **更新备用付费墙文件** 更新备用付费墙文件以确保与新版 SDK 的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 用新文件[替换移动应用中现有的备用付费墙](ios-use-fallback-paywalls)。 </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") Task { - try await Adapty.activate(with: configurationBuilder) + try await Adapty.activate(with: configurationBuilder.build()) } } var body: some Scene { WindowGroup { ContentView() } } } ``` **更新备用付费墙文件** 更新你的备用付费墙文件,以确保与新版 SDK 的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 用新文件[替换移动应用中现有的备用付费墙](ios-use-fallback-paywalls)。 </TabItem> </Tabs> --- # File: migration-to-ios330 --- --- title: "迁移 Adapty iOS SDK 至 v3.3" description: "迁移至 Adapty iOS SDK v3.3,享受更高性能与全新变现功能。" --- Adapty SDK 3.3.0 是一个主要版本,带来了一些改进,但可能需要你执行一些迁移步骤。 1. 将 `Adapty.Configuration` 重命名为 `AdaptyConfiguration`。 2. 将 `getViewConfiguration` 方法重命名为 `getPaywallConfiguration`。 3. 从 SwiftUI 中移除 `didCancelPurchase` 和 `paywall` 参数,并将 `viewConfiguration` 参数重命名为 `paywallConfiguration`。 4. 通过从 `AdaptyDelegate` 方法中移除 `defermentCompletion` 参数,更新处理 App Store 应用内促销购买的方式。 5. 移除 `getProductsIntroductoryOfferEligibility` 方法。 6. 更新 Adjust、AirBridge、Amplitude、AppMetrica、Appsflyer、Branch、Facebook Ads、Firebase and Google Analytics、Mixpanel、OneSignal、Pushwoosh 的集成配置。 7. 更新 Observer 模式的实现方式。 <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/9Xs8d0lt_RY?si=xvWhUO2tlG1tKP5f" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen> </iframe> </div> ## 将 Adapty.Configuration 重命名为 AdaptyConfiguration \{#rename-adaptyconfiguration-to-adaptyconfiguration\} 按以下方式更新 Adapty iOS SDK 激活代码: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Task { try await Adapty.activate(with: configurationBuilder) } } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> ## 将 getViewConfiguration 方法重命名为 getPaywallConfiguration \{#rename-getviewconfiguration-method-to-getpaywallconfiguration\} 更新用于获取付费墙 `viewConfiguration` 的方法名称: ```diff showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { - let paywallConfiguration = try await AdaptyUI.getViewConfiguration( + let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall ) // use loaded configuration } catch { // handle the error } ``` 有关该方法的更多详情,请参阅[获取使用付费墙编辑工具设计的付费墙的视图配置](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 ## 在 SwiftUI 中更新参数 \{#change-parameters-in-swiftui\} 以下是针对 SwiftUI 的更新内容: 1. `didCancelPurchase` 参数已被移除,请改用 `didFinishPurchase`。 2. `.paywall()` 方法不再接受付费墙对象。 3. `paywallConfiguration` 参数已替代 `viewConfiguration` 参数。 请按如下方式更新你的代码: ```diff showLineNumbers @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, - paywall: <paywall object>, - viewConfiguration: <LocalizedViewConfiguration>, + paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, - didFinishPurchase: { product, profile in paywallPresented = false }, + didFinishPurchase: { product, purchaseResult in /* handle the result*/ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } - didCancelPurchase: { product in /* handle the result*/} ) } ``` ## 更新 App Store 促销应用内购买的处理方式 \{#update-handling-of-promotional-in-app-purchases-from-app-store\} 按照以下示例,从 `AdaptyDelegate` 方法中移除 `defermentCompletion` 参数,以更新对 App Store 促销应用内购买的处理方式: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from the 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## 移除 getProductsIntroductoryOfferEligibility 方法 \{#remove-getproductsintroductoryoffereligibility-method\} 在 Adapty iOS SDK 3.3.0 之前,产品对象始终包含优惠信息,无论用户是否符合资格。您需要在使用优惠前手动检查资格。 现在,产品对象仅在用户符合资格时才包含优惠信息。这意味着您不再需要检查资格——如果存在优惠,则用户具有资格。 如果您仍然希望查看不符合资格用户的优惠,请参考 `sk1Product` 和 `sk2Product`。 ## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\} 从 Adapty iOS SDK 3.3.0 开始,我们更新了 `updateAttribution` 方法的公共 API。以前,它接受 `[AnyHashable: Any]` 字典,允许您直接从各种服务传递归因对象。现在,它需要 `[String: any Sendable]`,因此您需要在传递归因对象之前进行转换。 为确保集成能与 Adapty iOS SDK 3.3.0 及更高版本正常工作,请按以下各节所述更新以下集成的 SDK 配置。 ### Adjust 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。 <Tabs groupId="current-os" queryString> <TabItem value="v5" label="Adjust 5.x+" default> ```diff showLineNumbers class AdjustModuleImplementation { - func updateAdjustAttribution() { - Adjust.attribution { attribution in - guard let attributionDictionary = attribution?.dictionary()?.toSendableDict() else { return } - - Adjust.adid { adid in - guard let adid else { return } - - Adapty.updateAttribution(attributionDictionary, source: .adjust, networkUserId: adid) { error in - // handle the error - } - } - } - } + func updateAdjustAdid() { + Adjust.adid { adid in + guard let adid else { return } + + Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) + } + } + + func updateAdjustAttribution() { + Adjust.attribution { attribution in + guard let attribution = attribution?.dictionary() else { + return + } + + Adapty.updateAttribution(attribution, source: "adjust") + } + } } ``` </TabItem> <TabItem value="v4" label="Adjust 4.x" default> ```diff showLineNumbers class YourAdjustDelegateImplementation { // Find your implementation of AdjustDelegate // and update adjustAttributionChanged method: func adjustAttributionChanged(_ attribution: ADJAttribution?) { - if let attribution = attribution?.dictionary()?.toSendableDict() { - Adapty.updateAttribution(attribution, source: .adjust) + if let attribution = attribution?.dictionary() { + Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> </Tabs> ### AirBridge 按以下方式更新您的移动应用代码。完整代码示例请查看 [AirBridge 集成的 SDK 配置](airbridge#connect-your-app-to-airbridge)。 ```diff showLineNumbers import AirBridge - let builder = AdaptyProfileParameters.Builder() - .with(airbridgeDeviceId: AirBridge.deviceUUID()) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "airbridge_device_id", + value: AirBridge.deviceUUID() + ) + } catch { + // handle the error + } ``` ### Amplitude 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。 ```diff showLineNumbers import Amplitude - let builder = AdaptyProfileParameters.Builder() - .with(amplitudeUserId: Amplitude.instance().userId) - .with(amplitudeDeviceId: Amplitude.instance().deviceId) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "amplitude_user_id", + value: Amplitude.instance().userId + ) + try await Adapty.setIntegrationIdentifier( + key: "amplitude_device_id", + value: Amplitude.instance().deviceId + ) + } catch { + // handle the error + } ``` ### AppMetrica 按照以下说明更新您的移动应用代码。完整代码示例请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。 ```diff showLineNumbers import AppMetricaCore - if let deviceID = AppMetrica.deviceID { - let builder = AdaptyProfileParameters.Builder() - .with(appmetricaDeviceId: deviceID) - .with(appmetricaProfileId: "YOUR_ADAPTY_CUSTOMER_USER_ID") - - Adapty.updateProfile(params: builder.build()) - } + if let deviceID = AppMetrica.deviceID { + do { + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceID + ) + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID" + ) + } catch { + // handle the error + } + } ``` ### AppsFlyer 按照以下示例更新移动应用代码。完整代码示例请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。 ```diff showLineNumbers class YourAppsFlyerLibDelegateImplementation { // Find your implementation of AppsFlyerLibDelegate // and update onConversionDataSuccess method: func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { let uid = AppsFlyerLib.shared().getAppsFlyerUID() - Adapty.updateAttribution( - conversionInfo.toSendableDict(), - source: .appsflyer, - networkUserId: uid - ) + Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + Adapty.updateAttribution(conversionInfo, source: "appsflyer") } } ``` ### Branch 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。 ```diff showLineNumbers class YourBranchImplementation { func initializeBranch() { // Pass the attribution you receive from the initializing method of Branch iOS SDK to Adapty. Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data = data?.toSendableDict() { - Adapty.updateAttribution(data, source: .branch) - } + if let data { + Adapty.updateAttribution(data, source: "branch") + } } } } ``` ### Facebook 广告 \{#facebook-ads\} 按照以下说明更新您的移动应用代码。完整的代码示例,请参阅 [Facebook 广告集成的 SDK 配置](facebook-ads#connect-your-app-to-facebook-ads)。 ```diff showLineNumbers import FacebookCore - let builder = AdaptyProfileParameters.Builder() - .with(facebookAnonymousId: AppEvents.shared.anonymousID) - - do { - try Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "facebook_anonymous_id", + value: AppEvents.shared.anonymousID + ) + } catch { + // handle the error + } ``` ### Firebase 和 Google Analytics \{#firebase-and-google-analytics\} 按照以下示例更新你的移动应用代码。完整代码示例请参阅 [Firebase 和 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。 ```diff showLineNumbers import FirebaseCore import FirebaseAnalytics FirebaseApp.configure() - if let appInstanceId = Analytics.appInstanceID() { - let builder = AdaptyProfileParameters.Builder() - .with(firebaseAppInstanceId: appInstanceId) - Adapty.updateProfile(params: builder.build()) { error in - // handle error - } - } + if let appInstanceId = Analytics.appInstanceID() { + do { + try await Adapty.setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId + ) + } catch { + // handle the error + } + } ``` ### Mixpanel 按照以下示例更新您的移动应用代码。完整代码示例请参阅 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。 ```diff showLineNumbers import Mixpanel - let builder = AdaptyProfileParameters.Builder() - .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) - - do { - try await Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "mixpanel_user_id", + value: Mixpanel.mainInstance().distinctId + ) + } catch { + // handle the error + } ``` ### OneSignal 按照以下说明更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。 ```diff showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { - let params = AdaptyProfileParameters.Builder() - .with(oneSignalPlayerId: playerId) - .build() - - Adapty.updateProfile(params:params) { error in - // check error - } + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId + ) + } } } // SubscriptionID (v5+ OneSignal SDK) OneSignal.Notifications.requestPermission({ accepted in - let id = OneSignal.User.pushSubscription.id - - let builder = AdaptyProfileParameters.Builder() - .with(oneSignalSubscriptionId: id) - - Adapty.updateProfile(params: builder.build()) + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_subscription_id", + value: OneSignal.User.pushSubscription.id + ) + } }, fallbackToSettings: true) ``` ### Pushwoosh 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。 ```diff showLineNumbers - let params = AdaptyProfileParameters.Builder() - .with(pushwooshHWID: Pushwoosh.sharedInstance().getHWID()) - .build() - - Adapty.updateProfile(params: params) { error in - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: Pushwoosh.sharedInstance().getHWID() + ) + } catch { + // handle the error + } ``` ## 更新 Observer 模式实现 \{#update-observer-mode-implementation\} 更新付费墙与交易的关联方式。此前,你需要使用 `setVariationId` 方法来指定 `variationId`。现在,你可以在通过新的 `reportTransaction` 方法记录交易时直接传入 `variationId`。详情请参阅[在 Observer 模式下将付费墙与购买交易关联](report-transactions-observer-mode)中的完整代码示例。 :::warning 请务必使用 `reportTransaction` 方法记录交易。跳过此步骤意味着 Adapty 将无法识别该交易、授予访问等级、将其纳入分析统计,或将其发送至各集成渠道。此步骤至关重要! ::: ```diff showLineNumbers - let variationId = paywall.variationId - - // There are two overloads: for StoreKit 1 and StoreKit 2 - Adapty.setVariationId(variationId, forPurchasedTransaction: transaction) { error in - if error == nil { - // successful binding - } - } + do { + // every time when calling transaction.finish() + try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) + } catch { + // handle the error + } ``` --- # File: migration-to-ios-sdk-v3 --- --- title: "将 Adapty iOS SDK 迁移至 v3.0" description: "迁移至 Adapty iOS SDK v3.0,获得更好的性能与全新的变现功能。" --- Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder)支持,这是用于创建付费墙的无代码、用户友好型工具的全新版本。凭借其极高的灵活性和丰富的设计能力,您的付费墙将变得更加高效且更具盈利能力。 :::info 请注意,AdaptyUI 库已被弃用,现已作为 AdaptySDK 的一部分包含在内。 ::: ## 通过 Swift Package Manager 重新安装 Adapty SDK v3.x \{#reinstall-adapty-sdk-v3x-via-swift-package-manager\} 1. 从项目中删除 AdaptyUI SDK 包依赖,后续不再需要它。 2. 虽然你之前已经添加过,但仍需重新添加 Adapty SDK 依赖。在 Xcode 中,打开 **File** -> **Add Package Dependency...**。请注意,不同版本的 Xcode 添加包依赖的方式可能有所不同,如有需要请参考 Xcode 官方文档。 3. 输入仓库 URL `https://github.com/adaptyteam/AdaptySDK-iOS.git` 4. 选择版本,然后点击 **Add package** 按钮。 5. 选择所需的模块: 1. **Adapty** 是必选模块 2. **AdaptyUI** 是可选模块,如果你计划使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),则需要添加此模块。 6. Xcode 会将包依赖添加到你的项目中,之后即可导入使用。在 **Choose Package Products** 窗口中,再次点击 **Add package** 按钮,该包将出现在 **Packages** 列表中。 ## 通过 CocoaPods 重新安装 Adapty SDK v3.x \{#reinstall-adapty-sdk-v3x-via-cocoapods\} 1. 将 Adapty 添加到你的 `Podfile`。按需选择所需模块: 1. **Adapty** 是必须安装的核心模块。 2. **AdaptyUI** 是可选模块,如果你计划使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),则需要安装此模块。 2. ```shell showLineNumbers title="Podfile" pod 'Adapty', '~> 3.2.0' pod 'AdaptyUI', '~> 3.2.0' # optional module needed only for Paywall Builder ``` 3. 运行: ```sh showLineNumbers title="Shell" pod install ``` 此操作会为你的应用创建一个 `.xcworkspace` 文件。后续所有开发工作请使用该文件。 激活 Adapty 和 AdaptyUI SDK 模块。v3.0 之前无需激活 AdaptyUI,记得**添加 AdaptyUI 激活**。参数无需更改,保持原样即可。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```swift title="" showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> --- # End of Documentation _Generated on: 2026-07-24T13:01:53.369Z_ _Successfully processed: 44/44 files_ # KMP - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.371Z Total files: 48 --- # File: kmp-sdk-overview --- --- title: "Kotlin Multiplatform SDK 概览" description: "了解 Adapty Kotlin Multiplatform SDK 及其主要功能。" --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-KMP.svg?style=flat&logo=kotlin)](https://github.com/adaptyteam/AdaptySDK-KMP/releases) 欢迎使用!我们致力于让应用内购变得轻松愉快 🚀 Adapty Kotlin Multiplatform SDK 的设计目标,就是让你从繁琐的应用内购事务中解脱出来,专注于打造优秀的应用。以下是我们帮你处理的事项: - 开箱即用地处理购买、收据验证和订阅管理 - 无需更新应用即可创建和测试付费墙 - 零配置获取详细的购买分析数据——包含同期群、LTV、流失率和漏斗分析 - 跨会话和跨设备实时保持用户订阅状态同步 - 只需一行代码即可将应用与营销归因和分析服务集成 :::note 在深入代码之前,你需要先将 Adapty 与 Google Play Console 集成,并在看板中配置产品。请查看我们的[快速入门指南](quickstart)完成所有配置。 ::: ## 开始使用 \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. 以下是集成指南的内容概览: 1. [安装并配置 SDK](sdk-installation-kotlin-multiplatform):将 SDK 添加为项目依赖并在代码中激活。 2. [通过流程启用购买](kmp-quickstart-paywalls):设置购买流程,让用户能够购买产品。如需自定义 UI,请参阅[手动实现付费墙](kmp-quickstart-manual)。 3. [检查订阅状态](kmp-check-subscription-status):自动检查用户的订阅状态,控制其对付费内容的访问权限。 4. [识别用户(可选)](kmp-quickstart-identify):将用户与其 Adapty 用户画像关联,确保数据在各设备间一致存储。 ### 实际效果演示 \{#see-it-in-action\} 想看看完整的实现效果?我们为你准备好了: - **示例应用**:查看我们的[完整示例](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example),展示了完整的配置流程 - **视频教程**:跟随下方的逐步实现视频一起操作 <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## 核心概念 \{#main-concepts\} 在深入代码之前,先来了解让 Adapty 运转的几个关键概念。 Adapty 方案的精妙之处在于:只有版位是硬编码在应用中的,其余所有内容——产品、付费墙设计、定价和优惠——都可以在 Adapty 看板中灵活管理,无需更新应用: 1. [**产品**](product) - 应用中所有可购买的内容,包括订阅、消耗型商品或永久授权。 2. **流程或付费墙** - 将产品与配置捆绑,挂载到版位上。分为两种形式: - **[流程](adapty-flow-builder)** - 在 Flow Builder 中构建的可视化无代码界面,Adapty 负责渲染 UI 并处理购买。 - **[付费墙](paywalls)** - 无可视化配置;由你在代码中构建 UI 并自行调用 `makePurchase`。请参阅[手动实现付费墙](kmp-quickstart-manual)。 在 SDK 代码中,两者均通过同一个 `getFlow` 方法获取。 3. [**版位**](placements) - 用户旅程中你希望展示流程或付费墙的关键节点。版位解决的是变现策略中"在哪里"和"什么时候"的问题。常见版位包括: - `main` - 主付费墙位置 - `onboarding` - 在用户引导流程中展示 - `settings` - 从应用设置中访问 首次集成时,从 `main` 或 `onboarding` 等基础版位入手,然后再[思考应用中还有哪些地方的用户可能准备好购买](choose-meaningful-placements)。 4. [**用户画像**](profiles-crm) - 当用户购买产品后,其用户画像会被赋予**访问等级**,你可以用它来控制付费功能的访问权限。 --- # File: sdk-installation-kotlin-multiplatform --- --- title: "安装并配置 Adapty Kotlin Multiplatform SDK" description: "为 Kotlin Multiplatform 应用安装并配置 Adapty SDK。" --- Adapty SDK 包含两个核心模块,可无缝集成到您的移动应用中: - **Core Adapty**:这是 Adapty 正常运行所必需的核心 SDK。 - **AdaptyUI** (`io.adapty:adapty-kmp-ui`):如果你使用[付费墙编辑工具](adapty-paywall-builder)并通过 Compose Multiplatform 渲染层(`view.present()`)展示付费墙,则需要此模块。如果你的项目不使用 Compose Multiplatform,可以改用核心模块中的 [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform) 和 [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform)。 :::tip 想看看 Adapty SDK 在移动端应用中集成的真实案例?查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example),它演示了完整的配置流程,包括展示付费墙、发起购买以及其他基本功能。 ::: 如需完整的实现流程演示,还可以观看视频: <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## 要求 \{#requirements\} Adapty Kotlin Multiplatform SDK 兼容 Xcode 16.2 及更高版本。 :::info Adapty 兼容 Google Play Billing Library 最高至 8.x。默认情况下,Adapty 使用 Google Play Billing Library v.7.0.0,但如果您希望强制使用更新版本,可以手动[添加依赖项](https://developer.android.com/google/play/billing/integrate#dependency)。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 通过 Gradle 安装 Adapty SDK \{#install-adapty-sdk-via-gradle\} Android 和 iOS 应用均需通过 Gradle 安装 Adapty SDK。 选择你的依赖配置方式: - 标准 Gradle:将依赖添加到**模块级** `build.gradle` - 如果项目使用 `.gradle.kts` 文件,将依赖添加到**模块级** `build.gradle.kts` - 如果使用版本目录,将依赖添加到 `libs.versions.toml` 文件,然后在 `build.gradle.kts` 中引用 :::important Adapty Kotlin Multiplatform SDK 4.0 目前为预发布版本。Gradle 不会通过动态版本范围(如 `+` 或 `latest.release`)自动选取预发布版本,因此你必须指定确切版本,例如 `io.adapty:adapty-kmp:4.0.0-beta.1`,或在 `libs.versions.toml` 中填写 `adapty-kmp = "4.0.0-beta.1"`。详见 [将 Adapty Kotlin Multiplatform SDK 迁移至 v4](migration-to-kmp-sdk-v4)。 ::: <Tabs> <TabItem value="module-level build.gradle" label="module-level build.gradle" default> ```kotlin showLineNumbers kotlin { sourceSets { commonMain { dependencies { implementation libs.adapty.kmp } } } } ``` </TabItem> <TabItem value="module-level build.gradle.kts" label="module-level build.gradle.kts" default> ```kotlin showLineNumbers kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> <TabItem value="version-catalog" label="版本库" default> ```toml showLineNumbers // libs.versions.toml [versions] .. adapty-kmp = "<the latest SDK version>" [libraries] .. adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } // build.gradle.kts kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> </Tabs> :::note 如果遇到 Maven 相关错误,请确保在 Gradle 脚本中添加了 `mavenCentral()`。 <details> <summary>添加方法说明</summary> 如果您的项目在 `settings.gradle` 中没有 `dependencyResolutionManagement`,请将以下内容添加到顶层 `build.gradle` 的 repositories 末尾: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` 否则,请将以下内容添加到 `settings.gradle` 中 `dependencyResolutionManagement` 部分的 `repositories` 里: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... google() mavenCentral() } } ``` </details> ::: ## 激活 Adapty SDK \{#activate-adapty-sdk\} ### 基本设置 \{#basic-setup\} 尽早添加初始化代码——通常在适用于两个平台的 Kotlin 共享代码中进行。 :::note Adapty SDK 在你的应用中只需激活一次。 ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `activate` 完成。完整调用顺序请参阅 [Kotlin Multiplatform SDK 中的调用顺序](kmp-sdk-call-order)。 ::: 获取 **Public SDK Key** 的步骤: 1. 打开 Adapty 看板,进入 [App settings → General](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(注意:不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为你复制的密钥。 :::info - 请确保在初始化 Adapty 时使用公开 SDK 密钥,私密密钥仅用于[服务端 API](getting-started-with-server-side-api)。 - SDK 密钥对每个应用都是唯一的,如果您有多个应用,请确保选择正确的密钥。 ::: 现在在您的应用中配置付费墙: - 如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请先[激活下方的 AdaptyUI 模块](#activate-adaptyui-module-of-adapty-sdk),然后参考[付费墙编辑工具快速入门](kmp-quickstart-paywalls)。 - 如果您自行构建付费墙 UI,请参阅[自定义付费墙快速入门](kmp-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果你计划激活 **AdaptyUI** 模块以使用 [Adapty 付费墙编辑工具](kmp-present-paywalls),请确保在配置中设置 `.withActivateUI(true)`。 :::info 重要提示 在代码中,必须先激活 Adapty 核心模块,再激活 AdaptyUI。 ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withActivateUI(true) // true for activating the AdaptyUI module .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` ## 配置 Proguard(Android) \{#configure-proguard-android\} 在正式发布应用之前,您可能需要在 Proguard 配置中添加 `-keep class com.adapty.** { *; }`。 ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解运行状态。以下是可用的日志级别: | 级别 | 描述 | | :----------------------- | :---------------------------------------------------------------------------------------- | | `AdaptyLogLevel.ERROR` | 仅记录错误日志。 | | `AdaptyLogLevel.WARN` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息。 | | `AdaptyLogLevel.INFO` | 记录错误、警告及各类信息消息。默认值。 | | `AdaptyLogLevel.VERBOSE` | 记录调试过程中可能有用的额外信息,例如函数调用、API 请求等。 | | `AdaptyLogLevel.DEBUG` | 记录最详细的信息,包括内部调试数据。 | 您可以在配置 Adapty 之前在应用中设置日志级别: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withLogLevel(AdaptyLogLevel.VERBOSE) // recommended for development .build() ``` ### 数据策略 \{#data-policies\} #### 禁用 IP 地址的采集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 即可禁用用户 IP 地址的采集与共享。默认值为 `false`。 使用此参数可以保护用户隐私、遵守地区性数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据采集。 ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` #### 禁用广告 ID 的收集与共享 \{#disable-advertising-id-collection-and-sharing\} 激活 Adapty 模块时,将 `appleIdfaCollectionDisabled`(iOS)或 `googleAdvertisingIdCollectionDisabled`(Android)设置为 true,即可禁用广告标识符的收集。默认值为 false。 使用此参数可遵守 App Store/Play Store 政策,避免触发 App 跟踪透明度提示,或者当您的应用不需要基于广告 ID 的广告归因或分析时使用。 ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleAdvertisingIdCollectionDisabled(true) // Android only .withAppleIdfaCollectionDisabled(true) // iOS only .build() ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} 默认情况下,AdaptyUI 会缓存媒体文件(如图片和视频)以提升性能、减少网络消耗。你可以通过提供自定义配置来调整缓存设置。 使用 `mediaCache` 覆盖默认缓存设置: ```kotlin val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withMediaCacheConfiguration( AdaptyConfig.MediaCacheConfiguration( memoryStorageTotalCostLimit = 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit = Int.MAX_VALUE, diskStorageSizeLimit = 200 * 1024 * 1024 // 200 MB ) ) .build() ``` ### 启用本地访问等级(Android) \{#enable-local-access-levels-android\} 默认情况下,Android 的[本地访问等级](local-access-levels)处于禁用状态。要启用它们,请将 `withLocalAccessLevelAllowed` 设置为 `true`: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleLocalAccessLevelAllowed(true) .build() ``` ### 备份还原时清除数据 \{#clear-data-on-backup-restore\} 当 `withAppleClearDataOnBackup` 设置为 `true` 时,SDK 会检测应用从 iCloud 备份恢复的情况,并删除所有本地存储的 SDK 数据,包括缓存的用户画像信息、产品详情和付费墙。之后 SDK 将以全新状态重新初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录以及 Adapty 服务器上的用户数据不受影响。 ::: ```swift showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withAppleClearDataOnBackup(true) .build() ``` ## 故障排查 \{#troubleshooting\} #### Android 备份规则(自动备份配置) \{#android-backup-rules-auto-backup-configuration\} 部分 SDK(包括 Adapty)会自带 Android 自动备份配置。如果多个 SDK 都定义了备份规则,Android 清单合并工具可能会报错,提示 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup` 相关问题。 常见错误示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note 以下更改应在你的 Android 平台目录(通常位于项目的 `android/` 文件夹)中进行。 ::: 要解决此问题,你需要: - 告知清单合并工具使用应用自身的备份相关属性值。 - 创建备份规则文件,将 Adapty 的规则与其他 SDK 的规则合并。 #### 1. 在清单中添加 `tools` 命名空间 \{#1-add-the-tools-namespace-to-your-manifest\} 在 `AndroidManifest.xml` 文件中,确保根标签 `<manifest>` 包含 tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. 在 `<application>` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在同一个 `AndroidManifest.xml` 文件中,更新 `<application>` 标签,使应用提供最终属性值,并告知清单合并工具替换库中的值: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 Android 项目的 `res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并。Android 根据系统版本使用不同的备份规则格式,同时创建两个文件可确保应用所支持的所有 Android 版本都能正常兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请替换或添加你应用中实际使用的其他 SDK 的规则。 ::: **Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Android 11 及更低版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important 在 Kotlin Multiplatform 项目中,请在生成 APK/AAB 的 Android 应用模块(例如 `androidApp` 或 `app`)中应用以下更改: - Manifest:`androidApp/src/main/AndroidManifest.xml` - 备份规则 XML:`androidApp/src/main/res/xml/` ::: #### 在 Android 上从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app-in-android\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会错误地重建或复用该 Activity,导致购买结果丢失或被视为已取消。 为确保购买流程正常运行,请仅为启动购买流程的 Activity 使用 `standard` 或 `singleTop` 启动模式,避免使用其他模式。 在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` --- # File: kmp-quickstart-paywalls --- --- title: "在 Kotlin Multiplatform SDK 中使用 Flow Builder 启用购买" description: "使用 Adapty Flow Builder 启用应用内购买的快速入门指南。" --- 本指南使用 Adapty Kotlin Multiplatform SDK v4 (beta) API。如果你使用的是 v3,请参阅[迁移指南](migration-to-kmp-sdk-v4)了解对应的方法名称。 要启用应用内购买,你需要了解以下三个核心概念: - [**产品**](product) – 用户可以购买的内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 向用户展示产品的页面序列,通过无代码的 Flow Builder 构建。SDK 通过 `getFlow` 获取流程。如果你更倾向于用自己的代码构建 UI,请使用付费墙代替——参见[手动实现付费墙](kmp-quickstart-manual)。 - [**版位**](placements) – 流程在应用中展示的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 请求。这样可以轻松进行 A/B 测试,向不同用户展示不同的流程。 Adapty 为您提供三种在应用中开启购买功能的方式。请根据您的应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |---|---|---| | Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可立即购买的流程](quickstart-paywalls)。Adapty 自动渲染并在后台处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 您在应用代码中实现付费墙 UI,但仍通过 Adapty 获取流程对象,以保持产品供应的灵活性。请参阅[指南](kmp-quickstart-manual)。 | | 观察者模式 | 🔴 复杂 | 您已有自己的购买处理基础设施并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。请参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤介绍如何实现在 Adapty Flow Builder 中创建的流程。** 如果您希望自行构建付费墙 UI,请参阅[手动实现付费墙](kmp-quickstart-manual)。 ::: 要在应用代码中展示 Adapty Flow Builder 创建的流程,您只需: 1. **获取流程**:从 Adapty 获取流程。 2. **展示流程,Adapty 将自动处理购买**:在应用中显示视图。 3. **处理按钮操作**:将用户交互与应用的响应关联起来。例如,在用户点击按钮时打开链接或关闭流程。 ## 开始之前 \{#before-you-start\} 开始之前,请完成以下步骤: 1. 在 Adapty 看板中将你的应用连接至 [App Store](initial_ios) 和/或 [Google Play](initial-android)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建流程并向其中添加产品](create-paywall)。 4. [创建版位并将流程添加到其中](create-placement)。 5. 在你的应用代码中[安装并激活 Adapty SDK](sdk-installation-kotlin-multiplatform)。 :::tip 完成这些步骤的最快方式是参照[快速入门指南](quickstart),或使用 [Developer CLI](developer-cli-quickstart) 创建流程和版位。 ::: ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。版位允许你为不同的目标受众运行不同的流程,或运行 [A/B 测试](ab-tests)。 要获取在 Adapty 付费墙编辑工具中创建的流程,你需要: 1. 使用 `getFlow` 方法,通过[版位](placements) ID 获取 `flow` 对象。 2. 使用 `createFlowView` 方法创建流程视图。该视图包含显示流程所需的 UI 元素和样式。如果流程没有配置视图,`createFlowView` 将返回错误——请在 `onError` 中处理该错误。 :::important 要获取视图,必须在 Flow Builder 中开启 **Show on device** 开关。否则,`createFlowView` 将返回错误,流程也不会显示。 ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured, or view creation failed } } .onError { error -> // handle the error } ``` ## 2. 展示流程 \{#2-display-the-flow\} 现在,当你已获取到流程后,只需添加几行代码即可展示它。 要在设备屏幕上展示可视化流程,必须先创建视图。为此,请调用 `AdaptyUI.createFlowView()` 方法: ```kotlin showLineNumbers AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` 成功创建视图后,你可以将其呈现在设备屏幕上。每个视图只能使用一次:调用 `dismiss()` 后,需再次调用 `createFlowView` 才能重新显示流程。 :::tip 有关如何展示流程的详细信息,请参阅我们的[指南](kmp-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#3-handle-button-actions\} 当用户点击流程中的按钮时,Kotlin Multiplatform SDK 会自动处理购买、恢复、关闭流程以及打开链接等操作。 但是,其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作。或者,你可能希望覆盖它们的默认行为。 例如,以下是关闭按钮的默认行为。你无需在代码中添加此内容,但在这里你可以看到如果需要时应如何实现。 请注意,默认情况下,流程在购买成功后仍保持打开状态。如果您希望在购买完成后关闭它,请在 `flowViewDidFinishPurchase` 回调中关闭该视图。 :::tip 阅读我们的指南,了解如何处理按钮[操作](kmp-handle-paywall-actions)和[事件](kmp-handling-events)。 ::: ```kotlin showLineNumbers AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) ``` ## 后续步骤 \{#next-steps\} 您的流程已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试购买,确保您可以从流程中完成测试购买。 接下来,您需要[检查用户的访问等级](kmp-check-subscription-status),以确保向正确的用户展示流程或开放付费功能。 ## 完整示例 \{#full-example\} 以下是如何将所有这些步骤整合到您的应用中的完整示例。 ```kotlin showLineNumbers // Set up the observer for handling flow events AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) // Get and display the flow Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured — use custom logic } } .onError { error -> // handle the error } ``` --- # File: kmp-check-subscription-status --- --- title: "在 Kotlin Multiplatform SDK 中检查订阅状态" description: "了解如何使用 Adapty 在 Kotlin Multiplatform 应用中检查订阅状态。" --- 要决定用户是否可以访问付费内容或查看付费墙,您需要检查用户画像中的[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户显示什么内容——是展示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您决定是否向用户显示付费墙或付费内容时,需要检查用户画像中的[访问等级](access-level)。您有两种选择: - 如果需要立即获取最新的用户画像数据(例如应用启动时)或希望强制更新,请调用 `getProfile`。 - 设置**自动用户画像更新**,以保留一份本地副本,该副本会在订阅状态发生变化时自动刷新。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方式是使用 `getProfile` 方法来访问用户画像: ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access } .onError { error -> // handle the error } ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 要在应用中自动接收用户画像更新: 1. 使用 `Adapty.setOnProfileUpdatedListener()` 监听用户画像变化——每当用户订阅状态发生变化时,Adapty 会自动调用此方法。 2. 在此方法被调用时存储更新后的用户画像数据,以便在整个应用中使用,而无需发起额外的网络请求。 ```kotlin showLineNumbers class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true } } ``` :::note Adapty 会在应用启动时自动调用用户画像更新监听器,即使设备处于离线状态,也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当您需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。此方式适用于应用启动、进入付费专区或显示特定内容前等场景。 ```kotlin showLineNumbers private fun checkAccessAndShowPaywall() { // First, check if user has access Adapty.getProfile() .onSuccess { profile -> val hasAccess = profile.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true if (!hasAccess) { // User doesn't have access, show paywall showPaywall() } else { // User has access, show premium content showPremiumContent() } } .onError { error -> // If we can't check access, show paywall as fallback showPaywall() } } private fun showPaywall() { // Get and display paywall using the KMP SDK Adapty.getPaywall("YOUR_PLACEMENT_ID") .onSuccess { paywall -> if (paywall.hasViewConfiguration) { val paywallView = AdaptyUI.createPaywallView(paywall = paywall) paywallView?.present() } else { // Handle remote config paywall or show custom UI handleRemoteConfigPaywall(paywall) } } .onError { error -> // Handle paywall loading error showError("Unable to load paywall") } } private fun showPremiumContent() { // Show your premium content here // This is where you unlock paid features } ``` ## 后续步骤 \{#next-steps\} 现在,当您了解如何追踪订阅状态后,请学习如何[使用用户画像](kmp-quickstart-identify),以确保用户能够访问其已付费的内容。 --- # File: kmp-quickstart-identify --- --- title: "在 Kotlin Multiplatform SDK 中识别用户" description: "在 KMP 中设置 Adapty 进行应用内订阅管理的快速入门指南。" --- :::important 本指南适用于拥有自定义身份验证系统的开发者。在这里,您将了解如何在 Adapty 中管理用户画像,以确保其与您现有的身份验证系统保持一致。 ::: 如何管理用户购买记录取决于您的应用的身份验证模式: - 如果您的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户相关章节](#anonymous-users)。 - 如果您的应用已有(或将有)后端身份验证,请参阅[已识别用户相关章节](#identified-users)。 **核心概念**: - **用户画像** 是 SDK 运行所必需的实体,由 Adapty 自动创建。 - 用户画像可以是匿名的**(不含 customer user ID)**,也可以是已识别的**(含 customer user ID)**。 - 您提供 **customer user ID** 是为了将 Adapty 中的用户画像与您内部的身份认证系统进行关联。 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|---------------------------------------------------|-------------------------------------------------------------------------| | **购买管理** | 通过商店级别恢复购买 | 通过客户用户 ID 跨设备维护购买历史记录 | | **用户画像管理** | 每次重新安装生成新的用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用安装间持久保存 | ## 匿名用户 \{#anonymous-users\} 如果你没有后端身份验证,**无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时被激活,Adapty 会**为该用户创建一个新的用户画像**。 2. 当用户在应用内购买任何内容时,该购买记录会**与其 Adapty 用户画像及其商店账户关联**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建一个新的匿名用户画像**。 4. 如果用户此前已在您的应用中进行过购买,默认情况下,SDK 激活时会自动从 App Store 同步其购买记录。 因此,对于匿名用户,每次安装都会创建新的用户画像,但这并不是问题,因为在 Adapty 分析中,你可以[配置将什么视为新安装](general#4-installs-definition-for-analytics)。 对于匿名用户,你需要按**设备 ID** 统计安装次数。在这种情况下,设备上的每次应用安装(包括重新安装)都会被计为一次安装。 :::note 备份恢复与重新安装的行为不同。默认情况下,当用户从备份恢复时,SDK 会保留缓存数据,不会创建新的用户画像。你可以通过 `withAppleClearDataOnBackup` 设置来配置此行为。[了解更多](sdk-installation-kotlin-multiplatform#clear-data-on-backup-restore)。 ::: ## 已识别用户 \{#identified-users\} 在应用中识别用户有两种方式: - [**登录/注册时:**](#during-loginsignup) 如果用户在应用启动后才登录,请在用户完成身份验证时调用 `identify()`,并传入 customer user ID。 - [**SDK 初始化时:**](#during-the-sdk-activation) 如果应用启动时已有存储的 customer user ID,可在调用 `activate()` 时直接传入。 :::important 默认情况下,当 Adapty 收到来自某个 Customer User ID 的购买记录,而该 ID 当前已与另一个 Customer User ID 关联时,访问等级将被共享,即两个用户画像均拥有付费访问权限。你可以将此设置配置为将付费访问权限从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[相关文档](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 登录/注册时 \{#during-loginsignup\} 如果你需要在应用启动后识别用户(例如,在用户登录或注册之后),可以使用 `identify` 方法来设置其 customer user ID。 - 如果你**之前从未使用过该 customer user ID**,Adapty 会自动将其关联到当前用户画像。 - 如果你**之前已使用该 customer user ID 识别过用户**,Adapty 将切换到与该 customer user ID 关联的用户画像。 :::important 每位用户的 Customer user ID 必须唯一。如果将该参数硬编码为固定值,所有用户将被视为同一人。 ::: 请等待 `identify` 完成(在其 `onSuccess` 回调中)后再调用其他 SDK 方法。并发调用可能会落在匿名用户画像上。详见 [Kotlin Multiplatform SDK 的调用顺序](kmp-sdk-call-order)。 ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // 每位用户唯一 .onSuccess { // 成功识别 } .onError { error -> // 处理错误 } ``` ### 在 SDK 激活期间 \{#during-the-sdk-activation\} 如果在激活 SDK 时已知 customer user ID,可以直接在 `activate` 方法中传入,无需单独调用 `identify`。 如果已知 customer user ID,但在激活之后才设置,则意味着 Adapty 会在激活时先创建一个匿名用户画像,等你调用 `identify` 后才会切换到已有的用户画像。 您可以传入已有的客户用户 ID(之前使用过的),也可以传入新的。如果传入新的,激活时创建的新用户画像将自动关联到该客户用户 ID。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装量是基于设备 ID 统计的。 设备 ID 代表应用在设备上的一次安装实例,仅在重新安装应用后才会重新生成。 它与此次安装是首次还是重复安装无关,也与是否使用了已有的客户用户 ID 无关。 创建用户画像(在 SDK 激活或登出时)、登录,或在不重新安装应用的情况下升级应用,均不会产生额外的安装事件。 如果您希望按唯一用户而非设备来统计安装量,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ### 用户退出登录 \{#log-users-out\} 如果您的应用有用户退出登录的按钮,请使用 `logout` 方法。 :::important 用户退出登录会为该用户创建一个新的匿名用户画像。 ::: ```kotlin showLineNumbers Adapty.logout() .onSuccess { // successful logout } .onError { error -> // handle the error } ``` :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录时购买 \{#allow-purchases-without-login\} 如果你的用户在登录之前和登录之后都可以进行购买,你需要确保他们登录后仍能保留访问权限: 1. 当未登录用户完成购买时,Adapty 会将该购买绑定到其匿名用户画像 ID。 2. 当用户登录账号后,Adapty 会切换到使用其已识别的用户画像。 - 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 会将该 customer user ID 分配给当前用户画像,从而保留所有购买历史记录。 - 如果是已存在的 customer user ID(该 customer user ID 已关联到某个用户画像),则需要在切换用户画像后获取实际的访问等级。你可以在完成身份识别后立即调用 [`getProfile`](kmp-check-subscription-status),也可以[监听用户画像更新](kmp-check-subscription-status),让数据自动同步。 ## 后续步骤 \{#next-steps\} 恭喜!您已经在应用中成功实现了应用内购买逻辑!祝您的应用变现之路一切顺利! 要充分发挥 Adapty 的价值,可以深入了解以下内容: - [**测试**](troubleshooting-test-purchases):确保一切按预期运行 - [**集成**](configuration):只需一行代码,即可与营销归因和分析服务完成集成 - [**设置自定义用户画像属性**](kmp-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,从而针对不同用户发起 A/B 测试或展示不同的付费墙 --- # File: adapty-sdk-integration-skill-kmp --- --- title: "使用 SDK 集成技能将 Adapty 集成到你的 Kotlin Multiplatform 应用中" description: "使用 adapty-sdk-integration 技能,通过 AI 编程工具将 Adapty SDK 端到端集成到你的 Kotlin Multiplatform 应用中。" --- <AdaptySdkIntegrationSkill platform="Kotlin Multiplatform" /> :::important 该技能目前处于测试阶段。如果遇到卡顿或异常行为,请参考[分步集成指南](adapty-cursor-kmp)——它会引导你的 AI 工具逐步完成每个阶段,并提供相应文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-kmp --- --- title: "借助 AI 将 Adapty 集成到 Kotlin Multiplatform 应用" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具,将 Adapty 集成到 Kotlin Multiplatform 应用的分步指南。" --- 本指南将带你逐步完成将 Adapty 集成到 Kotlin Multiplatform 应用的全过程,借助 AI 编程工具——按正确顺序向它提供相应的 Adapty 文档即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始之前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。你可以使用交互式 LLM 技能,或通过看板手动完成。 ### 技能方式(推荐) \{#skill-approach-recommended\} Adapty CLI 技能让你的 LLM 可以直接设置你的应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。你只需要在看板中[连接你的应用商店](integrate-payments)。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在你的 agent 中运行 `/adapty-cli`。它将引导你完成每个步骤——包括何时打开看板来连接你的应用商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你更倾向于手动配置所有内容,以下是编写代码前需要准备的内容。你的 LLM 无法自动获取看板中的数值——你需要自己提供。 1. **连接应用商店**:在 Adapty 看板中,前往 **App settings → General**。如果你的 KMP 应用同时面向两个平台,请同时连接 App Store 和 Google Play。这是购买功能正常运行的必要步骤。 [连接应用商店](integrate-payments) 2. **复制您的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这就是您传入 Adapty 配置构建器的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,前往 **Products** 页面。您无需在代码中直接引用产品 —— Adapty 会通过付费墙来下发产品。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配到版位。在代码中,版位 ID 就是你传入 `Adapty.getPaywall("YOUR_PLACEMENT_ID")` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板中,于 **Products** 页面为每个产品进行配置。在代码中,通过 `profile.accessLevels["premium"]?.isActive` 检查相应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户可根据所购产品访问不同功能(例如 `basic` 方案与 `pro` 方案),请在开始编写代码前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五项之后,就可以开始写代码了。把以下信息告诉你的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",这样它就能生成正确的初始化和付费墙获取代码。 ::: ### 准备好后再设置 \{#set-up-when-ready\} 以下内容不是开始编码的必要条件,但随着集成的成熟,你会需要它们: - **A/B 测试**:在 **Placements** 页面进行配置。无需更改代码。 [A/B 测试](ab-tests) - **额外的付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。 - **分析集成**:在 **Integrations** 页面进行配置。设置方式因集成而异。请参阅[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 向你的 LLM 提供 Adapty 文档 \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) [Context7](https://context7.com) 是一个 MCP 服务器,可让你的 LLM 直接访问最新的 Adapty 文档。LLM 会根据你的提问自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the Kotlin Multiplatform SDK ``` :::warning 即使 Context7 省去了手动粘贴文档链接的步骤,实现顺序仍然很重要。请按照下方的[实现步骤](#implementation-walkthrough)逐步操作,确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 你可以以纯文本 Markdown 格式访问任何 Adapty 文档。在其 URL 末尾添加 `.md`,或点击文章标题下的 **Copy for LLM**。例如:[adapty-cursor-kmp.md](https://adapty.io/docs/zh/adapty-cursor-kmp.md)。 下面[实施流程](#implementation-walkthrough)中的每个阶段都包含一个"发送给你的 LLM"块,其中有可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下面的[索引文件和平台专属子集](#plain-text-doc-index-files)。 ## 实施流程 \{#implementation-walkthrough\} 本指南的其余部分按实施顺序介绍 Adapty 集成。每个阶段包括要发送给 LLM 的文档、完成后应看到的内容以及常见问题。 ### 规划您的集成 \{#plan-your-integration\} 在开始编写代码之前,请让您的 LLM 分析您的项目并制定实现计划。如果您的 AI 工具支持规划模式(例如 Cursor 或 Claude Code 的 plan mode),建议先使用该模式,让 LLM 在编写任何代码之前,同时读取您的项目结构和 Adapty 文档。 告诉您的 LLM 您使用哪种购买方式——这会影响它应遵循的指南: - [**Adapty 付费墙编辑工具**](adapty-paywall-builder):在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染。 - [**手动创建付费墙**](kmp-making-purchases):自行编写付费墙 UI,但仍使用 Adapty 获取产品并处理购买。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅使用 Adapty 进行数据分析和集成。 不确定选哪个?请参阅[快速入门中的对比表](kmp-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 通过 Gradle 添加 Adapty SDK 依赖,并使用你的公共 SDK 密钥激活它。这是一切的基础——没有这一步,其他功能都无法正常使用。 **指南:** [安装并配置 Adapty SDK](sdk-installation-kotlin-multiplatform) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-kotlin-multiplatform.md ``` :::tip[Checkpoint] - **预期结果:** 应用成功构建并运行。Logcat(Android)或 Xcode 控制台(iOS)显示 Adapty 激活日志。 - **常见问题:** 出现 "Public API key is missing" 错误 → 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 展示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙、展示付费墙并处理购买事件。你需要参考哪些指南,取决于你处理购买的方式。 建议边开发边在沙盒中测试每笔购买,不要等到最后再测。沙盒配置说明请参阅[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **指南:** - [通过付费墙启用购买(快速入门)](kmp-quickstart-paywalls) - [获取付费墙编辑工具付费墙及其配置](kmp-get-pb-paywalls) - [展示付费墙](kmp-present-paywalls) - [处理付费墙事件](kmp-handling-events) - [响应按钮操作](kmp-handle-paywall-actions) 请将以下内容发送给您的 LLM: ``` 在编写代码之前,请阅读以下 Adapty 文档: - https://adapty.io/docs/zh/kmp-quickstart-paywalls.md - https://adapty.io/docs/zh/kmp-get-pb-paywalls.md - https://adapty.io/docs/zh/kmp-present-paywalls.md - https://adapty.io/docs/zh/kmp-handling-events.md - https://adapty.io/docs/zh/kmp-handle-paywall-actions.md ``` :::tip[Checkpoint] - **预期结果:** 付费墙正常显示,并包含你配置的产品。点击某个产品会触发沙盒购买弹窗。 - **注意事项:** 付费墙为空或 `getPaywall` 报错 → 请确认版位 ID 与看板中完全一致,且该版位已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="Manual paywalls"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](kmp-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-kmp) - [渲染通过远程配置设计的付费墙](present-remote-config-paywalls-kmp) - [进行购买](kmp-making-purchases) - [恢复购买](kmp-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/zh/kmp-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-kmp.md - https://adapty.io/docs/zh/present-remote-config-paywalls-kmp.md - https://adapty.io/docs/zh/kmp-making-purchases.md - https://adapty.io/docs/zh/kmp-restore-purchase.md :::tip[检查点] - **预期效果:** 自定义付费墙正常显示从 Adapty 获取的产品。点击产品后触发沙盒购买弹窗。 - **常见问题:** 产品数组为空 → 请确认付费墙在看板中已分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **指南:** - [Observer 模式概述](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-kmp) - [在 Observer 模式下上报交易](report-transactions-observer-mode-kmp) 将以下内容发送给您的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode-kmp.md - https://adapty.io/docs/zh/report-transactions-observer-mode-kmp.md ``` :::tip[检查点] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易应出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认您已向 Adapty 上报交易,并且两个应用商店的服务端通知均已配置。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买后,检查用户画像中的活跃访问等级,以限制高级内容的访问。 **指南:** [检查订阅状态](kmp-check-subscription-status) 发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/kmp-check-subscription-status.md ``` :::tip[检查点] - **预期结果:** 沙盒购买后,`profile.accessLevels["premium"]?.isActive` 返回 `true`。 - **注意事项:** 购买后 `accessLevels` 为空 → 检查看板中该产品是否已分配访问等级。 ::: ### 关联用户 \{#identify-users\} 将应用的用户账户与 Adapty 用户画像绑定,让购买记录可以跨设备同步。 :::important 如果你的应用无需身份验证,请跳过此步骤。 ::: **指南:** [关联用户](kmp-quickstart-identify) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/kmp-quickstart-identify.md ``` :::tip[检查点] - **预期结果:** 调用 `Adapty.identify("your-user-id")` 后,看板的 **Profiles** 页面应显示你的自定义用户 ID。 - **注意事项:** 请在激活之后、获取付费墙之前调用 `identify`,以避免用户画像归因到匿名用户。 ::: ### 准备发布 \{#prepare-for-release\} 沙盒环境中的集成测试通过后,请完成发布检查清单,确保一切已就绪。 **指南:**[发布检查清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[Checkpoint] - **预期结果:** 所有清单项目均已确认:商店连接、服务器通知、购买流程、访问等级检查以及隐私要求。 - **注意事项:** 若服务器通知缺失,请在 **App settings → iOS SDK** 中配置 App Store 服务器通知,并在 **App settings → Android SDK** 中配置 Google Play 实时开发者通知。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单个页面范围的更广泛上下文,我们提供了列出或汇总所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面的 `.md` 链接。这是一项[新兴标准](https://llmstxt.org/),旨在让 LLM 能够访问网站内容。请注意,对于某些 AI 智能体(如 ChatGPT),你需要下载 `llms.txt` 并作为文件上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站合并为单个文件。体积较大——仅在需要完整内容时使用。 - Kotlin Multiplatform 专属的 [`kmp-llms.txt`](https://adapty.io/docs/zh/kmp-llms.txt) 和 [`kmp-llms-full.txt`](https://adapty.io/docs/zh/kmp-llms-full.txt):特定平台的子集,相比完整站点可节省 token 用量。 --- # File: kmp-paywalls --- --- title: "流程与付费墙 - Kotlin Multiplatform" description: "在 Kotlin Multiplatform 应用中展示和管理通过 Adapty Flow Builder 或付费墙编辑工具构建的流程与付费墙。" --- ## 展示付费墙 \{#display-paywalls\} ### Adapty Flow Builder 与付费墙编辑工具 \{#adapty-flow-builder--paywall-builder\} <CustomDocCardList ids={['kmp-get-pb-paywalls', 'kmp-present-paywalls', 'kmp-handling-events', 'kmp-handle-paywall-actions']} /> :::tip 若要快速上手 Adapty 付费墙编辑工具,请参阅我们的[快速入门指南](kmp-quickstart-paywalls)。 ::: ### 手动实现付费墙 \{#implement-paywalls-manually\} <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases']} /> 关于手动实现付费墙和处理购买的更多指南,请参阅[此分类](kmp-implement-paywalls-manually)。 ## 实用功能 \{#useful-features\} <CustomDocCardList ids={['kmp-use-fallback-paywalls', 'kmp-web-paywalls']} /> --- # File: kmp-get-pb-paywalls --- --- title: "获取流程与付费墙 - Kotlin Multiplatform" description: "在 Kotlin Multiplatform 应用中从 Adapty 获取流程和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" /> 在[设计好您的流程或付费墙编辑工具付费墙](adapty-paywall-builder)后,您可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体操作如下所述。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在您的移动应用中开始展示流程之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程/付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程/付费墙添加到其中](create-placement)。 4. 在您的移动应用中安装 [Adapty SDK](sdk-installation-kotlin-multiplatform)。 </details> ## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已使用流程编辑工具或付费墙编辑工具设计了流程或付费墙,则无需在移动端代码中手动处理其渲染逻辑来向用户展示。此类流程或付费墙本身已包含展示内容与展示方式的全部配置。不过,你仍需通过版位获取其 ID 及视图配置,然后在移动端应用中将其呈现出来。 为确保最佳性能,请尽早获取流程或付费墙及其[视图配置](kmp-get-pb-paywalls#fetch-the-view-configuration),以便在向用户展示之前有足够的时间下载图片。 使用 `getFlow` 方法获取流程或付费墙: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // the requested flow/paywall }.onError { error -> // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果你认为用户的网络连接不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存存在时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速获取,并在 CDN 不可用时提供独立的备用服务器。这套系统旨在确保你始终获取最新版本,同时在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。达到超时后,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。</p><p>对于 Kotlin Multiplatform:你可以使用扩展函数创建 `Duration`,例如 `5.seconds`,其中 `.seconds` 来自 `kotlin.time.Duration.Companion.seconds`。</p> | 响应参数: | 参数 | 描述 | | :-------- | :---------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`instanceIdentity`、`variationId`)、名称、付费墙变体(`paywalls`——`AdaptyFlowPaywall` 列表)以及远程配置(`remoteConfigs`——每个语言区域对应一条记录)。如需预加载产品、自定义 UI 或以编程方式检查,请调用 `getPaywallProducts(flow)`。 | ## 获取视图配置 \{#fetch-the-view-configuration\} 获取流程或付费墙后,使用 `createFlowView` 方法一步完成视图配置的加载和视图的创建。无需单独检查任何标志:如果该版位是在 **Flow Builder**(流程)或 **Paywall Builder**(付费墙)中设计的,`createFlowView` 将返回已准备好展示的视图。如果该版位是没有编辑工具界面的自定义付费墙,`createFlowView` 将返回 `AdaptyResult.Error`——[将其作为远程配置付费墙处理](present-remote-config-paywalls-kmp)。 :::important 请确保在 Flow Builder 中启用 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: ```kotlin showLineNumbers AdaptyUI.createFlowView( flow = flow, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { view -> // use view }.onError { error -> // the flow has no view configured, or view creation failed } ``` | 参数 | 是否必填 | 描述 | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | | **loadTimeout** | 选填 | 此值限制该方法的超时时间。若超时,将返回缓存数据或本地备用数据。注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作底层可能包含多个请求。可使用 `kotlin.time.Duration.Companion` 中的扩展函数,例如 `5.seconds`。 | | **preloadProducts** | 选填 | 设为 `true` 可预加载产品以提升性能。启用后,产品将提前加载,从而减少显示流程或付费墙所需的时间。 | | **productPurchaseParams** | 选填 | [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) 到 [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/) 的映射。用于为流程或付费墙中的各个产品配置特定的购买参数,例如个性化优惠或订阅更新参数。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder)。 ::: 加载完成后,[展示流程或付费墙](kmp-present-paywalls)。 ## 为默认目标受众获取流程或付费墙以加快加载速度 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,流程和付费墙几乎可以即时获取,无需担心速度问题。但如果你的目标受众和版位数量较多,且用户的网络连接较差,获取流程或付费墙可能会比预期慢。在这种情况下,你可能希望显示默认的流程或付费墙,以确保流畅的用户体验,而不是什么都不展示。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程或付费墙。但需要特别注意的是,推荐的做法是通过 `getFlow` 方法来获取流程或付费墙,详情请参见上方的[获取流程/付费墙](#fetch-flowpaywall)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺点: - **潜在的向后兼容性问题**:如果你需要针对不同的应用版本(当前版本和未来版本)展示不同的流程,可能会遇到挑战。你要么设计出兼容当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能遇到流程无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些缺点以换取更快的流程或付费墙加载速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。 ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它可确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存数据存在时优先返回缓存。这种情况下,用户获取到的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,仅在应用重新安装或手动清理时才会被清除。</p> | ## 自定义资源 \{#customize-assets\} 要自定义流程或付费墙中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你通过这些 ID 来定位相应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 向部分用户显示不同的图片或视频。 - 在远程主图加载时,先显示本地预览图。 - 在播放视频前,先显示预览图。 以下是如何通过 map 提供自定义资源的示例: :::info Kotlin Multiplatform SDK 仅支持本地资源。如需使用远程内容,请在将其用于自定义资源之前,先将其下载并缓存到本地。 ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating the flow view AdaptyUI.createFlowView( flow = flow, customAssets = customAssets ).onSuccess { view -> // Present the flow with custom assets view.present() }.onError { error -> // Handle the error - the flow will fall back to default appearance } } ``` :::note 如果某个资源未找到或加载失败,流程或付费墙将回退至在编辑工具中配置的默认外观。 ::: </SDKv4> <SDKv3> 在 [Adapty 看板中使用新版付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,即可在移动应用中展示该付费墙。第一步是获取与版位关联的付费墙及其视图配置,具体如下所述。 请注意,本主题涉及使用付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中为远程配置付费墙获取付费墙和产品](fetch-paywalls-and-products-kmp)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中展示付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-kotlin-multiplatform)。 </details> ## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果您已[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动应用代码中处理其渲染逻辑即可向用户展示。此类付费墙同时包含展示内容和展示方式。但您仍需通过版位获取其 ID、视图配置,然后在移动应用中呈现它。 为确保最佳性能,请务必尽早获取付费墙及其[视图配置](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够时间下载图片。 使用 `getPaywall` 方法获取付费墙: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` 参数说明: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。此参数应为由一个或两个子标签组成的语言代码,子标签之间以减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关区域代码及使用建议,请参阅[本地化与区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何都能获得更快的加载速度。缓存会定期更新,因此在会话期间使用它以避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后保持不变,仅在卸载重装应用或手动清理时才会清除。</p><p></p><p>Adapty SDK 在本地以两层存储付费墙:上述定期更新的缓存和[备用付费墙](fallback-paywalls)。我们还使用 CDN 更快地获取付费墙,以及在 CDN 不可达时使用独立的备用服务器。此系统旨在确保您始终获取最新版本的付费墙,同时在网络连接不佳的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>此值限制该方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个不同请求组成。</p><p>对于 Kotlin Multiplatform:您可以使用扩展函数(如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`)或 `TimeInterval.seconds(5)` 创建 `TimeInterval`。若不设置限制,请使用 `TimeInterval.INFINITE`。</p> | 响应参数: | 参数 | 描述 | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | 一个 [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙的视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中开启 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 获取付费墙后,检查其是否包含 `ViewConfiguration`,这表示该付费墙是使用付费墙编辑工具创建的。这将指导您如何展示该付费墙。如果存在 `ViewConfiguration`,则将其视为付费墙编辑工具付费墙;否则,[将其作为远程配置付费墙处理](present-remote-config-paywalls-kmp)。 使用 `createPaywallView` 方法加载视图配置。 ```kotlin showLineNumbers if (paywall.hasViewConfiguration) { AdaptyUI.createPaywallView( paywall = paywall, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { paywallView -> // use paywallView }.onError { error -> // handle the error } } else { // use your custom logic } ``` | 参数 | 是否必填 | 描述 | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 | | **loadTimeout** | 可选 | 此值限制该方法的超时时间。如果达到超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,此方法的超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能由多个不同请求组成。您可以使用 `kotlin.time.Duration.Companion` 中的扩展函数,如 `5.seconds`。 | | **preloadProducts** | 可选 | 设置为 `true` 以预加载产品从而提升性能。启用后,产品将提前加载,减少展示付费墙所需的时间。 | | **productPurchaseParams** | 可选 | 从 [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) 到 [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/) 的映射。使用此参数为付费墙中的各个产品配置特定的购买参数,例如个性化优惠或订阅更新参数。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder)。 ::: 加载完成后,[展示付费墙](kmp-present-paywalls)。 ## 为默认目标受众获取付费墙以加快获取速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙的获取几乎是即时完成的,您无需担心加速此过程。但是,如果您拥有大量目标受众和付费墙,且用户的网络连接较弱,付费墙的获取时间可能比预期更长。在这种情况下,您可能希望展示默认付费墙以确保流畅的用户体验,而不是完全不展示付费墙。 为解决这一问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的付费墙。但请务必了解,推荐的方式是使用 `getPaywall` 方法获取付费墙,详见上方的[获取付费墙信息](#fetch-paywall-designed-with-paywall-builder)部分。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在一些显著缺点: - **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)的付费墙,要么接受使用当前(旧版)的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为**所有用户**目标受众设计的相同付费墙,这意味着您将失去个性化定向能力(包括基于国家、营销归因或您自己的自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请坚持使用[上方](#fetch-paywall-designed-with-paywall-builder)介绍的 `getPaywall`。 ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。此参数应为由一个或多个子标签组成的语言代码,子标签之间以减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关区域代码及使用建议,请参阅[本地化与区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何都能获得更快的加载速度。缓存会定期更新,因此在会话期间使用它以避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后保持不变,仅在卸载重装应用或手动清理时才会清除。</p> | ## 自定义素材 \{#customize-assets\} 要自定义付费墙中的图片和视频,请实现自定义素材。 主图和视频具有预定义的 ID:`hero_image` 和 `hero_video`。在自定义素材包中,您通过这些 ID 定位相应元素并自定义其行为。 对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,您可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载时展示本地预览图。 - 在播放视频前展示预览图。 :::important 要使用此功能,请将 Adapty SDK 更新至 3.7.0 或更高版本。 ::: 以下是通过映射提供自定义素材的示例: :::info Kotlin Multiplatform SDK 仅支持本地素材。对于远程内容,您应在使用自定义素材之前先将其下载并缓存到本地。 ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating paywall view AdaptyUI.createPaywallView( paywall = paywall, customAssets = customAssets ).onSuccess { paywallView -> // Present the paywall with custom assets paywallView.present() }.onError { error -> // Handle the error - paywall will fall back to default appearance } } ``` :::note 如果某个资源未找到或加载失败,付费墙将回退到在付费墙编辑工具中配置的默认外观。 ::: </SDKv3> --- # File: kmp-present-paywalls --- --- title: "展示流程与付费墙 - Kotlin Multiplatform" description: "在 Kotlin Multiplatform 应用中向用户展示流程与付费墙。" --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> 如果你已创建了流程或付费墙,无需在移动应用代码中手动处理其渲染逻辑来向用户展示。流程或付费墙本身已包含展示内容和展示方式的完整定义。 :::warning 本指南适用于由 Adapty 渲染的流程和**新付费墙编辑工具付费墙**。远程配置付费墙和 [Observer 模式](observer-vs-full-mode)的处理方式有所不同。 - 有关呈现**远程配置付费墙**,请参阅[呈现远程配置设计的付费墙](present-remote-config-paywalls-kmp)。 - 有关在**Observer 模式**下呈现流程,请参阅[在 Observer 模式下呈现流程](kmp-present-flows-in-observer-mode)。 ::: 要获取下面使用的 `flow` 对象,请参阅[获取流程与付费墙](kmp-get-pb-paywalls)。 Adapty Kotlin Multiplatform SDK 提供两种呈现流程和付费墙的方式: - **使用 Compose Multiplatform** - **不使用 Compose Multiplatform** ## 使用 Compose Multiplatform \{#with-compose-multiplatform\} 要展示流程或付费墙,请在通过 [`createFlowView`](kmp-get-pb-paywalls#fetch-the-view-configuration) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示流程,请重新调用 `createFlowView` 创建一个新的 `view` 实例。 :::warning 重复使用同一个 `view` 而不重新创建,可能会导致报错。 ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createFlowView(flow = flow).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### 显示对话框 \{#show-dialog\} 在 Android 上展示流程或付费墙时,请使用此方法替代原生 alert 对话框。在 Android 上,普通的 alert 会显示在流程视图的后面,导致用户看不到。此方法可确保对话框在所有平台上都能正确显示在流程上方。 ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close this screen?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the flow view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传入 `iosPresentationStyle` 参数,可以配置流程或付费墙在 iOS 上的展示方式。该参数接受 `AdaptyUIIOSPresentationStyle.FULLSCREEN`(默认值)或 `AdaptyUIIOSPresentationStyle.PAGESHEET` 两个值。 ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createFlowView(flow = flow).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## 不使用 Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeFlowView` 是核心模块 `io.adapty:adapty-kmp` 的一部分。如果你的项目不使用 Compose Multiplatform,则无需添加 `io.adapty:adapty-kmp-ui` 依赖。 ::: 如需在不使用 Compose Multiplatform 的情况下嵌入流程或付费墙,请调用 `createNativeFlowView`。它会返回一个 `AdaptyNativeFlowView`,你可以将其添加到布局中: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` 默认情况下,嵌入式视图不会应用安全区域内边距——您的布局需要自行处理插边。如果希望视图自行应用安全区域内边距,请在 `createNativeFlowView` 中传入 `androidEnableSafeArea = true`。该参数仅适用于 Android。 </TabItem> <TabItem value="ios" label="iOS"> 由于 KMP 接口的默认方法在 Swift 中会变为 `@required`,您无法直接在 Swift 中实现 `AdaptyUIFlowsEventsObserver`。请先在 `iosMain` 中声明一个开放的基类: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseFlowObserver : AdaptyUIFlowsEventsObserver ``` 然后在 Swift 中对其进行子类化,只重写所需的方法: ```swift showLineNumbers title="Swift" class MyFlowObserver: BaseFlowObserver { override func flowViewDidPerformAction(view: AdaptyUIFlowView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativeFlowView( flow: flow, observer: MyFlowObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### 销毁视图 \{#dispose-the-view\} 从布局中移除视图时,请调用 `dispose()`。这会注销事件监听器并释放内部资源。 ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## 自定义标签 \{#custom-tags\} 自定义标签让你无需为不同场景创建单独的流程或付费墙。想象一个可以根据用户数据动态调整的流程:不再是千篇一律的"你好!",而是亲切地问候"你好,John!"或"你好,Ann!" 以下是自定义标签的一些使用场景: - 在流程或付费墙上显示用户的姓名或邮箱。 - 展示当天是星期几以促进销售(例如"周四快乐")。 - 为销售的产品添加个性化信息(如健身计划名称,或 VoIP 应用中的电话号码)。 自定义标签可以帮助你创建灵活的流程,适应各种场景,让应用界面更加个性化、更具吸引力。 :::warning 在某些情况下,应用可能不知道如何替换某个自定义标签——尤其是当用户使用的是旧版 AdaptyUI SDK 时。为了防止这种情况,请始终添加备用文本,以替换包含未知自定义标签的行。否则,用户可能会看到标签以代码形式显示(`<USERNAME/>`)。 ::: 要在流程或付费墙中使用自定义标签,请在创建流程视图时传入这些标签: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createFlowView( flow = flow, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## 自定义计时器 \{#custom-timers\} 计时器是推广限时特价和季节性优惠的绝佳工具。但请注意,该计时器与优惠的有效期或活动持续时间无关。它只是一个独立的倒计时,从你设置的值开始递减至零。当计时器归零后,什么都不会发生——它只会停在零。 你可以自定义计时器前后的文字,以呈现所需的信息,例如:"优惠将在:10:00 秒后结束。" 要在流程或付费墙中使用自定义计时器,请在创建流程视图时传入相应参数: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createFlowView( flow = flow, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> 如果您已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中额外处理渲染逻辑即可将其展示给用户。此类付费墙已包含展示内容及展示方式的完整配置。 :::warning 本指南仅适用于**新版付费墙编辑工具**构建的付费墙。使用远程配置付费墙和 [Observer 模式](observer-vs-full-mode)设计的付费墙,其展示流程有所不同。 如需展示**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls-kmp)。 ::: Adapty Kotlin Multiplatform SDK 提供了两种展示付费墙的方式: - **使用 Compose Multiplatform** - **不使用 Compose Multiplatform** ## 使用 Compose Multiplatform \{#with-compose-multiplatform\} 要显示付费墙,请在由 [`createPaywallView`](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示付费墙,请重新调用 `createPaywallView` 创建一个新的 `view` 实例。 :::warning 重复使用同一个 `view` 而不重新创建,可能会导致错误。 ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createPaywallView(paywall = paywall).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### 显示对话框 \{#show-dialog\} 在 Android 上展示付费墙视图时,请使用此方法代替原生的警告对话框。在 Android 上,普通的警告框会显示在付费墙视图的后面,导致用户看不到它们。此方法可确保对话框在所有平台上都能正确显示在付费墙上方。 ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close paywall?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the paywall view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置付费墙在 iOS 上的呈现方式。该参数接受 `AdaptyUIIOSPresentationStyle.FULLSCREEN`(默认值)或 `AdaptyUIIOSPresentationStyle.PAGESHEET`。 ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createPaywallView(paywall = paywall).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## 不使用 Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativePaywallView` 是核心模块 `io.adapty:adapty-kmp` 的一部分。如果你的项目不使用 Compose Multiplatform,则无需添加 `io.adapty:adapty-kmp-ui` 依赖。 ::: 若要在不使用 Compose Multiplatform 的情况下嵌入付费墙,请调用 `createNativePaywallView`。它会返回一个 `AdaptyNativePaywallView`,你可以将其添加到布局中: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> 由于 KMP 接口的默认方法在 Swift 中会变为 `@required`,因此无法直接从 Swift 实现 `AdaptyUIPaywallsEventsObserver`。请先在 `iosMain` 中声明一个 open 基类: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BasePaywallObserver : AdaptyUIPaywallsEventsObserver ``` 然后在 Swift 中创建其子类,只覆盖你需要的方法: ```swift showLineNumbers title="Swift" class MyPaywallObserver: BasePaywallObserver { override func paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativePaywallView( paywall: paywall, observer: MyPaywallObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### 销毁视图 \{#dispose-the-view\} 从布局中移除视图时,请调用 `dispose()`。这将注销事件监听器并释放内部资源。 ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## 自定义标签 \{#custom-tags\} 自定义标签让你无需为不同场景创建多个付费墙,只需一个付费墙即可根据用户数据动态调整内容。例如,与其显示通用的"你好!",不如用"你好,John!"或"你好,Ann!"来个性化问候用户。 以下是自定义标签的一些使用场景: - 在付费墙上展示用户的姓名或邮箱。 - 显示当前是星期几以促进销售(例如,"愉快的星期四")。 - 为你销售的产品添加个性化详情(如健身计划的名称,或 VoIP 应用中的电话号码)。 自定义标签可帮助你创建灵活的付费墙,使其能够适应各种场景,让应用界面更加个性化、更具吸引力。 :::warning 在某些情况下,应用可能无法识别某个自定义标签应替换成什么内容——尤其是当用户使用的是较旧版本的 AdaptyUI SDK 时。为避免这种情况,请务必为包含未知自定义标签的文本行添加备用文本。否则,用户可能会看到标签以代码形式显示(`<USERNAME/>`)。 ::: 要在付费墙中使用自定义标签,请在创建付费墙视图时传入这些标签: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createPaywallView( paywall = paywall, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## 自定义计时器 \{#custom-timers\} 付费墙计时器是推广限时特惠和季节性活动的利器。但需要注意的是,这个计时器与优惠的有效期或活动的持续时间无关。它只是一个独立的倒计时,从你设定的值开始递减至零。计时器归零后不会触发任何操作——它只会停在零。 你可以自定义计时器前后的文字,以呈现所需的提示信息,例如:"优惠剩余时间:10:00 秒。" 要在付费墙中使用自定义计时器,请在创建付费墙视图时传入相应参数: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createPaywallView( paywall = paywall, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv3> --- # File: kmp-handle-paywall-actions --- --- title: "响应流程操作 - Kotlin Multiplatform" description: "在你的 Kotlin Multiplatform 应用中处理流程和付费墙的按钮操作。" --- <SDKv4> 如果你使用 Adapty Flow Builder 或付费墙编辑工具构建流程或付费墙,正确设置按钮至关重要: 1. 在[编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中为每个已分配的操作编写处理逻辑。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **只有购买、恢复、关闭流程/付费墙以及打开链接会被自动处理。** 其他所有按钮操作(如自定义操作)均需在应用代码中实现相应的处理逻辑。 ::: ## 设置 AdaptyUIFlowsEventsObserver \{#set-up-the-adaptyuiflowsevents-observer\} 要处理流程操作,您需要实现 `AdaptyUIFlowsEventsObserver` 接口,并通过 `AdaptyUI.setFlowsEventsObserver()` 进行设置。该操作应在应用生命周期的早期完成,通常在主 Activity 或应用初始化阶段。 ```kotlin // In your app initialization AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` 所有按钮操作都会作为 `AdaptyUIAction` 密封类(包含 `CloseAction`、`AndroidSystemBackAction`、`OpenUrlAction` 或 `CustomAction`)通过 `flowViewDidPerformAction(view, action)` 回调传入。 :::warning 覆盖 `flowViewDidPerformAction` 会替换**所有**操作的默认处理逻辑,而不仅仅是你感兴趣的那一个。除非你有意修改,否则请保留 `CloseAction`(关闭流程)和 `OpenUrlAction`(打开 URL)的默认处理分支,如下方示例所示。 ::: ## 关闭流程和付费墙 \{#close-flows-and-paywalls\} 要添加一个关闭流程或付费墙的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,实现一个 `close` 操作的处理程序,用于关闭流程。 :::info 在 Kotlin Multiplatform SDK 中,`CloseAction` 默认会触发关闭流程或付费墙。但如果需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。 ::: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` 如果您使用的是 [`createNativeFlowView`](kmp-present-paywalls#without-compose-multiplatform),调用 `view.dismiss()` 不会有任何效果——该视图是嵌入在您的布局中的,而非通过 KMP 堆栈呈现的。请将该视图从布局中移除,并对其调用 `dispose()`。 ## 处理 Android 系统返回按钮 \{#handle-the-android-system-back-button\} 按下 Android 系统返回按钮(或使用返回手势)会触发 `AdaptyUIAction.AndroidSystemBackAction`。默认情况下,此操作会被忽略——流程保持打开状态,用户通过你定义的路径(例如 **Close** 按钮或编辑工具中的 `on_device_back` 操作)退出流程。如果你希望系统返回按钮能够关闭流程,请自行处理该操作: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } ``` ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加一个 **Link** 元素,并以与 **Open URL** 操作按钮相同的方式处理它。 ::: 要在流程或付费墙中添加一个打开链接的按钮(例如**使用条款**或**隐私政策**),请在编辑工具中添加一个按钮,为其分配 **Open URL** 操作,然后输入你想打开的 URL。 默认情况下,SDK 会原生打开接收到的 URL——使用外部浏览器还是应用内浏览器取决于 `action.openIn`——无需编写任何代码。仅当需要自定义逻辑时(例如先显示确认对话框),才需要覆盖该处理程序: ```kotlin class MyAdaptyUIFlowsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } else -> Unit } } } // Set up the observer with UriHandler AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver(uriHandler)) ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在编辑工具中,添加一个按钮,并为其分配一个 ID 为 "login" 的**自定义**操作。 2. 在应用代码中,实现一个自定义操作的处理器,用于识别用户身份。 ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } else -> Unit } } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在编辑工具中,添加一个按钮,为其指定 **Custom** 操作,并为其分配一个 ID。 2. 在应用代码中,为您创建的操作 ID 实现处理程序。 例如,如果您有另一组订阅优惠或一次性购买,可以添加一个按钮,用于显示另一个流程或付费墙: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "openNewFlow" -> { // Display another flow or paywall } } } else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` </SDKv4> <SDKv3> :::warning **只有购买和恢复操作会被自动处理。** 所有其他按钮操作,例如关闭付费墙或打开链接,都需要在应用代码中实现相应的响应逻辑。 ::: 如果你正在使用 Adapty 付费墙编辑工具构建付费墙,正确设置按钮至关重要: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有的操作或创建自定义操作 ID。 2. 在应用中编写代码,处理你已分配的每个操作。 本指南介绍如何在代码中处理自定义操作和预置操作。 ## 设置 AdaptyUIPaywallsEventsObserver \{#set-up-the-adaptyuipaywallseventsobserver\} 要处理付费墙操作,您需要实现 `AdaptyUIPaywallsEventsObserver` 接口,并通过 `AdaptyUI.setPaywallsEventsObserver()` 进行配置。这应在应用生命周期的早期完成,通常在主 Activity 或应用初始化时进行。 ```kotlin // In your app initialization AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## 关闭付费墙 \{#close-paywalls\} 要添加一个关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在您的应用代码中,实现一个处理 `close` 操作的处理程序,用于关闭付费墙。 :::info 在 Kotlin Multiplatform SDK 中,`CloseAction` 和 `AndroidSystemBackAction` 默认会触发关闭付费墙。但如果需要,您可以在代码中覆盖此行为。例如,关闭一个付费墙可能会触发打开另一个付费墙。 ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss() } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` 如果您正在使用 [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform),调用 `view.dismiss()` 将不会产生任何效果——该视图是嵌入在您的布局中的,而非通过 KMP 栈呈现的。请从您的布局中移除该视图,并对其调用 `dispose()`。 ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果您想添加一组链接(例如使用条款和购买恢复),请在付费墙编辑工具中添加一个 **Link** 元素,并像处理带有 **Open URL** 操作的按钮一样对其进行处理。 ::: 要添加一个从付费墙打开链接的按钮(例如 **Terms of use** 或 **Privacy policy**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入您想要打开的 URL。 2. 在您的应用代码中,实现 `openUrl` 操作的处理程序,以在浏览器中打开接收到的 URL。 :::info 在 Kotlin Multiplatform SDK 中,`OpenUrlAction` 提供了需要打开的 URL。您可以实现自定义逻辑来处理 URL 的打开方式,例如显示确认对话框或使用应用程序首选的 URL 处理方法。 ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } } } } // Set up the observer with UriHandler AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver(uriHandler)) ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配一个 ID 为 "login" 的 **Custom** 动作。 2. 在应用代码中,实现一个自定义动作处理器来识别您的用户。 ```kotlin class MyAdaptyUIObserver : AdaptyUIObserver { override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } ``` ## 处理自定义动作 \{#handle-custom-actions\} 要添加一个处理任意其他动作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 动作,并为其指定一个 ID。 2. 在您的应用代码中,为您创建的动作 ID 实现一个处理程序。 例如,如果您有另一组订阅优惠或一次性购买,可以添加一个按钮来显示另一个付费墙: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "login" -> { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` </SDKv3> --- # File: kmp-handling-events --- --- title: "处理流程与付费墙事件 - Kotlin Multiplatform" description: "在 Kotlin Multiplatform 应用中处理流程和付费墙事件。" --- <SDKv4> :::important 本指南介绍购买、恢复、产品选择和流程渲染的事件处理。你还需要实现按钮处理(关闭流程、打开链接等)。详情请参阅[流程操作处理指南](kmp-handle-paywall-actions)。 ::: 使用[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)配置的流程和付费墙无需额外代码即可完成购买和恢复购买。但它们会触发一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及购买相关操作的通知。请阅读以下内容了解如何响应这些事件。 要控制或监听移动应用中流程页面上发生的事件,请实现 `AdaptyUIFlowsEventsObserver` 接口的方法,并通过 `AdaptyUI.setFlowsEventsObserver()` 注册您的观察者。部分方法已有默认实现,可自动处理常见场景,因此只需覆盖您想要修改的方法即可: ```kotlin showLineNumbers title="Kotlin" AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { // override only the methods you want to change }) ``` :::note 这些方法是您添加自定义逻辑以响应流程事件的地方。您可以使用 `view.dismiss()` 关闭流程,或实现任何其他所需的自定义行为。请注意,`dismiss()` 是一个挂起函数——在回调中,请在观察者的 `mainUiScope` 上启动它:`mainUiScope.launch { view.dismiss() }`。 ::: ### 用户生成的事件 \{#user-generated-events\} #### 流程的显示与隐藏 \{#flow-appearance-and-disappearance\} 当流程出现或消失时,以下方法将被调用: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidAppear(view: AdaptyUIFlowView) { // Handle flow appearance // You can track analytics or update UI here } override fun flowViewDidDisappear(view: AdaptyUIFlowView) { // Handle flow disappearance // You can track analytics or update UI here } ``` :::note - 在 iOS 上,当用户点击流程中的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button),且网页付费墙在应用内浏览器中打开时,`flowViewDidAppear` 也会被触发。 - 在 iOS 上,当从流程中在应用内浏览器打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)从屏幕上消失时,`flowViewDidDisappear` 也会被触发。 ::: <Details> <summary>事件示例(点击展开)</summary> ```javascript // Flow appeared { // No additional data } // Flow disappeared { // No additional data } ``` </Details> #### 产品选择 \{#product-selection\} 如果用户选择了某个产品进行购买,将触发以下方法: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### 开始购买 \{#started-purchase\} 如果用户发起了购买流程,将触发以下方法: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` :::note 在[观察者模式](kmp-present-flows-in-observer-mode)下,从流程发起的购买会被传递到你的 `AdaptyUIObserverModeResolver`,而不是这里。 ::: <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### 购买成功、取消或待处理 \{#successful-canceled-or-pending-purchase\} 购买完成后,此方法将被调用。默认情况下,它不执行任何操作——购买完成后流程保持打开状态,直到你手动关闭它,因此请在用户获得访问权限后自行调用 `view.dismiss()`: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { when (purchaseResult) { is AdaptyPurchaseResult.Success -> { // Check if user has access to premium features if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } AdaptyPurchaseResult.Pending -> { // Handle pending purchase (e.g., user will pay offline with cash) } AdaptyPurchaseResult.UserCanceled -> { // Handle user cancellation } } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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" } } ``` </Details> 我们建议在购买成功后关闭流程页面。 #### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,此方法将被调用。这包括 StoreKit/Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `flowViewDidFinishPurchase` 并返回已取消的结果,而待处理的付款不会触发此方法。 ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } } } ``` </Details> #### 开始恢复购买 \{#started-restore\} 当用户发起恢复购买流程时,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartRestore(view: AdaptyUIFlowView) { // Handle restore start // You can show loading indicators or track analytics here } ``` #### 恢复成功 \{#successful-restore\} 如果购买恢复成功,此方法将被调用。默认情况下,它不执行任何操作——恢复完成后流程保持开启,直到你关闭它: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishRestore(view: AdaptyUIFlowView, profile: AdaptyProfile) { // Add your successful restore handling logic here // For example: show success message, update UI, or dismiss the flow // Check if user has access to premium features if (profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } ] } } ``` </Details> 我们建议在用户已拥有所需 `accessLevel` 时关闭该界面。请参阅[订阅状态](subscription-status)主题,了解如何检查订阅状态。 #### 恢复失败 \{#failed-restore\} 如果 `Adapty.restorePurchases()` 失败,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailRestore(view: AdaptyUIFlowView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### 网页支付导航完成 \{#web-payment-navigation-completion\} 如果用户通过 [web paywall](web-paywall) 发起购买流程,将会调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishWebPaymentNavigation( view: AdaptyUIFlowView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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" } } } ``` </Details> ### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 如果您在初始化时未传入产品信息,AdaptyUI 会自行从服务器获取所需对象。若此操作失败,AdaptyUI 将通过调用以下方法来报告错误: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailLoadingProducts(view: AdaptyUIFlowView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### 渲染和运行时错误 \{#rendering-and-runtime-errors\} 如果在界面渲染过程中发生错误,或出现其他非购买类运行时错误,该方法将予以上报。默认情况下,流程会在出错时关闭——可以重写此方法以保持流程继续运行,或添加自定义处理逻辑: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) { // Handle the error // The default implementation dismisses the flow; // once you override this method, dismissal is up to you } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ``` </Details> 在正常情况下,这类错误不应该出现,如果你遇到了,请告知我们。 #### 分析事件 \{#analytics-events\} `flowViewDidReceiveAnalyticEvent` 回调专用于接收来自流程的自定义分析事件。目前流程尚未向你的代码发送此类事件,因此无需实现它: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String ) { // Reserved for custom analytic events from a flow } ``` ### 导航 \{#navigation\} #### Android 系统返回按钮 \{#android-system-back-button\} 默认情况下,流程无法通过 Android 系统返回按钮或返回手势关闭——默认的 `flowViewDidPerformAction` 实现仅在 `CloseAction` 时关闭流程,并忽略 `AndroidSystemBackAction`,因此用户只能通过你定义的路径离开流程,例如 **Close** 按钮或编辑器中的 `on_device_back` 动作。如果你希望系统返回按钮能够关闭流程,请自行处理该动作: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } ``` 有关操作的完整列表,请参阅[处理流程操作的指南](kmp-handle-paywall-actions)。 </SDKv4> <SDKv3> 使用[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买。不过,它们会产生一些你的应用可以响应的事件,包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上购买相关操作的通知。请参阅下文了解如何响应这些事件。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**。 ::: 要在移动应用的付费墙页面中监控或控制各类事件,请实现 `AdaptyUIPaywallsEventsObserver` 接口的方法。其中部分方法已有默认实现,可自动处理常见场景。 :::note 这些方法是你添加自定义逻辑以响应付费墙事件的地方。你可以调用 `view.dismiss()` 关闭付费墙,或根据需要实现任何其他自定义行为。 ::: ## 用户生成的事件 \{#user-generated-events\} ### 付费墙的显示与隐藏 \{#paywall-appearance-and-disappearance\} 当付费墙显示或隐藏时,以下方法将被调用: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance // You can track analytics or update UI here } override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance // You can track analytics or update UI here } ``` :::note - 在 iOS 上,当用户点击付费墙内的 [web 付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button) 且 web 付费墙在应用内浏览器中打开时,`paywallViewDidAppear` 也会被触发。 - 在 iOS 上,当从付费墙在应用内浏览器中打开的 [web 付费墙](web-paywall#step-2a-add-a-web-purchase-button) 从屏幕上消失时,`paywallViewDidDisappear` 也会被触发。 ::: <Details> <summary>事件示例(点击展开)</summary> ```javascript // Paywall appeared { // No additional data } // Paywall disappeared { // No additional data } ``` </Details> ### 产品选择 \{#product-selection\} 如果用户选择了要购买的产品,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> ### 已开始购买 \{#started-purchase\} 如果用户发起购买流程,将会调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> ### 成功、取消或待处理的购买 \{#successful-canceled-or-pending-purchase\} 如果购买成功,将调用此方法。默认情况下,它会自动关闭付费墙,除非购买被用户取消: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishPurchase( view: AdaptyUIPaywallView, 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 } } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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" } } ``` </Details> 我们建议在购买成功后关闭付费墙页面。 ### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,此方法将被调用。这包括 StoreKit/Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `paywallViewDidFinishPurchase` 并返回已取消的结果,待处理的付款不会触发此方法。 ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailPurchase( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } } } ``` </Details> ### 开始恢复购买 \{#started-restore\} 如果用户发起恢复购买流程,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) { // Handle restore start // You can show loading indicators or track analytics here } ``` ### 成功恢复购买 \{#successful-restore\} 如果恢复购买成功,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, 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() } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } ] } } ``` </Details> 我们建议在用户拥有所需 `accessLevel` 时关闭该界面。请参阅[订阅状态](subscription-status)主题了解如何检查。 ### 恢复失败 \{#failed-restore\} 如果 `Adapty.restorePurchases()` 失败,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### Web 支付导航完成 \{#web-payment-navigation-completion\} 如果用户使用 [web 付费墙](web-paywall) 发起购买流程,将调用此方法: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishWebPaymentNavigation( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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" } } } ``` </Details> ## 数据获取与渲染 \{#data-fetching-and-rendering\} ### 产品加载错误 \{#product-loading-errors\} 如果您在初始化时未传入产品,AdaptyUI 将自行从服务器检索所需对象。若此操作失败,AdaptyUI 将通过调用以下方法来报告错误: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> ### 渲染错误 \{#rendering-errors\} 如果在界面渲染过程中发生错误,该方法将报告此错误: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error // In a normal situation, such errors should not occur // If you come across one, please let us know } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> 正常情况下不应出现此类错误,如果遇到,请及时告知我们。 </SDKv3> --- # File: kmp-use-fallback-paywalls --- --- title: "Kotlin Multiplatform - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况" --- 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} 1. 将备用配置文件添加到您的应用程序中。 * 如果目标平台是 Android,请将备用配置文件移动到 `android/app/src/main/assets/` 文件夹中。 * 如果目标平台是 iOS,请将备用 JSON 文件添加到项目包中。(**File** -> **Add Files to YourProjectName**) 2. 在获取目标流程、付费墙或用户引导**之前**调用 `.setFallback` 方法。 3. 根据目标平台设置 `assetId` 参数。 * Android:使用相对于 `assets` 目录的文件路径。 * iOS:使用完整文件名。 ```kotlin showLineNumbers Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` :::important `setFallback` 必须在 SDK 获取目标流程、付费墙或用户引导之前运行。 ::: 参数: | 参数 | 描述 | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **assetId** | 备用配置文件名(iOS)。 <br /> 备用配置文件路径,相对于 `assets` 目录(Android)。 | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: kmp-localizations-and-locale-codes --- --- title: "在 Kotlin Multiplatform SDK 中使用本地化和语言区域代码" description: "在 Kotlin Multiplatform 应用中管理本地化和语言区域代码,覆盖全球受众。" --- <SDKv4> ## 为什么这很重要 \{#why-this-is-important\} 当 Adapty 为流程选择本地化内容,以及当你读取自定义付费墙的远程配置时,语言区域代码就会发挥作用。 语言区域代码比较复杂,不同平台之间可能存在差异,因此 Adapty 在其所支持的每个平台上统一采用一套内部标准。了解这套标准有助于你预测用户最终会收到哪个本地化版本。 ## Adapty 的语言区域代码标准 \{#locale-code-standard-at-adapty\} Adapty 的语言区域代码采用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 查找与用户语言区域匹配的本地化内容时,会按以下步骤执行: 1. 将语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. Adapty 查找与完整语言区域代码完全匹配的本地化内容 3. 如果未找到匹配项,Adapty 会截取第一个连字符前的子字符串(例如 `pt-br` 对应 `pt`),并查找匹配的本地化内容 4. 如果仍未找到匹配项,Adapty 返回默认的 `en` 本地化内容 通过这种方式,`'pt_BR'`、`pt-BR` 和 `pt-br` 都会解析为相同的本地化内容。 ## 实现本地化 \{#implementing-localizations\} 在 SDK v4 中,获取 flow 时无需传入语言区域代码。 - **付费墙编辑工具与 Flow Builder 付费墙**:Adapty 会根据设备设置和您在编辑工具中配置的本地化内容自动解析语言区域。使用 `createFlowView` 渲染 flow,无需传入语言区域代码。 - **自定义(远程配置)付费墙**:`getFlow` 会在 `flow.remoteConfigs` 中返回所有已配置的本地化内容。每个条目都是一个 `AdaptyRemoteConfig`,包含 `locale` 代码和 `dataMap`。请自行选择与用户匹配的条目,并实现回退逻辑: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() // read your values from config?.dataMap } .onError { error -> // handle the error } ``` 上述语言区域代码匹配规则描述了 Adapty 如何规范化存储在每个远程配置中的 `locale` 代码。 </SDKv4> <SDKv3> ## 为什么这很重要 \{#why-this-is-important\} 在某些场景下,语言区域代码会发挥关键作用——例如,当你需要根据应用的当前本地化语言获取对应的付费墙时。 由于语言区域代码较为复杂,且在不同平台之间可能存在差异,我们在所有支持的平台上统一采用内部标准。也正因为这些代码较为复杂,你需要清楚地了解自己向服务器发送的内容,以及后续的处理逻辑——这样才能始终获取到预期的本地化结果。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} Adapty 的语言代码采用经过轻微修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 传来的语言代码并开始查找对应的付费墙本地化版本时,处理流程如下: 1. 将传入的语言代码字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. 查找与完整语言代码完全匹配的本地化版本 3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 取 `pt`),并查找匹配的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 这样,发送了 `'pt_BR'` 的 iOS 设备、发送了 `pt-BR` 的 Android 设备,以及发送了 `pt-br` 的另一台设备,都会得到相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果你在考虑本地化的问题,很可能你的项目中已经在处理本地化字符串资源了。如果是这样,我们建议在每个对应语言的资源文件中,添加一个键值对,将 Adapty 的语言区域代码作为其值。然后在调用 SDK 时,提取该键对应的值,示例如下: ```kotlin showLineNumbers // 1. 将 Adapty 语言代码添加到你的 Compose Multiplatform 资源中 /* composeResources/values/strings.xml(默认 — 英语) */ <string name="adapty_paywalls_locale">en</string> /* composeResources/values-es/strings.xml(西班牙语) */ <string name="adapty_paywalls_locale">es</string> /* composeResources/values-pt-rBR/strings.xml(葡萄牙语 — 巴西) */ <string name="adapty_paywalls_locale">pt-br</string> // 2. 提取并使用语言代码 suspend fun fetchPaywall() { val locale = getString(Res.string.adapty_paywalls_locale) Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = locale ).onSuccess { paywall -> // 请求到的付费墙 }.onError { error -> // 处理错误 } } ``` 这样,您就能完全掌控应用中每位用户获取到的本地化内容。 如果您没有使用 Compose Multiplatform 资源,同样的思路也适用于您所使用的任何本地化库(例如 [moko-resources](https://github.com/icerockdev/moko-resources))——将 Adapty 语言区域代码作为字符串存储在每个语言包的资源文件中,并在调用 SDK 前读取该值。 ## 实现本地化:另一种方式 \{#implementing-localizations-the-other-way\} 你可以不为每个本地化显式定义语言区域代码,同样能达到类似(但不完全相同)的效果。这种方式是直接从设备上提取语言区域代码——由于 `commonMain` 中没有共享的语言区域 API,因此需要用到 `expect`/`actual` 声明: ```kotlin showLineNumbers // commonMain expect fun currentLocaleTag(): String // androidMain actual fun currentLocaleTag(): String = Locale.getDefault().toLanguageTag() // iosMain actual fun currentLocaleTag(): String = NSLocale.currentLocale.localeIdentifier // commonMain — pass the locale code to Adapty suspend fun fetchPaywall() { Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = currentLocaleTag() ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } } ``` 请注意,由于以下几个原因,我们不建议使用此方法: 1. 在 iOS 上,用户的首选语言与设备的地区语言环境并不相同。`NSLocale.currentLocale.localeIdentifier` 返回的是地区语言环境,可能与用户实际阅读应用时使用的语言不一致。使用本地化字符串文件的 iOS 应用依赖 Apple 的解析逻辑来综合两者——这在上述推荐方案中可以开箱即用。 2. 很难预测设备会返回什么,以及它是否与 Adapty 的某个本地化配置匹配。设备语言环境可能包含你未在 Adapty 中配置的扩展或地区代码,在这种情况下,SDK 会回退到第一个子标签匹配,最终回退到 `en`。 如果你仍然决定采用这种方式,请确保覆盖了所有相关的使用场景。 </SDKv3> --- # File: kmp-web-paywalls --- --- title: "在 Kotlin Multiplatform SDK 中实现 Web 付费墙" description: "设置 Web 付费墙,无需支付应用商店费用和审核即可收款。" --- :::important 在开始之前,请确保您已[在看板中配置了 Web 付费墙](web-paywall),并已安装 Adapty SDK 3.15 或更高版本。 ::: ## 打开 Web 付费墙 \{#open-web-paywalls\} 如果您使用的是自行开发的付费墙,需要通过 SDK 方法来处理 Web 付费墙。`openWebPaywall` 方法的作用: 1. 生成唯一 URL,使 Adapty 能够将展示给特定用户的付费墙与其跳转到的网页关联起来。 2. 检测用户何时返回应用,并以短间隔请求 `getProfile`,以判断用户画像的访问权限是否已更新。 这样一来,如果支付成功且访问权限已更新,订阅几乎会立即在应用中激活。 :::note 用户返回应用后,请刷新 UI 以反映用户画像的更新。Adapty 将接收并处理用户画像更新事件。 ::: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall(product = product).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall(product = product)` — 根据付费墙生成 URL,并将产品数据附加到 URL 中。 2. `openWebPaywall(paywall = paywall)` — 根据付费墙生成 URL,但不附加产品数据。当 Adapty 付费墙中的产品与网页付费墙中的产品不同时,请使用此版本。 在 SDK v4 中,`paywall` 参数已替换为 `flowPaywall` 参数,该参数接受 `AdaptyFlowPaywall`,即 `flow.paywalls` 中的某个付费墙变体。详情请参阅[迁移指南](migration-to-kmp-sdk-v4)。 ::: ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} 默认情况下,网页付费墙会在外部浏览器中打开。 为了提供更流畅的用户体验,你可以在应用内浏览器中打开网页付费墙。这样,网页购买页面将直接显示在你的应用内,用户无需切换应用即可完成交易。 要启用此功能,请将 `openIn` 参数设置为 `AdaptyWebPresentation.IN_APP_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall( product = product, openIn = AdaptyWebPresentation.IN_APP_BROWSER // default – EXTERNAL_BROWSER ).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` --- # File: kmp-present-flows-in-observer-mode --- --- title: "在 Observer 模式下展示流程 - Kotlin Multiplatform" description: "在 Kotlin Multiplatform 应用中以 Observer 模式展示流程和付费墙编辑工具付费墙,同时使用您自己的代码处理购买。" --- 如果你已使用编辑工具自定义了流程或付费墙,则无需在移动应用代码中手动处理渲染逻辑来向用户展示它。此类流程或付费墙已同时包含展示内容和展示方式。 :::warning 本节仅适用于[观察者模式](observer-vs-full-mode)。如果你不在观察者模式下工作,请参阅[展示流程与付费墙](kmp-present-paywalls)主题。 ::: :::info 此功能需要 Adapty Kotlin Multiplatform SDK 4.0(beta)或更高版本——此前仅在原生 iOS 和 Android SDK 中可用。请参阅[迁移指南](migration-to-kmp-sdk-v4)进行升级。 ::: <details> <summary>在开始展示流程之前(点击展开)</summary> 1. 在 Adapty 中完成初始集成:[与 App Store 集成](initial_ios) 和 [与 Google Play 集成](initial-android)。 2. 安装并配置 Adapty SDK。请确保在配置构建器中调用 `withObserverMode(true)`。请参阅 [Kotlin Multiplatform SDK 安装指南](sdk-installation-kotlin-multiplatform#activate-adapty-sdk)。 3. 在 Adapty 看板中[创建产品](create-product)。 4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。 5. [创建版位并将流程或付费墙分配给对应版位](create-placement)。 6. 在移动应用代码中[获取流程及其配置](kmp-get-pb-paywalls)。 </details> 在观察者模式下,SDK 不会代替你执行购买操作。当用户在 Adapty 渲染的流程或付费墙中点击购买或恢复按钮时,SDK 会调用你的 `AdaptyUIObserverModeResolver`——请在其中使用你自己的代码完成购买或恢复操作。 1. 实现 `AdaptyUIObserverModeResolver` 接口: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUIObserverModeResolver import com.adapty.kmp.models.AdaptyPaywallProduct import com.adapty.kmp.models.AdaptyUIFlowView class MyObserverModeResolver : AdaptyUIObserverModeResolver { override fun observerModeDidInitiatePurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, onStartPurchase: () -> Unit, onFinishPurchase: () -> Unit ) { onStartPurchase() // the view shows its loading indicator // make the purchase with your own code, // then report the transaction to Adapty and call: onFinishPurchase() // the view hides the loading indicator } override fun observerModeDidInitiateRestore( view: AdaptyUIFlowView, onStartRestore: () -> Unit, onFinishRestore: () -> Unit ) { onStartRestore() // restore purchases with your own code, then: onFinishRestore() } } ``` `observerModeDidInitiatePurchase` 方法会通知你用户已发起购买,`observerModeDidInitiateRestore` 则通知用户已发起恢复。请在收到通知后触发你自定义的购买或恢复流程。 另外,请记得调用以下回调,以便将购买或恢复的进度通知给 AdaptyUI。这对于正确的流程行为(例如显示加载动画等)是必要的: | 回调 | 描述 | | :----------------- | :------------------------------------------------------------------------------- | | onStartPurchase() | 调用此回调以通知 AdaptyUI 购买已开始。 | | onFinishPurchase() | 调用此回调以通知 AdaptyUI 购买已完成。 | | onStartRestore() | 调用此回调以通知 AdaptyUI 恢复购买已开始。 | | onFinishRestore() | 调用此回调以通知 AdaptyUI 恢复购买已完成。 | 在您的代码运行期间,流程保持开启状态——在购买或恢复成功后,请自行关闭它。 2. 在展示任何屏幕之前注册解析器: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUI AdaptyUI.setObserverModeResolver(MyObserverModeResolver()) ``` 如果未注册解析器,流程视图将无法将购买请求传递给您的代码,用户点击购买按钮时将不会有任何响应。 3. 按常规方式创建并展示流程视图:[获取流程并创建其视图](kmp-get-pb-paywalls),然后[展示它](kmp-present-paywalls)。无需额外参数——一旦注册了解析器,每个 Adapty 渲染的流程或付费墙都会通过它来路由购买和恢复操作。 :::warning 不要忘记[上报交易并将其与付费墙关联](report-transactions-observer-mode-kmp)。否则,Adapty 将无法识别该交易,也无法确定购买来源的付费墙。 ::: --- # File: kmp-troubleshoot-paywall-builder --- --- title: "在 Kotlin Multiplatform SDK 中排查付费墙编辑工具问题" description: "在 Kotlin Multiplatform SDK 中排查付费墙编辑工具问题" --- 本指南帮助您解决在 Kotlin Multiplatform SDK 中使用 Adapty 付费墙编辑工具设计的付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:`createPaywallView` 方法无法创建付费墙视图,或付费墙没有视图配置。 **原因**:该付费墙未在付费墙编辑工具中启用设备展示。 **解决方法**:在付费墙编辑工具中开启 **Show on device** 开关。您也可以通过 `AdaptyPaywall` 对象上的 `hasViewConfiguration` 属性来检查付费墙是否具有视图配置。 <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 付费墙浏览次数过多 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙的浏览次数显示为预期值的两倍。 **原因**:你可能在代码中调用了 `logShowFlow`(SDK v4+)/ `logShowPaywall`,如果你使用的是付费墙编辑工具或 Flow Builder,这会导致浏览次数重复计算。对于使用这些工具构建的流程和付费墙,数据分析会自动追踪,无需手动调用此方法。 **解决方案**:如果你使用的是付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(SDK v4+)/ `logShowPaywall`。 --- # File: kmp-implement-paywalls-manually --- --- title: "在 Kotlin Multiplatform SDK 中手动实现付费墙" description: "了解如何在 Kotlin Multiplatform 应用中使用 Adapty SDK 手动实现付费墙。" --- ## 接受购买 \{#accept-purchases\} 如果你使用的是自己实现的付费墙,可以将购买处理委托给 Adapty,使用 `makePurchase` 方法即可。这样,我们会处理所有用户场景,你只需处理购买结果。 :::important `makePurchase` 仅适用于在 Adapty 看板中创建的产品。请确保按照[快速入门指南](quickstart)在看板中配置产品及其获取方式。 ::: <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases', 'kmp-restore-purchase', 'kmp-troubleshoot-purchases']} /> ## 观察者模式 \{#observer-mode\} 如果你想从头实现自己的购买处理逻辑,同时又希望享受 Adapty 的高级分析功能,可以使用观察者模式。 :::important 请在[此处](observer-vs-full-mode)了解观察者模式的限制。 ::: <CustomDocCardList ids={['implement-observer-mode-kmp', 'report-transactions-observer-mode-kmp', 'kmp-troubleshoot-purchases']} /> --- # File: kmp-quickstart-manual --- --- title: "在 Kotlin Multiplatform SDK 中为自定义付费墙启用购买功能" description: "将 Adapty SDK 集成到您的自定义 Kotlin Multiplatform 付费墙中,以启用应用内购买。" --- 本指南介绍如何将 Adapty 集成到您的自定义付费墙中。您可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。本指南使用 Adapty Kotlin Multiplatform SDK v4(测试版)API——如果您使用的是 v3 版本,请参阅[迁移指南](migration-to-kmp-sdk-v4)了解对应的方法名称。 :::important **本指南面向实现自定义付费墙的开发者。** 如果你想用最简便的方式开启购买功能,请使用 [Adapty Flow Builder](kmp-quickstart-paywalls)。通过 Flow Builder,你可以在无代码可视化编辑器中创建流程,Adapty 自动处理所有购买逻辑,无需重新发布应用即可测试不同设计。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,你需要了解三个关键概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,但这种设计让你无需修改应用代码即可更改产品、价格和优惠。在 SDK v4 中,版位的付费墙变体由一个 **flow** 对象承载——你获取 flow 并从中查询产品。 - [**版位**](placements) – 在应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求。这样可以方便地进行 A/B 测试,向不同用户展示不同的付费墙。 即使使用自定义付费墙,也请确保理解这些概念。简单来说,它们就是你在应用中管理销售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到**版位**中。这样才能获取你的产品。要了解在看板中需要完成哪些操作,请参考[这里](quickstart)的快速入门指南。 ### 管理用户 \{#manage-users\} 您可以选择是否在后端进行身份验证。 但是,Adapty SDK 对匿名用户和已识别用户的处理方式有所不同。请阅读[用户识别快速入门指南](kmp-quickstart-identify),了解其中的具体差异,确保正确处理用户。 ## 第一步:获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,你需要: 1. 通过将[版位](placements) ID 传入 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该流程的产品数组。 ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow(placementId = "YOUR_PLACEMENT_ID") .onSuccess { flow -> Adapty.getPaywallProducts(flow = flow) .onSuccess { products -> // Use products to build your custom paywall UI } .onError { error -> // Handle the error } } .onError { error -> // Handle the error } } ``` ## 第二步:处理购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,调用 `makePurchase` 方法并传入所选产品。该方法将处理购买流程并返回更新后的用户画像。 ```kotlin showLineNumbers fun purchaseProduct(product: AdaptyPaywallProduct) { Adapty.makePurchase(product = product) .onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } .onError { error -> // Handle the error } } ``` ## 步骤 3. 恢复购买 \{#step-3-restore-purchases\} 应用商店要求所有包含订阅功能的应用为用户提供恢复购买的途径。 当用户点击恢复购买按钮时,调用 `restorePurchases` 方法。这将把用户的购买历史与 Adapty 同步,并返回更新后的用户画像。 ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases() .onSuccess { profile -> // Restore successful, profile updated } .onError { error -> // Handle the error } } ``` ## 第四步:检查订阅状态 \{#step-4-check-the-subscription-status\} 购买或恢复后,检查用户的[访问等级](access-level),以决定是否显示付费墙或解锁付费功能。`makePurchase` 和 `restorePurchases` 方法已返回更新后的用户画像;在应用的其他地方需要获取当前状态时,请使用 `getProfile` 方法: ```kotlin showLineNumbers fun checkPremiumAccess() { Adapty.getProfile() .onSuccess { profile -> val hasPremiumAccess = profile.accessLevels["premium"]?.isActive == true // Grant access to paid features if hasPremiumAccess is true } .onError { error -> // Handle the error } } ``` 如需了解检查和监控订阅状态的更多方式(包括监听实时更新),请参阅[检查订阅状态](kmp-check-subscription-status)。 ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试您的购买,确保能够从付费墙完成测试购买。如需查看生产环境下的完整实现示例,请参阅我们示例应用中的 [AppViewModel.kt](https://github.com/adaptyteam/AdaptySDK-KMP/blob/main/example/composeMultiplatformApp/composeApp/src/commonMain/kotlin/com/adapty/exampleapp/AppViewModel.kt),其中演示了包含完整错误处理和状态管理的购买流程。 --- # File: fetch-paywalls-and-products-kmp --- --- title: "在 Kotlin Multiplatform SDK 中获取远程配置付费墙的付费墙和产品" description: "在 Adapty Kotlin Multiplatform SDK 中获取付费墙和产品,以增强用户变现能力。" --- <SDKv4> 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本文内容涉及远程配置和自定义付费墙。如需了解如何获取在 **Flow Builder** 或 **Paywall Builder** 中配置的流程或付费墙,请参阅[获取流程与付费墙](kmp-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>开始在移动应用中获取流程和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程或付费墙并将产品添加其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程或付费墙添加到版位中](create-placement)。 4. 在移动应用中[安装 Adapty SDK](sdk-installation-kotlin-multiplatform)。 </details> ## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product) 是将 App Store 和 Google Play 商品整合在一起的集合体。这些跨平台产品被集成到流程和付费墙中,让你能够在移动应用的特定版位展示它们。 要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的 ID 是版位 ID。流程是远程配置的,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果今天流程返回两个产品,明天返回三个,则应在无需修改代码的情况下全部展示。 ::: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存存在时直接返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但加载速度更快,不受网络质量影响。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](kmp-use-fallback-paywalls)。我们还使用 CDN 来加快流程和付费墙的加载速度,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的流程和付费墙,同时在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值用于限制此方法的超时时间。达到超时时间后,将返回缓存数据或本地备用内容。</p><p></p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能由多个不同请求组成。</p> | 不要硬编码产品 ID!由于流程是远程配置的,可用产品的数量以及特殊优惠(例如免费试用)都可能随时间变化。请确保您的代码能够处理这些场景。 例如,如果您最初获取到 2 个产品,应用应显示这 2 个产品;如果之后获取到 3 个产品,应用无需修改任何代码即可显示全部 3 个产品。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含:流程标识符、付费墙变体(`paywalls` — 每个变体有各自的产品标识符)、`remoteConfigs` 列表(每个已配置的语言环境对应一条记录),以及其他若干属性。如需获取该流程的产品,请调用 `getPaywallProducts(flow)`。 | :::note 在 v4 中,`getFlow` 没有 `locale` 参数。当你使用 `createFlowView` 渲染流程时,本地化会自动解析。对于自定义付费墙,所有可用的语言区域会一并通过 `flow.remoteConfigs` 返回——选择与用户设备或应用设置相匹配的语言区域即可。详情请参阅[本地化与语言区域代码](kmp-localizations-and-locale-codes)。 ::: ## 获取产品 \{#fetch-products\} 获取流程后,你可以查询与其对应的产品数组: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` 响应参数: | 参数 | 描述 | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 | 在实现自定义流程设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 对象中的这些属性。以下列出了最常用的属性,完整的属性说明请参阅上方链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 若要显示产品名称,请使用 `product.localizedTitle`。请注意,此本地化基于用户在商店中选择的国家/地区,而非设备本身的语言环境。 | | **Price** | 若要显示本地化价格,请使用 `product.price.localizedString`。此本地化基于设备的语言环境信息。您也可以通过 `product.price.amount` 以数字形式获取价格,该值以本地货币表示。若要获取对应的货币符号,请使用 `product.price.currencySymbol`。 | | **Subscription Period** | 若要显示订阅周期(如周、月、年等),请使用 `product.subscriptionDetails?.localizedSubscriptionPeriod`。此本地化基于设备的语言环境。若要以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。通过该属性可访问 `unit` 枚举以获取时长单位(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 值表示周期单位的数量。例如,对于按季度计费的订阅,`unit` 属性值为 `MONTH`,`numberOfUnits` 属性值为 `3`。 | | **Introductory Offer** | 若要显示标记或其他指示符来表明订阅包含新用户优惠,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。这是一个列表,最多可包含两个折扣阶段:免费试用阶段和优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:枚举类型,取值为 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。<br/>• `price`:折扣价格的数值形式。免费试用时该值为 `0`。<br/>• `localizedNumberOfPeriods`:根据设备语言环境本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段显示为 `3 days`。<br/>• `subscriptionPeriod`:您也可以通过此属性获取优惠周期的各项详细信息,其用法与上一部分关于订阅周期的说明相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言环境格式化后的折扣订阅周期字符串。 | ## 使用默认目标受众流程加速流程获取 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流程的获取几乎是即时完成的,无需担心速度问题。但如果您配置了大量目标受众和版位,且用户的网络连接较差,流程的获取时间可能会超出预期。在这种情况下,您可能希望先展示一个默认流程,以确保流畅的用户体验,而不是什么都不显示。 为了解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的流程。但请务必了解,推荐的做法是通过 `getFlow` 方法获取流程,详见上方的[获取流程信息](fetch-paywalls-and-products-kmp#fetch-flow-information)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺点: - **潜在的向后兼容性问题**:如果需要针对不同应用版本(当前版本和未来版本)展示不同的流程,可能会遇到挑战。你要么设计兼容当前(旧版)的流程,要么接受使用当前(旧版)的用户可能遇到流程无法渲染的问题。 - **失去定向能力**:所有用户都将看到针对 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些缺点以换取更快的流程获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-kmp#fetch-flow-information)介绍的 `getFlow`。 ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,失败时返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`——在缓存存在时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存仍然保留,只有在重新安装应用或手动清除时才会被清空。</p> | </SDKv4> <SDKv3> 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。如需了解如何获取付费墙编辑工具自定义付费墙的相关指导,请参阅[获取付费墙编辑工具的付费墙及其配置](kmp-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在您的移动应用中开始获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位中](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-kotlin-multiplatform)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合体。这些跨平台产品被集成到付费墙中,让你可以在移动应用的特定版位展示它们。 要展示产品,你需要使用 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果一个付费墙今天返回两个产品,明天返回三个,则无需修改代码即可全部展示。 ::: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。即您在 Adapty 看板中创建版位时所指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存数据存在时优先返回缓存。这样虽然用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后仍会保留,只有在卸载重装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储付费墙:上述定期更新的缓存层,以及[备用付费墙](kmp-use-fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终能获取最新版本的付费墙,同时在网络条件较差时也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为底层操作可能涉及多个请求。</p> | 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果之后获取到 3 个产品,应用应在无需修改代码的情况下显示全部 3 个。唯一需要硬编码的只有版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,你可以查询与其对应的产品数组: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` 响应参数: | 参数 | 描述 | | :-------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 对象中的这些属性。以下列出了最常用的属性,完整的属性列表请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **标题** | 要显示产品标题,请使用 `product.localizedTitle`。注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 | | **价格** | 要显示本地化价格,请使用 `product.price.localizedString`。该本地化基于设备的语言区域信息。也可以通过 `product.price.amount` 以数字形式获取价格,值将以当地货币提供。要获取对应的货币符号,请使用 `product.price.currencySymbol`。 | | **订阅周期** | 要显示周期(如周、月、年等),请使用 `product.subscriptionDetails?.localizedSubscriptionPeriod`。该本地化基于设备语言区域。要以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。从中可以访问 `unit` 枚举以获取时长单位(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性为 `MONTH`,`numberOfUnits` 属性为 `3`。 | | **新用户优惠** | 要显示徽标或其他指示符以表明订阅包含新用户优惠,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。这是一个列表,最多包含两个折扣阶段:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:枚举值为 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。<br/>• `price`:折扣价格(数字形式)。免费试用时此处为 `0`。<br/>• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示 `3 days`。<br/>• `subscriptionPeriod`:也可以通过此属性获取优惠周期的各项详细信息,其使用方式与上一节中描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期。 | ## 使用默认受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络连接较弱,付费墙的加载时间可能会比预期更长。在这种情况下,你可能希望先展示一个默认付费墙,以保证流畅的用户体验,而不是让用户看到空白页面。 为了解决这个问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的付费墙。但请务必了解,推荐的方式是通过 `getPaywall` 方法来获取付费墙,详情请参阅上方的[获取付费墙信息](fetch-paywalls-and-products-kmp#fetch-paywall-information)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显缺陷: - **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么需要设计兼容当前(旧版)版本的付费墙,要么接受当前(旧版)用户可能遇到付费墙无法渲染的问题。 - **定向能力缺失**:所有用户都会看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果您愿意接受这些不足之处以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-kmp#fetch-paywall-information)所述的 `getPaywall`。 ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p> | | **fetchPolicy** | 默认值:`AdaptyPaywallFetchPolicy.Default` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果您认为用户的网络连接不稳定,可以考虑使用 `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存依然保留,只有在卸载重装应用或手动清除时才会被清空。</p> | </SDKv3> --- # File: present-remote-config-paywalls-kmp --- --- title: "在 Kotlin Multiplatform SDK 中渲染远程配置设计的付费墙" description: "了解如何在 Adapty Kotlin Multiplatform SDK 中展示远程配置付费墙以个性化用户体验。" --- <SDKv4> 如果您使用远程配置自定义了付费墙,则需要在移动应用的代码中实现渲染逻辑,以便向用户展示。由于远程配置具有高度灵活性,您可以完全掌控付费墙的内容和显示方式。Adapty 提供了获取远程配置的方法,让您能够自主展示自定义付费墙。 ## 获取流程远程配置并展示 \{#get-flow-remote-config-and-present-it\} 在 v4 中,一个流程会在 `remoteConfigs` 列表中为每个已配置的语言环境携带一个 `AdaptyRemoteConfig` 条目。选取与用户偏好匹配的语言环境,然后读取所需的值。 ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String // use the remote config values } .onError { error -> // handle the error } ``` 至此,获取所有必要的值后,接下来需要将它们渲染并组合成一个视觉上吸引人的页面。确保设计能够适配各种手机屏幕尺寸和方向,在不同设备上提供流畅且友好的用户体验。 :::warning 请务必按照下方说明[记录付费墙查看事件](present-remote-config-paywalls-kmp#track-paywall-view-events),以便 Adapty 分析系统能够采集漏斗和 A/B 测试所需的数据。 ::: 展示付费墙后,继续设置购买流程。当用户发起购买时,直接调用 `.makePurchase()` 并传入流程中的产品即可。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](kmp-making-purchases)。 我们建议[创建一个备用付费墙(即备用付费墙)](kmp-use-fallback-paywalls)。当设备没有网络连接或无可用缓存时,系统会向用户展示此备用付费墙,从而确保用户在上述情况下依然能获得流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 可以帮助你衡量流程和付费墙的表现。虽然购买数据会自动收集,但浏览记录需要你手动上报,因为只有你知道用户何时看到了某个流程。 要记录一次浏览事件,只需调用 `.logShowFlow(flow)`,该事件随即会反映在漏斗和 A/B 测试的数据中。 :::important 如果你正在展示由[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙,则无需调用 `.logShowFlow(flow)`。在这些情况下,Adapty 会自动记录浏览。 ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) .onSuccess { // flow view logged successfully } .onError { error -> // handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:-----------------------------------------------------------------| | **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 | </SDKv4> <SDKv3> 如果您通过远程配置自定义了付费墙,则需要在移动应用的代码中实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由您掌控,付费墙视图的内容和呈现方式均可自定义。我们提供了一个获取远程配置的方法,让您能够自主展示通过远程配置设置的自定义付费墙。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String // use the remote config values }.onError { error -> // handle the error } ``` 此时,一旦获取了所有必要的值,就可以将它们渲染并组合成一个视觉效果出色的页面。请确保设计能够适配各种移动设备屏幕尺寸和方向,为不同设备上的用户提供流畅且友好的体验。 :::warning 请务必按照以下说明[记录付费墙展示事件](present-remote-config-paywalls-kmp#track-paywall-view-events-1),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 完成付费墙展示后,继续设置购买流程。当用户发起购买时,直接使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](kmp-making-purchases)。 我们建议[创建一个名为备用付费墙的备份付费墙](kmp-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,将向用户展示该备份付费墙,确保在这些情况下仍能提供流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 可帮助您衡量付费墙的效果。虽然我们会自动收集购买数据,但记录付费墙浏览事件需要您的配合,因为只有您知道用户何时看到了付费墙。 要记录付费墙浏览事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是通过[付费墙编辑工具](adapty-paywall-builder)创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall = paywall) .onSuccess { // paywall view logged successfully } .onError { error -> // handle the error } ``` 请求参数: | 参数 | 是否必需 | 描述 | | :---------- | :------- |:-------------------------------------------------------------------------------------------------------| | **paywall** | 必需 | 一个 [`AdaptyPaywall`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/) 对象。 | </SDKv3> --- # File: kmp-making-purchases --- --- title: "在 Kotlin Multiplatform SDK 的移动应用中进行购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙是向用户提供高级内容或服务访问权限的重要步骤。但是,仅仅展示付费墙本身,只有在您使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙时,才足以支持购买流程。 如果您未使用付费墙编辑工具,则必须使用名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙互动并完成所需交易的入口。 如果您的付费墙针对用户尝试购买的产品设置了有效的促销活动,Adapty 将在购买时自动应用该促销活动。 :::warning 请注意,新用户优惠仅在您使用付费墙编辑工具设置付费墙时才会自动应用。 在其他情况下,您需要[在 iOS 上验证用户是否具备新用户优惠资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布时被拒绝,并可能对有资格享受新用户优惠的用户收取全价。 ::: 请确保您已[完成初始配置](quickstart),不要跳过任何步骤。否则我们将无法验证购买。 ## 进行购买 \{#make-purchase\} :::note **正在使用[付费墙编辑工具](adapty-paywall-builder)?** 购买将自动处理——您可以跳过此步骤。 **需要分步指导?** 请查看[快速入门指南](kmp-implement-paywalls-manually),获取包含完整背景的端到端实现说明。 ::: ```kotlin showLineNumbers Adapty.makePurchase(product = product).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } }.onError { error -> // Handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :------------ | :------- |:-------------------------------------------------------------------------------------------------------------------------------------------------| | **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) 对象。| 响应参数: | 参数 | 描述 | |------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>请求成功后,响应中包含此对象。[AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) 对象提供了用户在应用内的访问等级、订阅及非订阅购买的完整信息。</p><p>请检查访问等级状态,以确认用户是否具有所需的应用访问权限。</p> | :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。该方法目前已被 Apple 弃用。 ::: ## 购买时更换订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而非续订当前订阅时,具体行为取决于所使用的应用商店。对于 Google Play,订阅不会自动更新,你需要按照以下说明在移动应用代码中手动处理切换逻辑。 要在 Android 中将订阅替换为另一个订阅,请在调用 `.makePurchase()` 方法时传入额外参数: ```kotlin showLineNumbers val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( oldSubVendorProductId = "old_subscription_product_id", replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE ) val purchaseParams = AdaptyPurchaseParameters.Builder() .setSubscriptionUpdateParams(subscriptionUpdateParams) .build() Adapty.makePurchase( product = product, parameters = purchaseParams ).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } }.onError { error -> // Handle the error } ``` 附加请求参数: | 参数 | 是否必填 | 描述 | |:---------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **parameters** | 可选 | 通过 [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/) 传入的 [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/) 对象。 | 您可以在 Google 开发者文档中了解更多关于订阅和替换模式的信息: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 关于替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更仅在当前订阅计费周期结束时生效。 ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 在您的应用中显示代码兑换界面: ```kotlin showLineNumbers Adapty.presentCodeRedemptionSheet() .onSuccess { // code redemption sheet presented successfully } .onError { error -> // handle the error } ``` :::danger 根据我们的观察,部分应用中的优惠码兑换界面可能无法可靠运行。我们建议将用户直接重定向至 App Store。 为此,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## 管理预付方案 (Android) \{#manage-prepaid-plans-android\} 如果您的应用用户可以购买[预付方案](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月的非续订订阅),您可以为预付方案启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。 ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withGoogleEnablePendingPrepaidPlans(true) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } ``` --- # File: kmp-restore-purchase --- --- title: "在 Kotlin Multiplatform SDK 中恢复移动应用内购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 恢复购买是一项功能,允许用户重新获得对之前购买内容(例如订阅或应用内购买)的访问权限,而无需再次付费。此功能对于可能已卸载并重新安装应用程序,或切换到新设备并希望访问之前购买内容而无需再次付款的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您额外编写代码。如果您属于这种情况,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```kotlin showLineNumbers Adapty.restorePurchases().onSuccess { profile -> if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } }.onError { error -> // handle the error } ``` 响应参数: | 参数 | 描述 | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>一个 [`AdaptyProfile`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-profile/) 对象。该模型包含有关访问等级、订阅和非订阅购买的信息。</p><p>检查**访问等级状态**以确定用户是否有权访问该应用。</p> | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: implement-observer-mode-kmp --- --- title: "在 Kotlin Multiplatform SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以便在 Kotlin Multiplatform SDK 中追踪用户订阅事件。" --- 如果您已有自己的购买基础设施,暂时不打算完全切换到 Adapty,可以了解[观察者模式](observer-vs-full-mode)。在基本形态下,观察者模式提供高级数据分析功能,以及与归因和分析系统的无缝集成。 如果这满足您的需求,您只需: 1. 在配置 Adapty SDK 时,将 `observerMode` 参数设置为 `true` 以启用该功能。请按照 [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) 的设置说明进行操作。 2. 从现有购买基础设施向 Adapty [上报交易记录](report-transactions-observer-mode-kmp)。 :::tip 在 SDK v4 中,你也可以在观察者模式下呈现 Adapty 渲染的流程和付费墙:当用户点击购买或恢复按钮时,SDK 会将操作交给你的代码,由你自行处理购买或恢复逻辑。详见[在观察者模式下呈现流程](kmp-present-flows-in-observer-mode)。 ::: ## Observer 模式设置 \{#observer-mode-setup\} 如果你自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请开启 Observer 模式。 :::important 在 Observer 模式下运行时,Adapty SDK 不会关闭任何交易,请确保你自行处理。 ::: ```kotlin showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withObserverMode(true) // default false .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised in observer mode") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` 参数: | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | observerMode | 布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在观察者模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还想使用 Adapty 的付费墙和 A/B 测试功能,也是可以的——但在观察者模式下需要一些额外的设置。除上述步骤外,您还需要: 1. 按照[远程配置付费墙](present-remote-config-paywalls-kmp)的常规方式展示付费墙。 3. 将付费墙与购买交易[关联](report-transactions-observer-mode-kmp)。 --- # File: report-transactions-observer-mode-kmp --- --- title: "在 Kotlin Multiplatform SDK 的 Observer 模式下上报交易" description: "在 Kotlin Multiplatform SDK 的 Adapty Observer 模式下上报购买交易,用于用户洞察和收入追踪。" --- 在 Observer 模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要手动上报来自应用商店的交易。在发布应用**之前**完成此设置至关重要,以避免分析数据出现错误。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别。 :::warning **请勿跳过交易上报!** 如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成系统。 ::: 如果您使用 Adapty 付费墙,请在上报交易时包含 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```kotlin showLineNumbers Adapty.reportTransaction( transactionId = "your_transaction_id", variationId = paywall.variationId ).onSuccess { profile -> // Transaction reported successfully // profile contains updated user data }.onError { error -> // handle the error } ``` 参数说明: | 参数 | 是否必填 | 说明 | | --------------- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | 必填 | 来自应用商店购买的交易 ID。通常是商店返回的购买令牌或交易标识符。 | | variationId | 选填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 | --- # File: kmp-troubleshoot-purchases --- --- title: "在 Kotlin Multiplatform SDK 中排查购买问题" description: "在 Kotlin Multiplatform SDK 中排查购买问题" --- 本指南帮助您解决在 Kotlin Multiplatform SDK 中手动实现购买时遇到的常见问题。 ## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **问题**:`makePurchase` 方法成功完成,但用户的用户画像和订阅状态未在 Adapty 中更新。 **原因**:这通常表示 Google Play Store 设置不完整或存在配置问题。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\} **问题**:`makePurchase` 方法针对同一笔购买被多次调用。 **原因**:这通常发生在由于 UI 状态管理问题或用户快速操作而多次触发购买流程时。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## 观察者模式下出现 AdaptyError.cantMakePayments \{#adaptyelrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时收到 `AdaptyError.cantMakePayments`。 **原因**:在观察者模式下,您应在自己的代码中处理购买,而不是使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 处理购买,请关闭观察者模式。您需要二选一:使用 `makePurchase`,或在观察者模式下自行处理购买。详情请参阅[实现观察者模式](implement-observer-mode-kmp)。 ## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **问题**:您收到来自 Google Play Store 的计费不可用错误。 **原因**:此错误与 Adapty 无关,是 Google Play 计费库的错误,表示设备上的计费功能不可用。 **解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查阅更多相关信息:[处理 BillingResult 响应代码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。 ## 未找到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:您遇到了找不到 `makePurchasesCompletionHandlers` 的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建一个新的沙盒用户并重试。这通常可以解决与沙盒相关的购买完成处理程序问题。 --- # File: kmp-user --- --- title: "Kotlin Multiplatform SDK 中的用户与访问管理" description: "了解如何在 Kotlin Multiplatform 应用中使用 Adapty SDK 处理用户和访问等级。" --- 本页汇总了在 Kotlin Multiplatform 应用中处理用户和访问等级的所有指南。请选择您需要的主题: - **[识别用户](kmp-identifying-users)** - 了解如何在应用中识别用户 - **[更新用户数据](kmp-setting-user-attributes)** - 设置用户属性和用户画像数据 - **[监听订阅状态变化](kmp-listen-subscription-changes)** - 实时监控订阅变更 - **[Kids 模式](kids-mode-kmp)** - 为您的应用实现 Kids 模式 --- # File: kmp-identifying-users --- --- title: "在 Kotlin Multiplatform SDK 中识别用户" description: "在 Adapty 中识别用户,以提升个性化订阅体验。" --- Adapty 会为每位用户创建一个内部用户画像 ID。但如果您有自己的认证系统,应该设置您自己的 Customer User ID。您可以在[用户画像](profiles-crm)部分通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 还会被发送到所有集成渠道。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("YOUR_USER_ID") .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } } ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 在初始化后设置用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 初始化时没有用户 ID,可以随时通过 `.identify()` 方法进行设置。最常见的使用场景是在用户注册或登录之后,即用户从匿名状态切换为已认证状态时。 ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID").onSuccess { // successful identify }.onError { error -> // handle the error } ``` 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重新提交重要用户数据 在某些情况下,例如用户再次登录其账户时,Adapty 服务器可能已经存储了该用户的信息。在这种情况下,Adapty SDK 会自动切换到新用户。如果你之前向匿名用户传递了任何数据(例如自定义属性或来自第三方网络的归因数据),需要为已识别的用户重新提交这些数据。 同样需要注意的是,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 登出与登录 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法将用户登出: ```kotlin showLineNumbers Adapty.logout().onSuccess { // successful logout }.onError { error -> // handle the error } ``` 之后可以使用 `.identify()` 方法让用户重新登录。 ## 分配 `appAccountToken`(iOS)\{#assign-appaccounttoken-ios\} [`iosAppAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,用于将 App Store 交易与你的内部用户身份关联起来。 StoreKit 会将此 token 附加到每笔交易中,这样你的后端就能将 App Store 数据与用户进行匹配。 建议为每个用户生成一个稳定的 UUID,并在同一账号的不同设备上复用它。 这样可以确保购买记录和 App Store 通知始终与正确的用户绑定。 您可以通过两种方式设置 token——在 SDK 激活时或在识别用户时。 :::important 您必须始终将 `iosAppAccountToken` 与 `customerUserId` 一起传递。 如果只传递 token,它将不会包含在交易中。 ::: ```kotlin showLineNumbers // 配置时: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ) .build() ).onSuccess { // 激活成功 }.onError { error -> // 处理错误 } // 或在识别用户时 Adapty.identify( customerUserId = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ).onSuccess { // 识别成功 }.onError { error -> // 处理错误 } ``` ## 设置混淆账户 ID(Android)\{#set-obfuscated-account-ids-android\} Google Play 在某些场景下要求提供混淆账户 ID,以保护用户隐私和安全。这些 ID 可帮助 Google Play 在不暴露用户信息的情况下识别购买记录,对防范欺诈和数据分析尤为重要。 如果你的应用涉及敏感用户数据,或需要遵守特定隐私法规,就可能需要设置这些 ID。混淆 ID 让 Google Play 能够追踪购买行为,同时不会泄露真实的用户标识符。 :::important 您必须始终将 `androidObfuscatedAccountId` 与 `customerUserId` 一起传递。 如果仅传递混淆账号 ID,它将不会包含在交易中。 ::: ```kotlin showLineNumbers // 配置时: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ) .build() ).onSuccess { // 激活成功 }.onError { error -> // 处理错误 } // 或在识别用户时 Adapty.identify( customerUserId = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ).onSuccess { // 识别成功 }.onError { error -> // 处理错误 } ``` ## 跨设备用户识别 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: kmp-setting-user-attributes --- --- title: "在 Kotlin Multiplatform SDK 中设置用户属性" description: "了解如何在 Adapty 中设置用户属性以实现更好的目标受众细分。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。您可以使用这些属性来创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.FEMALE) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) .onSuccess { // profile updated successfully } .onError { error -> // handle the error } ``` 请注意,您之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 的允许键 `<Key>` 及其对应值 `<Value>` 如下所示: | 键 | 值 | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | 枚举,允许的值为:`AdaptyProfile.Gender.FEMALE`、`AdaptyProfile.Gender.MALE`、`AdaptyProfile.Gender.OTHER` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性,这些属性通常与您的应用使用情况相关。例如,对于健身应用,可以是每周锻炼次数;对于语言学习应用,可以是用户的知识水平,等等。您可以在市场细分中使用这些属性来创建针对性付费墙和优惠,也可以在数据分析中用它们来找出哪些产品指标对收入影响最大。 ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withCustomAttribute("key1", "value1") ``` 要删除已有的键,请使用 `.withRemovedCustomAttribute()` 方法: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withRemovedCustomAttribute("key2") ``` 有时您需要了解哪些自定义属性已经被设置过。为此,可以使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能不是最新的,因为用户属性可以随时从不同设备发送,因此服务器上的属性可能在上次同步后已发生变更。 ::: ### 限制 \{#limits\} - 每个用户最多 30 个自定义属性 - 键名最长 30 个字符,键名可包含字母数字字符及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,最长不超过 50 个字符。 --- # File: kmp-listen-subscription-changes --- --- title: "在 Kotlin Multiplatform SDK 中检查订阅状态" description: "在 Adapty 中跟踪和管理用户订阅状态,以提高 Kotlin Multiplatform 应用的用户留存率。" --- 借助 Adapty,追踪订阅状态变得十分简单。您无需在代码中手动插入产品 ID,而是可以通过检查活跃的[访问等级](access-level)来轻松确认用户的订阅状态。 在开始检查订阅状态之前,请先设置[实时开发者通知(RTDN)](enable-real-time-developer-notifications-rtdn)。 ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) 对象的属性。我们建议在应用启动时获取用户画像(例如在[识别用户](android-identifying-users#setting-customer-user-id-on-configuration)时),并在发生变更时及时更新。这样,您就可以直接使用用户画像对象,而无需反复请求。 如需接收用户画像更新通知,请按照下方[监听用户画像更新(包括访问等级变化)](android-listen-subscription-changes)章节中的说明监听用户画像变更。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 如需从服务器获取访问等级,请使用 `.getProfile()` 方法: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> // check the access }.onError { error -> // handle the error } ``` 响应参数: | 参数 | 说明 | | --------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>[AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) 对象。通常情况下,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。</p><p></p><p>`.getProfile` 方法始终会尝试查询 API,因此能提供最新的结果。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。此外,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的实时性。</p> | `.getProfile()` 方法会返回用户画像,您可以从中获取访问等级状态。一个应用可以设置多个访问等级。例如,如果您有一个新闻应用,并向用户独立销售不同主题的订阅,则可以创建"sports"和"science"两个访问等级。但在大多数情况下,您只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。 以下是检查默认"premium"访问等级的示例: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } }.onError { error -> // handle the error } ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化时,Adapty 都会触发一个事件。 如需接收来自 Adapty 的消息,您需要进行以下额外配置: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` Adapty 也会在应用启动时触发一个事件,此时将传递缓存中的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 中实现的缓存会存储用户画像的订阅状态。这意味着即使服务器不可用,也可以访问缓存数据以获取用户画像的订阅状态信息。 但需要注意的是,无法直接从缓存中请求数据。SDK 会每分钟定期查询服务器,检查是否有与用户画像相关的更新或变更。如果存在任何修改(例如新的交易或其他更新),这些修改将同步至缓存数据,以确保其与服务器保持一致。 --- # File: kmp-deal-with-att --- --- title: "在 Kotlin Multiplatform SDK 中处理 ATT" description: "开始在 Kotlin Multiplatform 上使用 Adapty,以简化订阅设置和管理。" --- 如果您的应用使用了 AppTrackingTransparency 框架,并向用户展示应用追踪授权请求,则您需要将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 ```kotlin showLineNumbers val profileParameters = AdaptyProfileParameters.Builder() .withAttStatus(3) // 3 = ATTrackingManagerAuthorizationStatusAuthorized .build() Adapty.updateProfile(profileParameters) .onSuccess { // ATT status updated successfully } .onError { error -> // handle AdaptyError } ``` :::warning 我们强烈建议您在该值发生变化时尽早发送,只有这样,数据才能及时传递到您已配置的集成渠道。 ::: --- # File: kids-mode-kmp --- --- title: "Kotlin Multiplatform SDK 中的儿童模式" description: "轻松启用儿童模式以符合 Google 政策。Kotlin Multiplatform SDK 中不收集 GAID 或广告数据。" --- 如果您的 Kotlin Multiplatform 应用面向儿童用户,则必须遵循 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单步骤即可完成配置,以满足这些政策要求并通过应用商店审核。 ## 需要做什么?\{#whats-required\} 您需要配置 Adapty SDK,禁止收集以下信息: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers)(iOS) - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)(Android) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用客户用户 ID。`<FirstName.LastName>` 格式的用户 ID 以及电子邮件地址,都会被视为收集个人数据。在儿童模式下,最佳实践是使用随机化或匿名化的标识符(例如哈希 ID 或设备生成的 UUID),以确保合规性。 ## 启用儿童模式\{#enabling-kids-mode\} ### 在 Adapty 看板中进行设置\{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。请前往 [App settings](https://app.adapty.io/settings/general),在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 在移动应用代码中进行更新\{#updates-in-your-mobile-app-code\} 为符合政策要求,您需要在初始化 Adapty SDK 时禁用 Android 广告 ID(AAID/GAID)和 IP 地址的收集: ```kotlin showLineNumbers override fun onCreate() { super.onCreate() val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") // highlight-start .withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised with privacy settings") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } } ``` --- # File: kmp-onboardings --- --- title: "Kotlin Multiplatform SDK 中的用户引导" description: "了解如何在 Kotlin Multiplatform 应用中使用 Adapty SDK 处理用户引导。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [流程](kmp-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程在设备上原生渲染,带来更流畅的动画、一致的原生视觉体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](kmp-get-pb-paywalls)和[展示流程与付费墙](kmp-present-paywalls)以开始使用。 ::: <CustomDocCardList /> --- # File: kmp-get-onboardings --- --- title: "在 Kotlin Multiplatform SDK 中获取用户引导" description: "了解如何在 Adapty 中为 Kotlin Multiplatform 检索用户引导。" --- :::warning **用户引导功能已在 SDK v4 中废弃,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用[流程](kmp-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染,带来更流畅的动画效果、一致的原生视觉体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](kmp-get-pb-paywalls)和[展示流程与付费墙](kmp-present-paywalls)开始使用。 ::: 在 [Adapty 看板中使用编辑工具完成用户引导的视觉设计](design-onboarding)之后,您可以在 Kotlin Multiplatform 应用中展示它。第一步是获取与版位关联的用户引导及其视图配置,具体步骤如下所示。 开始之前,请确认: 1. 已安装 [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) 3.15.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 ## 获取用户引导 \{#fetch-onboarding\} 当你使用我们的无代码编辑工具创建[用户引导](onboardings)后,它会以容器的形式存储,其中包含应用需要获取并展示的配置信息。该容器负责管理整个体验——包括显示哪些内容、如何呈现,以及如何处理用户交互(如测验答案或表单输入)。容器还会自动追踪数据分析事件,无需单独实现视图追踪。 为了获得最佳性能,建议尽早获取用户引导配置,以便图片有足够的时间在展示给用户之前完成下载。 要获取用户引导,请使用 `getOnboarding` 方法: ```kotlin showLineNumbers Adapty.getOnboarding( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 目标[版位](placements)的标识符。此值为您在 Adapty 看板中创建版位时所指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | 用户引导本地化的标识符。该参数应为由一个或两个子标签通过减号(**-**)连接组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。<p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时优先返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后依然保留,仅在应用卸载重装或手动清理时才会清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储用户引导:上述定期更新的缓存,以及备用用户引导。我们还使用 CDN 加速用户引导的获取,并设有独立的备用服务器以应对 CDN 不可用的情况。该系统旨在确保您始终获取最新版本的用户引导,同时在网络条件较差时也能保持可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p>请注意,在少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能包含多个不同的请求。</p> | 响应参数: | 参数 | 描述 | |:----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-onboarding/) 对象,包含:用户引导标识符与配置、远程配置以及其他若干属性。 | ## 使用默认目标受众用户引导加速获取用户引导 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常情况下,用户引导的获取几乎是即时完成的,无需担心速度问题。但当您配置了大量目标受众和用户引导,且用户网络连接较差时,获取用户引导可能会比预期花费更长时间。在这种情况下,您可能希望展示一个默认用户引导,以确保流畅的用户体验,而不是什么都不显示。 要解决这个问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的用户引导。但请务必了解,推荐的方式是通过 `getOnboarding` 方法来获取用户引导,详见上文的[获取用户引导](#fetch-onboarding)章节。 :::warning 建议使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者存在以下重要限制: - **兼容性问题**:在支持多个应用版本时可能引发问题,需要进行向后兼容设计,否则旧版本可能显示异常。 - **无个性化**:仅展示"全部用户"目标受众的内容,无法根据国家、归因或自定义属性进行定向。 如果更快的获取速度对你的使用场景更重要,请按以下方式使用 `getOnboardingForDefaultAudience`。否则,请按[上文](#fetch-onboarding)所述使用 `getOnboarding`。 ::: ```kotlin showLineNumbers Adapty.getOnboardingForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。该值在 Adapty 看板中创建版位时由您指定。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | 用户引导本地化的标识符。该参数应为语言代码,由一个或两个子标签组成,以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。<br/>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时直接返回缓存数据。这样用户获取到的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后依然保留,只有在卸载重装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储用户引导:上述定期更新的缓存,以及备用用户引导。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可用时提供独立的备用服务器。该机制旨在确保您始终能获取最新版本的用户引导,同时在网络条件较差的情况下也能保证可靠性。</p> | --- # File: kmp-present-onboardings --- --- title: "在 Kotlin Multiplatform SDK 中展示用户引导" description: "了解如何有效地展示用户引导以提升转化率。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用[流程](kmp-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——动画更流畅、外观与原生体验一致、加载速度更快,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](kmp-get-pb-paywalls)和[展示流程与付费墙](kmp-present-paywalls)以开始使用。 ::: 如果您已使用编辑工具自定义了用户引导,则无需在 Kotlin Multiplatform 应用代码中手动处理其渲染逻辑来向用户展示它。此类用户引导已同时包含应显示的内容及其显示方式。 在开始之前,请确保: 1. 您已安装 [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) 3.16.1 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 Adapty Kotlin Multiplatform SDK 提供两种展示用户引导的方式: - **使用 Compose Multiplatform** - **不使用 Compose Multiplatform** ## 使用 Compose Multiplatform \{#with-compose-multiplatform\} 要显示用户引导,请在通过 `createOnboardingView` 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示用户引导,请再次调用 `createOnboardingView` 创建一个新的 `view` 实例。 :::warning 在未重新创建的情况下复用同一个 `view` 可能会导致错误。 ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createOnboardingView(onboarding = onboarding).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置用户引导在 iOS 上的呈现方式。该参数接受 `AdaptyUIIOSPresentationStyle.FULLSCREEN`(默认值)或 `AdaptyUIIOSPresentationStyle.PAGESHEET`。 ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createOnboardingView(onboarding = onboarding).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ### 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} 默认情况下,用户引导中的链接会在应用内浏览器中打开。这种方式能让用户无需切换应用即可浏览网页,提供流畅的使用体验。 如果你希望链接在外部浏览器中打开,可以将 `externalUrlsPresentation` 参数设置为 `AdaptyWebPresentation.EXTERNAL_BROWSER` 来自定义此行为: ```kotlin showLineNumbers viewModelScope.launch { AdaptyUI.createOnboardingView( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.EXTERNAL_BROWSER // default – IN_APP_BROWSER ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ## 不使用 Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeOnboardingView` 是核心模块 `io.adapty:adapty-kmp` 的一部分。如果你的项目不使用 Compose Multiplatform,则无需添加 `io.adapty:adapty-kmp-ui` 依赖。 ::: 如需在不使用 Compose Multiplatform 的情况下嵌入用户引导,请调用 `createNativeOnboardingView`。它会返回一个 `AdaptyNativeOnboardingView`,你可以将其添加到布局中: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeOnboardingView( context = context, viewModelStoreOwner = activity, onboarding = onboarding, observer = myOnboardingObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> 由于 KMP 接口的默认方法在 Swift 中会变成 `@required`,你无法在 Swift 中直接实现 `AdaptyUIOnboardingsEventsObserver`。请先在 `iosMain` 中声明一个 open 基类: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseOnboardingObserver : AdaptyUIOnboardingsEventsObserver ``` 然后在 Swift 中继承它,只覆盖你需要的方法: ```swift showLineNumbers title="Swift" class MyOnboardingObserver: BaseOnboardingObserver { override func onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // remove nativeView from your view hierarchy } } let nativeView = AdaptyUI.shared.createNativeOnboardingView( onboarding: onboarding, observer: MyOnboardingObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### 释放视图 \{#dispose-the-view\} 从布局中移除视图时,请调用 `dispose()`。此操作将注销事件监听器并释放内部资源。 ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` --- # File: kmp-handling-onboarding-events --- --- title: "在 Kotlin Multiplatform SDK 中处理用户引导事件" description: "使用 Adapty 在 Kotlin Multiplatform 中处理用户引导相关事件。" --- :::warning **用户引导功能已在 SDK v4 中弃用,将在未来版本中移除。** 该功能不再接受修复或改进。请改用[流程](kmp-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上进行原生渲染,带来更流畅的动画效果、一致的原生外观体验、更快的加载速度,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](kmp-get-pb-paywalls)和[展示流程与付费墙](kmp-present-paywalls)快速上手。 ::: 开始之前,请确保: 1. 您已安装 [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) 3.15.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 通过编辑工具配置的用户引导会生成事件,您的应用可以对这些事件作出响应。请参阅下文了解如何响应这些事件。 ## 设置用户引导事件观察者 \{#set-up-the-onboarding-event-observer\} 要处理用户引导事件,您需要实现 `AdaptyUIOnboardingsEventsObserver` 接口,并通过 `AdaptyUI.setOnboardingsEventsObserver()` 进行设置。这应在应用生命周期的早期完成,通常在主 Activity 或应用初始化时进行。 ```kotlin // In your app initialization AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ## 自定义操作 \{#custom-actions\} 在编辑工具中,你可以为按钮添加 **custom** 操作并为其分配一个 ID。然后,你可以在代码中使用该 ID,并将其作为自定义操作进行处理。 <img src={require('./img/ios-events-1.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,委托方法 `onCustomAction` 将被触发,并携带来自编辑工具的操作 ID。您可以创建自己的 ID,例如 "allowNotifications"。 ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCustomAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { when (actionId) { "openPaywall" -> { // Display paywall from onboarding // You would typically fetch and present a new paywall here mainUiScope.launch { // Example: Get paywall by placement ID // val paywallResult = Adapty.getPaywall("your_placement_id") // paywallResult.onSuccess { paywall -> // val paywallViewResult = AdaptyUI.createPaywallView(paywall) // paywallViewResult.onSuccess { paywallView -> // paywallView.present() // } // } } } "allowNotifications" -> { // Handle notification permissions } else -> { // Handle other custom actions } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了 **Close** 操作的按钮时,用户引导即被视为已关闭。您需要管理用户关闭用户引导时所发生的情况。例如: :::important 您需要管理用户关闭用户引导时所发生的情况。例如,您需要停止显示用户引导本身。 ::: 如果您使用的是 [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform),`view.isStandaloneView` 为 `false` — 默认实现不会调用 `view.dismiss()`。请在此回调中将视图从布局中移除,并对其调用 `dispose()`。 ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Dismiss the onboarding screen mainUiScope.launch { view.dismiss() } // Additional cleanup or navigation logic can be added here // For example, navigate back or show main app content } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## 打开付费墙 \{#opening-a-paywall\} :::tip 如果你想在用户引导内部打开付费墙,请处理此事件。如果你想在付费墙关闭后再打开一个付费墙,有一种更直接的方式——处理 [`onboardingViewOnCloseAction`](#closing-onboarding) 事件,无需依赖事件数据即可打开付费墙。 ::: 在用户引导中使用付费墙最顺畅的方式,是将 action ID 设置为等于付费墙的版位 ID。这样你就可以直接用版位 ID 来获取并打开对应的付费墙: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnPaywallAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Get the paywall using the placement ID from the action mainUiScope.launch { val paywallResult = Adapty.getPaywall(placementId = actionId) paywallResult.onSuccess { paywall -> val paywallViewResult = AdaptyUI.createPaywallView(paywall) paywallViewResult.onSuccess { paywallView -> paywallView.present() }.onError { error -> // handle the error } }.onError { error -> // handle the error } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## 完成用户引导加载 \{#finishing-loading-onboarding\} 当用户引导完成加载时,将调用此方法: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewDidFinishLoading( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta ) { // Handle loading completion // You can add any initialization logic here } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## 导航事件 \{#navigation-events\} `onboardingViewOnAnalyticsEvent` 方法在用户引导流程中发生各种分析事件时被调用。 `event` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | 当用户引导加载完成时 | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | 当任意屏幕显示时 | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | 当某个屏幕完成时。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的回复)。当用户执行任何操作退出该屏幕时触发。 | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | 当第二个屏幕显示时 | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | 当用户通过输入框提交邮箱时触发 | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | 当用户到达 ID 为 `final` 的屏幕时触发。如果需要此事件,请将 `final` ID 分配给最后一个屏幕。 | | `AdaptyOnboardingsAnalyticsEventUnknown` | 用于任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据) | 每个事件都包含 `meta` 信息,内容如下: | 字段 | 描述 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前屏幕的标识符 | | `screenIndex` | 当前屏幕在流程中的位置 | | `screensTotal` | 流程中的屏幕总数 | 以下是如何使用分析事件进行追踪的示例: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnAnalyticsEvent( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, event: AdaptyOnboardingsAnalyticsEvent ) { when (event) { is AdaptyOnboardingsAnalyticsEventOnboardingStarted -> { // Track onboarding start trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenPresented -> { // Track screen presentation trackEvent("screen_presented", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenCompleted -> { // Track screen completion with user response trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingsAnalyticsEventOnboardingCompleted -> { // Track successful onboarding completion trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingsAnalyticsEventUnknown -> { // Handle unknown events trackEvent(event.name, event.meta) } // Handle other cases as needed } } private fun trackEvent(eventName: String, meta: AdaptyUIOnboardingMeta, elementId: String? = null, reply: String? = null) { // Implement your analytics tracking here // For example, send to your analytics service } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // OnboardingStarted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "welcome_screen", "screenIndex": 0, "screensTotal": 4 } } // ScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 4 } } // ScreenCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 }, "elementId": "profile_form", "reply": "success" } // SecondScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // UserEmailCollected { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // OnboardingCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "final_screen", "screenIndex": 3, "screensTotal": 4 } } ``` </Details> --- # File: kmp-onboarding-input --- --- title: "在 Kotlin Multiplatform SDK 中处理用户引导数据" description: "使用 Adapty SDK 在 Kotlin Multiplatform 应用中保存并使用用户引导数据。" --- :::warning **用户引导功能在 SDK v4 中已废弃,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [流程](kmp-get-pb-paywalls):与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染,带来更流畅的动画效果、一致的原生外观、更快的加载速度,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](kmp-get-pb-paywalls)和[展示流程与付费墙](kmp-present-paywalls)快速上手。 ::: 当您的用户回答测验问题或在输入字段中填写数据时,`onboardingViewOnStateUpdatedAction` 方法将被调用。您可以在代码中保存或处理字段类型。 例如: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsSelectParams -> { // Handle single selection val id = params.id val value = params.value val label = params.label AppLogger.d("Selected option: $label (id: $id, value: $value)") } is AdaptyOnboardingsMultiSelectParams -> { // Handle multiple selections } is AdaptyOnboardingsInputParams -> { // Handle text input } is AdaptyOnboardingsDatePickerParams -> { // Handle date selection } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>已保存数据示例(格式可能因实现方式不同而有所差异)</summary> ```javascript // Example of a saved select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "preferences_screen", "screen_index": 1, "total_screens": 3 }, "action": { "element_id": "preference_selector", "element_type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 3 }, "action": { "element_id": "interests_selector", "element_type": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "name_input", "element_type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "birthday_picker", "element_type": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用户画像数据补充 \{#enrich-user-profiles-with-data\} 如果你希望立即将用户输入的数据与用户画像关联,避免重复询问相同信息,可以在处理操作时[更新用户画像](kmp-setting-user-attributes),将输入数据写入其中。 例如,你让用户在 ID 为 `name` 的文本字段中输入姓名,并希望将该字段的值设置为用户的名字;同时,你让用户在 `email` 字段中输入邮箱。在你的应用代码中,实现方式可能如下: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsInputParams -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (elementId) { "name" -> { when (val input = params.input) { is AdaptyOnboardingsTextInput -> { builder.withFirstName(input.value) } } } "email" -> { when (val input = params.input) { is AdaptyOnboardingsEmailInput -> { builder.withEmail(input.value) } } } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Profile updated: ${profile.email}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ### 根据答案定制付费墙 \{#customize-paywalls-based-on-answers\} 在用户引导中使用测验,您还可以根据用户完成用户引导后的答案来定制向其展示的付费墙。 例如,您可以询问用户的运动经验,并向不同用户群体展示不同的 CTA 和产品。 1. 在用户引导编辑器中[添加测验](onboarding-quizzes),并为测验选项分配有意义的 ID。 2. 根据 ID 处理测验响应,并为用户[设置自定义属性](kmp-setting-user-attributes)。 ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Handle quiz responses and set custom attributes when (params) { is AdaptyOnboardingsSelectParams -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.value) } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Custom attribute 'experience' set to: ${params.value}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为你创建的每个市场细分添加[目标受众](audience)。 5. 在你的应用代码中为该版位[展示付费墙](kmp-paywalls)。如果你的用户引导中有一个打开付费墙的按钮,请将付费墙代码实现为[响应该按钮操作](kmp-handling-onboarding-events#opening-a-paywall)。 --- # File: kmp-best-practices --- --- title: "Kotlin Multiplatform SDK 最佳实践" description: "Adapty SDK 在 Kotlin Multiplatform 上集成的参考模式——调用顺序、错误处理及其他生产就绪规则。" --- <CustomDocCardList /> --- # File: kmp-sdk-call-order --- --- title: "Kotlin Multiplatform SDK 中的调用顺序" description: "按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、归因缺失及间歇性激活错误。" --- `Adapty.activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其完成之前,SDK 没有任何状态。在 `activate()` 之前或与其并行发出的任何调用都会以激活错误失败。详见 [在 Kotlin Multiplatform SDK 中处理错误](kmp-handle-errors)。 如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到 ID 后调用 `Adapty.identify()`。在 `identify` 完成之前,不要调用任何用户操作相关的方法。与 `identify` 并发执行的调用要么会返回错误,要么会落在激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属关系,不一定能转移到已识别的用户画像上。如果你的应用不需要用户认证,则无需调用 `identify`,直接使用匿名用户画像即可。 MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待其 UID 回调后再调用 `Adapty.activate`。否则 MMP ID 会落在一个临时匿名用户画像上,且不一定会转移到已识别的用户画像上。有关 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的操作路径取决于两个因素:您何时获得用户 ID,以及是否使用了 MMP 或分析 SDK。 - **步骤 2 和 5**:每个应用都必须完成。先激活 SDK,再调用 SDK 方法。 - **步骤 1 和 3**:仅在集成了 MMP 或分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时才需要。 - **步骤 4**:仅在应用需要用户身份验证,且在启动后才能获取用户 ID 时才需要。 如果在应用启动时就已知客户用户 ID,请在调用 `activate()` 之前将其传入 `AdaptyConfig.Builder`(步骤 2a)。这种方式不会创建匿名用户画像,因此无需执行步骤 4。 | 步骤 | 调用 | 时机 | 备注 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化你的 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动,优先执行 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | 应用启动,步骤 1 之后,如果你已有 customer user ID | 推荐方式。不会创建匿名用户画像。 | | 2b | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").build())` 不带 `withCustomerUserId` | 应用启动,步骤 1 之后,如果你没有 customer user ID(或从不收集) | Adapty 会创建一个匿名用户画像。 | | 3 | 为每个 MMP 调用 `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` | 步骤 2 之后,任何用户操作调用之前 | 必需,以确保 MMP ID 关联到正确的用户画像。 | | 4 | `Adapty.identify("YOUR_USER_ID").onSuccess { ... }.onError { ... }` | 步骤 3 之后(如无 MMP 则在步骤 2 之后),步骤 5 之前——仅适用于路径 2b 且有身份验证的情况 | 在任何用户操作调用前等待 `onSuccess`。`identify` 执行期间的并发调用可能会落到匿名用户画像上。 | | 5 | `getPaywall`(SDK v4 中为 `getFlow`)、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 如果调用了 `identify`,则在步骤 4 之后;否则在步骤 3 之后(如无 MMP 则在步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回归用户无法恢复高级权限、用户画像中缺少 `appsflyer_id`,以及付费墙针对错误的目标受众展示。 ::: ## Web2app 与 Web 漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在 Web 结账页面(Stripe、Paddle)完成购买后才安装原生应用,设备首次调用 `activate()` 时会创建一个全新的匿名用户画像,该画像不会自动与 Web 端的用户画像关联。如果你能在应用启动前(通过认证流程或安装来源引用)获取到用户 ID,可直接将其传入 `AdaptyConfig.Builder`。否则,在你调用 `identify("YOUR_USER_ID")` 并执行 `restorePurchases` 之前,设备上将无法看到该 Web 端购买记录。 关于每次 Web 结账时需要传递的元数据,请参考: - [Stripe](stripe) - [Paddle](paddle) --- # File: kmp-optimize-paywall-fetching --- --- title: "在 Kotlin Multiplatform SDK 中优化付费墙获取" description: "可靠地获取 Adapty 付费墙:针对 Kotlin Multiplatform 的时机选择、缓存策略与备用方案。" --- 在 Kotlin Multiplatform 上实现可靠的付费墙获取需要做到三点:渲染速度快、返回符合目标受众的付费墙,以及在网络较慢时优雅地降级到备用方案。以下规则涵盖了实现这一目标所需的时机选择、缓存策略和备用模式。 :::tip 以下规则假设 `Adapty.activate()` 和 `Adapty.identify()` 均已执行完毕。请参阅 [Kotlin Multiplatform SDK 调用顺序](kmp-sdk-call-order)。 ::: 以下建议使用 v3 方法名称。在 SDK v4 中,`getPaywall` 已重命名为 `getFlow`(详见[迁移指南](migration-to-kmp-sdk-v4))——所有规则同样适用。 ## 规则与注意事项 \{#rules-and-pitfalls\} | 建议做法 | 避免做法 | 原因 | |---|---|---| | 按需拉取即将展示的版位。 | 启动时并发预取所有版位。 | 批量预取会阻塞主线程,并在请求高峰期间产生黑屏。 | | 在归因数据有机会解析之后再调用 `getPaywall`,例如在 `activate` 之后等待 1–2 秒,或在 `setOnProfileUpdatedListener` 触发之后。 | 在应用启动时调用 `getPaywall`。 | 此时归因数据尚未到达。付费墙会按默认目标受众解析,并静默跳过市场细分和 ASA 个性化。 | | 为每个版位设置 `loadTimeout` 并配置[备用付费墙](fallback-paywalls)。 | 无限期等待 `getPaywall` 返回。 | 没有超时设置时,网络状况不佳的用户会一直看到空白屏幕,直到网络恢复——或者直接关闭应用。 | 请参阅[获取付费墙和产品](fetch-paywalls-and-products-kmp)了解 `fetchPolicy` 和 `loadTimeout` 参数说明,以及[版位](placements)了解如何选择合适的版位。 ## 针对弱网环境的调优 \{#tune-for-poor-connectivity\} 对于网络连接持续较差的市场(农村地区、交通途中、受路由影响的地区): - 除首次请求外,每次获取付费墙时均设置 `fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeout` 设置为 3–5 秒,并在超时触发时接受备用付费墙。 - 不要将付费墙的显示与 `Adapty.getProfile()` 绑定。独立调用 `getPaywall`,避免因用户画像加载缓慢而阻塞 UI。 --- # File: kmp-test --- --- title: "在 Kotlin Multiplatform SDK 中测试与发布" description: "了解如何在 Kotlin Multiplatform 应用中使用 Adapty 检查订阅状态。" --- 如果您已在 Kotlin Multiplatform 应用中集成了 Adapty SDK,接下来需要测试所有配置是否正确,以及购买流程是否按预期运行。这包括在沙盒环境中测试 SDK 集成和实际购买流程。 ## 测试您的应用 \{#test-your-app\} 如需全面测试应用内购买,请参阅我们的平台专项测试指南:[iOS 测试指南](test-purchases-in-sandbox) 和 [Android 测试指南](testing-on-android)。 ## 准备发布 \{#prepare-for-release\} 在将应用提交至商店之前,请按照[发布检查清单](release-checklist)确认以下事项: - 商店连接和服务器通知已配置 - 购买已完成并上报至 Adapty - 访问等级可正确解锁和恢复 - 已满足隐私和审核要求 --- # File: kmp-reference --- --- title: "Kotlin Multiplatform SDK 参考文档" description: "Adapty Kotlin Multiplatform SDK 的参考文档。" --- 本页面包含 Adapty Kotlin Multiplatform SDK 的参考文档。请选择您需要的主题: - **[SDK 模型](https://kmp.adapty.io/adapty/)** - SDK 使用的数据模型与结构 - **[错误处理](kmp-handle-errors)** - 错误处理与问题排查 --- # File: kmp-handle-errors --- --- title: "在 Kotlin Multiplatform SDK 中处理错误" description: "了解如何在 Kotlin Multiplatform 应用中使用 Adapty 处理错误。" --- 本页介绍 Adapty Kotlin Multiplatform SDK 中的错误处理。 ## 错误处理基础 \{#error-handling-basics\} 所有 Adapty SDK 方法都会返回成功或错误结果。请务必同时处理这两种情况: <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Handle success } is AdaptyResult.Error -> { val error = result.error // Handle error Log.e("Adapty", "Error: ${error.message}") } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // Handle success } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle error Log.e("Adapty", "Error: " + error.getMessage()); } }); ``` </TabItem> </Tabs> ## 常见错误码 \{#common-error-codes\} | 错误码 | 说明 | 解决方案 | |--------|------|----------| | 1000 | 未找到产品 ID | 检查看板中的产品配置 | | 1001 | 网络错误 | 检查网络连接 | | 1002 | SDK 密钥无效 | 验证您的 SDK 密钥 | | 1003 | 无法完成支付 | 设备不支持支付功能 | | 1004 | 产品不可用 | 产品未在商店中配置 | ## 处理特定错误 \{#handle-specific-errors\} ### 网络错误 \{#network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getPaywall("main") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1001 -> { // Network error - show offline message showOfflineMessage() } else -> { // Other errors showErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getPaywall("main", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1001: // Network error - show offline message showOfflineMessage(); break; default: // Other errors showErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ### 购买错误 \{#purchase-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers product.makePurchase { result -> when (result) { is AdaptyResult.Success -> { val purchase = result.value // Purchase successful showSuccessMessage() } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1003 -> { // Can't make payments showPaymentNotAvailableMessage() } 1004 -> { // Product not available showProductNotAvailableMessage() } else -> { // Other purchase errors showPurchaseErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers product.makePurchase(result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchase purchase = ((AdaptyResult.Success<AdaptyPurchase>) result).getValue(); // Purchase successful showSuccessMessage(); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1003: // Can't make payments showPaymentNotAvailableMessage(); break; case 1004: // Product not available showProductNotAvailableMessage(); break; default: // Other purchase errors showPurchaseErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ## 错误恢复策略 \{#error-recovery-strategies\} ### 网络错误重试 \{#retry-on-network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers fun getPaywallWithRetry(placementId: String, maxRetries: Int = 3) { var retryCount = 0 fun attemptGetPaywall() { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && retryCount < maxRetries) { // Network error - retry retryCount++ Handler(Looper.getMainLooper()).postDelayed({ attemptGetPaywall() }, 1000 * retryCount) // Exponential backoff } else { // Max retries reached or other error showErrorMessage(error.message) } } } } } attemptGetPaywall() } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public void getPaywallWithRetry(String placementId, int maxRetries) { AtomicInteger retryCount = new AtomicInteger(0); Runnable attemptGetPaywall = new Runnable() { @Override public void run() { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && retryCount.get() < maxRetries) { // Network error - retry retryCount.incrementAndGet(); new Handler(Looper.getMainLooper()).postDelayed(this, 1000 * retryCount.get()); } else { // Max retries reached or other error showErrorMessage(error.getMessage()); } } }); } }; attemptGetPaywall.run(); } ``` </TabItem> </Tabs> ### 回退至缓存数据 \{#fallback-to-cached-data\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers class PaywallManager { private var cachedPaywall: AdaptyPaywall? = null fun getPaywall(placementId: String) { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value cachedPaywall = paywall showPaywall(paywall) } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall!!) showOfflineIndicator() } else { // No cache available or other error showErrorMessage(error.message) } } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public class PaywallManager { private AdaptyPaywall cachedPaywall; public void getPaywall(String placementId) { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); cachedPaywall = paywall; showPaywall(paywall); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall); showOfflineIndicator(); } else { // No cache available or other error showErrorMessage(error.getMessage()); } } }); } } ``` </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} - [修复 Code-1000 noProductIDsFound 错误](InvalidProductIdentifiers-kmp) - [修复 Code-1003 cantMakePayments 错误](cantMakePayments-kmp) - [完整 API 参考](https://android.adapty.io) - 完整的 SDK 文档 --- # File: InvalidProductIdentifiers-kmp --- --- title: "修复 Kotlin Multiplatform SDK 中的 Code-1000 noProductIDsFound 错误" description: "解决在 Adapty 中管理订阅时出现的无效产品标识符错误。" --- 1000 错误码 `noProductIDsFound` 表示你在付费墙上请求的产品在 App Store 中无法购买,尽管它们已被列在其中。此错误有时会附带 `InvalidProductIdentifiers` 警告。如果只出现警告而没有错误,可以安全忽略。 如果你遇到了 `noProductIDsFound` 错误,请按照以下步骤解决: ## 步骤 1. 检查 Bundle ID \{#step-2-check-bundle-id\} 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 第二步:检查产品 \{#step-3-check-products\} 1. 前往 **App Store Connect**,在左侧菜单中导航至 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions)。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,即可在 **Subscriptions** 部分看到你的产品列表。 3. 确认要测试的产品已标记为 **Ready to Submit**。如未标记,请参考 [App Store 产品](app-store-products) 页面的说明。 <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将表格中的产品 ID 与 Adapty 看板中 [**Products**](https://app.adapty.io/products) 标签页里的产品 ID 进行比对。如果 ID 不匹配,请从表格中复制产品 ID,并在 Adapty 看板中[创建产品](create-product)。 <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 3. 检查产品可用性 \{#step-4-check-product-availability\} 1. 返回 **App Store Connect**,打开同一个 **Subscriptions** 部分。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称查看你的产品。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 滚动到 **Availability** 部分,确认所有必填的国家和地区均已列出。 <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第4步. 检查产品价格 \{#step-5-check-product-prices\} 1. 再次前往 **App Store Connect** 中的 **Monetization** → **Subscriptions** 部分。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动至 **Subscription Pricing**,展开 **Current Pricing for New Subscribers** 部分。 <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 确保所有必需的价格均已填写。 <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 5. 检查应用付费状态、银行账户和税务表单是否有效 \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. 在 **App Store Connect**](https://appstoreconnect.apple.com/) 主页,点击 **Business**。 <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择您的公司名称。 3. 向下滚动,确认您的 **Paid Apps Agreement**、**Bank Account** 和 **Tax forms** 均显示为 **Active**。 按照以上步骤,您应该能够解决 `InvalidProductIdentifiers` 警告,并让您的产品在商店中上线。 ## 第 6 步:如果产品卡住了,请重新创建 \{#step-6-recreate-the-product-if-its-stuck\} 即使第 1–5 步全部通过——状态为 `Approved`、Bundle ID 匹配、API 密钥有效——SDK 仍可能返回 `1000 noProductIDsFound`。这种情况下,产品可能卡在了 Apple 的注册表中。Apple 的产品注册表偶尔会进入一种状态:产品在 App Store Connect 界面中存在,但无法通过 StoreKit 的查询路径访问。 在 App Store Connect 中删除该产品,然后用相同的产品 ID 重新创建它。重新创建后,等待最长 24 小时以完成数据同步。 --- # File: cantMakePayments-kmp --- --- title: "修复 Kotlin Multiplatform SDK 中的 Code-1003 cantMakePayment 错误" description: "解决在 Adapty 中管理订阅时的付款错误。" --- 1003 错误 `cantMakePayments` 表示该设备无法进行应用内购买。 如果你遇到了 `cantMakePayments` 错误,通常是由以下原因之一导致的: - 设备限制:该错误与 Adapty 无关。请参阅下方的修复方法。 - 观察者模式配置:`makePurchase` 方法与观察者模式不能同时使用。请参阅下方相关章节。 ## 问题:设备限制 \{#issue-device-restrictions\} | 问题 | 解决方案 | |---------------------------|---------------------------------------------------------| | 屏幕使用时间限制 | 在 [Screen Time](https://support.apple.com/en-us/102470) 中关闭应用内购买限制 | | 账户被暂停 | 联系 Apple 支持以解决账户问题 | | 地区限制 | 使用受支持地区的 App Store 账户 | ## 问题:同时使用观察者模式和 makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} 如果你使用 `makePurchases` 来处理购买,则无需启用观察者模式。[观察者模式](observer-vs-full-mode) 仅在你自行实现购买逻辑时才需要使用。 因此,如果你正在使用 `makePurchase`,可以安全地从 SDK 激活代码中移除对观察者模式的启用。 --- # File: kmp-sdk-migration-guides --- --- title: "Kotlin Multiplatform SDK 迁移指南" description: "Adapty Kotlin Multiplatform SDK 各版本的迁移指南。" --- 本页面包含 Adapty Kotlin Multiplatform SDK 的所有迁移指南。请选择您要迁移到的目标版本以查看详细说明: - **[迁移至 v4.0(测试版)](migration-to-kmp-sdk-v4)** - **[迁移至 v3.15](migration-to-kmp-315)** --- # File: migration-to-kmp-sdk-v4 --- --- title: "将 Adapty Kotlin Multiplatform SDK 迁移至 v. 4.0" description: "通过将付费墙 API 替换为流程 API,迁移至 Adapty Kotlin Multiplatform SDK v4.0(测试版),兼容流程编辑工具和付费墙编辑工具。" --- Adapty Kotlin Multiplatform SDK 4.0(测试版)引入了流程功能,并相应地重命名了付费墙 API。新 API 同时支持新版流程编辑工具和现有的付费墙编辑工具——Adapty 看板端无需进行任何配置更改。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.createPaywallView(paywall, ...)` | `AdaptyUI.createFlowView(flow, ...)` | | `AdaptyUI.createNativePaywallView(...)` → `AdaptyNativePaywallView` | `AdaptyUI.createNativeFlowView(...)` → `AdaptyNativeFlowView` | | `AdaptyUIPaywallView` | `AdaptyUIFlowView` | | `AdaptyUI.presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI.presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUI.setPaywallsEventsObserver(observer)` | `AdaptyUI.setFlowsEventsObserver(observer)` | | `AdaptyUI.registerPaywallEventsListener` / `unregisterPaywallEventsListener` | `AdaptyUI.registerFlowEventsListener` / `unregisterFlowEventsListener` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUIPaywallPlatformView(paywall, ...)` | `AdaptyUIFlowPlatformView(flow, ...)` | | `paywallViewDidPerformAction`、`paywallViewDidAppear` 及其他 `paywallView...` 回调 | `flowViewDidPerformAction`、`flowViewDidAppear` 及其他 `flowView...` 回调 | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` 保持原名不变——产品仍然属于某个流程,`getPaywallProducts` 方法名也保持不变,现在接受一个 `AdaptyFlow` 参数。`getFlow` 和 `getFlowForDefaultAudience` 方法不再接受 `locale` 参数。购买和用户画像相关的 API(`makePurchase`、`restorePurchases`、`getProfile`、`identify`、`updateProfile`)以及通过 `setFallback` 设置备用付费墙的功能均保持不变。用户引导方法仍然可用,但已被废弃——详见[用户引导 API 废弃说明](#onboarding-api-deprecation)。部分默认行为有所变更——详见[默认行为变更](#default-behavior-changes)。 ## 安装 \{#installation\} v4.0 是预发布版本,因此需要固定精确版本——Gradle 不会通过动态范围选择预发布版本: ```toml showLineNumbers title="libs.versions.toml" [versions] adapty-kmp = "4.0.0-beta.1" [libraries] adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } adapty-kmp-ui = { module = "io.adapty:adapty-kmp-ui", version.ref = "adapty-kmp" } ``` `adapty-kmp-ui` 模块仅在你通过 Compose Multiplatform 层(`view.present()`)渲染流程和付费墙时才需要用到。完整配置步骤请参阅[安装 Adapty SDK](sdk-installation-kotlin-multiplatform)。 底层原生 Adapty SDK 在两个平台上均已升级至 4.x 版本,且会自动解析——无需修改构建配置。iOS 部署目标仍为 **15.0**,本次发布未作更改。 ## 获取流程 \{#fetching-flows\} ### getPaywall → getFlow 返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,同时移除了 `locale` 参数——渲染流程时,语言环境会自动解析;对于自定义付费墙,所有语言环境均通过 `flow.remoteConfigs` 返回: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") - .onSuccess { paywall -> - // use the paywall + Adapty.getFlow("YOUR_PLACEMENT_ID") + .onSuccess { flow -> + // use the flow } .onError { error -> // handle the error } ``` `getPaywallForDefaultAudience` 已按相同方式重命名: ```diff showLineNumbers - Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") + Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` 保持原名,但现在接受 `AdaptyFlow` 参数: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) + Adapty.getPaywallProducts(flow) .onSuccess { products -> // use the products } ``` ## 数据模型 \{#data-model\} `getFlow` 返回 `AdaptyFlow` 而非 `AdaptyPaywall`,对象结构也发生了变化: | v3 `AdaptyPaywall` 属性 | v4 `AdaptyFlow` 属性 | 操作 | |---|---|---| | `remoteConfig: AdaptyRemoteConfig?`(单个) | `remoteConfigs: List<AdaptyRemoteConfig>` | 一个流程为每种已配置的语言携带一个远程配置。读取与用户匹配的那个:`flow.remoteConfigs.firstOrNull { it.locale == "en" }`。 | | _(新增)_ | `paywalls: List<AdaptyFlowPaywall>` | 每个条目是流程中的一个付费墙变体,包含其自身的 `name`、`variationId` 和 `productIdentifiers`。Web 付费墙方法接受 `AdaptyFlowPaywall` 参数——请参阅 [Web 付费墙方法](#web-paywall-methods)。 | | `productIdentifiers` | 已迁移 | 产品标识符现在位于每个变体上:`flow.paywalls[i].productIdentifiers`。获取产品时,继续调用 `getPaywallProducts(flow)`。 | | `hasViewConfiguration` | 已移除 | 从代码中移除所有 `hasViewConfiguration` 检查——`createFlowView` 会返回错误(请参阅[展示流程](#displaying-flows))。 | `hasViewConfiguration` 保留在 `AdaptyOnboarding` 上——只有流程模型会移除它。 ## Web 付费墙方法 \{#web-paywall-methods\} `openWebPaywall` 和 `createWebPaywallUrl` 保持原有名称,但 `paywall` 参数已替换为 `flowPaywall` 参数,接受 `AdaptyFlowPaywall` 类型——即 `flow.paywalls` 中的某个实例。你也可以继续传入 `AdaptyPaywallProduct`: ```diff showLineNumbers - Adapty.openWebPaywall(paywall = paywall) + flow.paywalls.firstOrNull()?.let { flowPaywall -> + Adapty.openWebPaywall(flowPaywall = flowPaywall) + } ``` ## 跟踪流程查看次数 \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` 已重命名为 `logShowFlow`,现在接受一个 `AdaptyFlow` 参数。事件仍针对相同的实验变体进行记录,因此现有的漏斗和 A/B 测试数据图表无需更改看板配置即可继续使用。 ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` 与 v3 相同,当通过 [Flow Builder](adapty-flow-builder) 或[付费墙编辑工具](adapty-paywall-builder)渲染流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些浏览行为。 ## 显示流程 \{#displaying-flows\} ### createPaywallView → createFlowView 重命名工厂方法并传入 `AdaptyFlow`。返回的视图类型从 `AdaptyUIPaywallView` 重命名为 `AdaptyUIFlowView`,但其方法(`present`、`dismiss`)和可选参数(`loadTimeout`、`preloadProducts`、`customTags`、`customTimers`、`customAssets`、`productPurchaseParams`)保持不变: ```diff showLineNumbers - AdaptyUI.createPaywallView(paywall) + AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` 如果你不使用 Compose Multiplatform,原生工厂方法的重命名方式相同: ```diff showLineNumbers - AdaptyUI.createNativePaywallView(paywall) + AdaptyUI.createNativeFlowView(flow) ``` `createFlowView` 在流程未配置视图时返回 `AdaptyResult.Error`,这取代了 v3 中的 `hasViewConfiguration` 检查: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - AdaptyUI.createPaywallView(paywall) - .onSuccess { view -> view.present() } - } + AdaptyUI.createFlowView(flow) + .onSuccess { view -> view.present() } + .onError { error -> + // the flow has no view configured, or view creation failed + } ``` :::note 流程视图是一次性的:调用 `dismiss()` 后,视图会被销毁,如需再次展示该流程,请重新调用 `createFlowView`。 ::: ## 处理事件 \{#handling-events\} 事件观察器从 `AdaptyUIPaywallsEventsObserver` 更名为 `AdaptyUIFlowsEventsObserver`,其回调方法的 `paywallView` 前缀改为 `flowView`。现有的处理器主体无需修改代码——只需重命名类型和重写方法即可: ```diff showLineNumbers - AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver { - override fun paywallViewDidFinishPurchase( - view: AdaptyUIPaywallView, + AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { + override fun flowViewDidFinishPurchase( + view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { // custom logic after purchase } }) ``` 一个回调也已重命名:`paywallViewDidFailRendering` 改为 `flowViewDidReceiveError`。它会在与之前相同的渲染错误时触发,同时还涵盖其他非购买类运行时错误: ```diff showLineNumbers - override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {} + override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {} ``` 完整回调列表请参阅[处理流程与付费墙事件](kmp-handling-events)。 ### Compose 平台视图 \{#compose-platform-view\} 如果你使用 Compose Multiplatform 的 composable 嵌入视图,`AdaptyUIPaywallPlatformView(paywall, ...)` 已重命名为 `AdaptyUIFlowPlatformView(flow, ...)`。事件回调保留原有的 `onDid...` 命名,但 `onDidFailRendering` 更名为 `onDidReceiveError`: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall = paywall, + AdaptyUIFlowPlatformView( + flow = flow, onDidFinishPurchase = { view, product, result -> /* ... */ }, ) ``` 与 v3 相同,此处传入的回调(以及通过 `registerFlowEventsListener` 注册的任何观察者)会**在**全局观察者**之外**额外执行,而非取而代之——你的回调只是观察某个事件,并不会替换全局默认行为。请留意[已更改的默认行为](#default-behavior-changes):例如,全局默认行为不再在购买后关闭视图。 ### 新 API \{#new-apis\} - `AdaptyUI.setObserverModeResolver(...)` 配合 `AdaptyUIObserverModeResolver` — 在 SDK 以[观察者模式](implement-observer-mode-kmp)运行时,驱动从流程发起的购买与恢复操作。此前该功能仅在原生 iOS 和 Android SDK 中可用。请参阅[在观察者模式下呈现流程](kmp-present-flows-in-observer-mode)。 - `AdaptyUI.setSystemRequestsHandler(...)` 配合 `AdaptyUISystemRequestsHandler` — 用于处理流程中的系统请求(系统权限提示和应用评价请求)。流程目前尚未触发这些请求,因此无需注册处理器。 - 新的可选回调 `flowViewDidReceiveAnalyticEvent` 用于接收流程中的自定义分析事件。流程目前尚未向代码发送这些事件,因此无需实现该回调。 - `AdaptyUI.openWebUrl(url, openIn)` 和 `AdaptyUI.requestAppReview()` — 这两个方法分别支撑默认的 `OpenUrlAction` 处理逻辑和默认的 `handleAppReviewRequest`,因此 URL 跳转和应用评价提示均可开箱即用地以原生方式处理。仅在覆盖默认行为时才需直接调用它们。 - `AdaptyConfig.ServerCluster.CN` — 新增的服务器集群选项,与 `DEFAULT` 和 `EU` 并列,用于将应用连接至 [Adapty 中国服务器](china-cluster)。 ## 默认行为变更 \{#default-behavior-changes\} 这些变更不会导致编译错误,请在运行时进行测试: - **购买完成**:在 v3 中,默认的 `paywallViewDidFinishPurchase` 会在除 `AdaptyPurchaseResult.UserCanceled` 以外的任何购买结果后关闭视图。在 v4 中,默认的 `flowViewDidFinishPurchase` 不执行任何操作,因此**购买完成后流程会保持打开状态,直到你主动关闭它**——与 iOS 行为一致。如果你依赖之前的自动关闭逻辑,请在购买完成后自行调用 `view.dismiss()`。 - **Android 系统返回**:在 v3 中,默认的 `paywallViewDidPerformAction` 会在 `CloseAction` 和 `AndroidSystemBackAction` 时关闭视图。在 v4 中,默认行为仅处理 `CloseAction`——**系统返回按钮不再自动关闭流程**,与 iOS 保持一致(iOS 上流程无法通过系统手势关闭)。请为用户提供明确的退出方式(如 **Close** 按钮或 `on_device_back` 动作),或在 `flowViewDidPerformAction` 中自行关闭视图。 - **视图错误**:在 v3 中,默认的 `paywallViewDidFailRendering` 不执行任何操作。在 v4 中,默认的 `flowViewDidReceiveError` 会**关闭视图**——如需保持视图打开或自定义错误处理逻辑,请覆盖该方法。 - **视图仅可使用一次**:调用 `dismiss()` 后,视图将被销毁。如需再次展示流程,请重新调用 `createFlowView`。 ## 用户引导 API 弃用 \{#onboarding-api-deprecation\} 旧版用户引导 API 已在 v4.0 中弃用,请迁移至 [Flow Builder](adapty-flow-builder)。目前仍可正常使用,但将在未来版本中移除,请尽快将用户引导迁移至 Flow Builder。 已弃用的符号:`getOnboarding`、`getOnboardingForDefaultAudience`、`AdaptyUI.createOnboardingView`、`AdaptyUI.createNativeOnboardingView` 和 `AdaptyUIOnboardingsEventsObserver`。 --- # File: migration-to-kmp-315 --- --- title: "迁移指南:Adapty Kotlin Multiplatform SDK 3.15.0" description: "Adapty Kotlin Multiplatform SDK 3.15.0 的迁移步骤" --- Adapty Kotlin Multiplatform SDK 3.15.0 是一个主要版本,带来了新功能和改进,但可能需要您执行一些迁移步骤。 1. 更新观察者类和方法名称。 2. 更新备用付费墙方法名称。 3. 更新事件处理方法中的视图类名称。 ## 更新观察者类和方法名称 \{#update-observer-class-and-method-names\} 观察者类及其注册方法已重命名: ```diff - import com.adapty.kmp.AdaptyUIObserver + import com.adapty.kmp.AdaptyUIPaywallsEventsObserver - import com.adapty.kmp.models.AdaptyUIView + import com.adapty.kmp.models.AdaptyUIPaywallView - class MyAdaptyUIObserver : AdaptyUIObserver { - override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { + class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { + override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { // handle actions } } // Set up the observer - AdaptyUI.setObserver(MyAdaptyUIObserver()) + AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## 更新备用付费墙方法名称 \{#update-fallback-paywalls-method-name\} 设置备用付费墙的方法名称已更改: ```diff showLineNumbers - Adapty.setFallbackPaywalls(assetId = "fallback.json") + Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` ## 更新事件处理方法中的视图类名称 \{#update-view-class-name-in-event-handling-methods\} 所有事件处理方法现在使用新的 `AdaptyUIPaywallView` 类替代 `AdaptyUIView`: ```diff - override fun paywallViewDidAppear(view: AdaptyUIView) { + override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance } - override fun paywallViewDidDisappear(view: AdaptyUIView) { + override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance } - override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { + override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) { // Handle product selection } - override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) { + override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start } - override fun paywallViewDidFinishPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { + override fun paywallViewDidFinishPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { // Handle purchase result } - override fun paywallViewDidFailPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, error: AdaptyError) { + override fun paywallViewDidFailPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError) { // Add your purchase failure handling logic here } - override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) { + override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) { // Add your successful restore handling logic here } - override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here } - override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIView, product: AdaptyPaywallProduct?, error: AdaptyError?) { + override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError?) { // Handle web payment navigation result } - override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here } - override fun paywallViewDidFailRendering(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:53.397Z_ _Successfully processed: 48/48 files_ # REACT-NATIVE - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.398Z Total files: 45 --- # File: sdk-installation-react-native-expo --- --- title: "Install & configure Adapty React Native SDK in an Expo project" description: "Step-by-step guide on installing Adapty React Native SDK in an Expo project for subscription-based apps." --- :::important 本指南介绍如何在 **Expo 项目**中安装和配置 Adapty React Native SDK。 如果你使用的是**纯 React Native(不含 Expo)**,请参阅 [React Native 安装指南](sdk-installation-react-native-pure)。 ::: Adapty SDK 包含两个核心模块,可无缝集成到你的 React Native 应用中: - **Core Adapty**:此模块是 Adapty 在您的应用中正常运行的必要组件。 - **AdaptyUI**:如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)——一款无需编写代码即可轻松创建跨平台付费墙的工具,则需要此模块。AdaptyUI 会随核心模块一并自动激活。 如果您需要一份关于如何在 React Native 应用中实现 IAP 的完整教程,请参阅[这篇文章](https://adapty.io/blog/react-native-in-app-purchases-tutorial/)。 :::tip 想看看 Adapty SDK 如何集成到 Expo 应用中的真实示例?请参考我们的示例应用: - [Expo dev build 示例](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo):包含真实购买和付费墙编辑工具的完整功能 - [Expo Go & Web 示例](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock):使用模拟模式进行测试 ::: 如需完整的实现流程演示,也可以观看以下视频: <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/TtCJswpt2ms?si=FlFJGvpj-U33yoNK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> ## 要求 \{#requirements\} Adapty React Native SDK 要求 iOS 15.0 或更高版本。 构建 iOS 需要 **Swift 6.0** 或更高版本。[儿童模式](kids-mode-react-native) 需要 **Swift 6.1** 或更高版本。 :::info 从 SDK v3.17 开始,Adapty SDK 默认使用 Google Play Billing Library v8.0.0。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} :::important 从 v4 版本开始,Adapty React Native SDK 不再支持通过 CocoaPods 安装其原生依赖项。如果你需要 v4 或更高版本(用于 [Flow Builder](adapty-flow-builder)),请按照下方的 [Adapty SDK 4.0:启用 Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager) 进行操作。 ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) :::important 使用 [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/)(自定义开发构建版)才能在 Expo 项目中使用 Adapty。 Expo Go 不支持自定义原生模块,因此只能在[**模拟模式**](#set-up-mock-mode-for-expo-go--expo-web)下用于 UI/逻辑开发(不支持真实购买,也不支持 AdaptyUI/付费墙编辑工具渲染)。 ::: 1. 安装 Adapty SDK(同时会自动安装 `@adapty/core`): ```sh npx expo install react-native-adapty npx expo prebuild ``` 2. 使用 EAS 或本地构建为开发环境构建应用: <Tabs> <TabItem value="eas" label="EAS build" default> ```sh # For iOS eas build --profile development --platform ios # For Android eas build --profile development --platform android ``` </TabItem> <TabItem value="local" label="Local build"> ```sh # For iOS npx expo run:ios # For Android npx expo run:android ``` </TabItem> </Tabs> 3. 启动开发服务器: ```sh npx expo start --dev-client ``` ### Adapty SDK 4.0:启用 Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} React Native SDK 4.0(新增 [Flow Builder](adapty-flow-builder) 支持)需要 **React Native 0.75 或更高版本**。安装 SDK: ```sh npx expo install react-native-adapty@^4.0.0 ``` v4 通过 Swift Package Manager 而非 CocoaPods 子依赖来拉取原生 iOS SDK(`Adapty`、`AdaptyUI`、`AdaptyPlugin`)([CocoaPods 的 spec 仓库将于 2026 年 12 月变为只读](https://blog.cocoapods.org/CocoaPods-Specs-Repo/))。SPM 需要动态框架,在 Expo 中可通过 [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/) 插件来启用。将其添加到 `app.json`(或 `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` 然后安装插件并重新生成原生项目: ```sh npx expo install expo-build-properties npx expo prebuild --clean ``` 完整迁移步骤请参阅 [将 Adapty React Native SDK 迁移至 v4](migration-to-react-native-sdk-v4)。 ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 将以下代码复制到 `App.tsx` 以激活 Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `activate` 执行完成。完整的调用顺序请参阅 [React Native SDK 的调用顺序](react-native-sdk-call-order)。 ::: 现在在你的应用中配置付费墙: - 如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请参考[付费墙编辑工具快速入门](react-native-quickstart-paywalls)。 - 如果您自行构建付费墙 UI,请参考[自定义付费墙快速入门](react-native-quickstart-manual)。 :::tip 如需避免在开发环境中出现激活错误,请参考[相关技巧](#development-environment-tips)。 ::: ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果你计划使用[付费墙编辑工具](adapty-paywall-builder),则需要 AdaptyUI 模块。当你激活核心模块时,它会自动激活,无需额外操作。 ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解运行状况。可用的日志级别如下: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | 仅记录错误日志 | | `warn` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息 | | `info` | 记录错误、警告及各类信息消息 | | `verbose` | 记录调试时可能有用的所有附加信息,例如函数调用、API 请求等 | 您可以在应用中配置 Adapty 之前或期间设置日志级别: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### 数据政策 \{#data-policies\} Adapty 不会存储用户的个人数据,除非您主动发送,但您可以实施额外的数据安全策略,以符合应用商店或特定国家/地区的要求。 #### 禁用 IP 地址收集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 以禁用用户 IP 地址的收集与共享。默认值为 `false`。 使用此参数可以保护用户隐私、遵守地区数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据收集。 ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### 禁用广告 ID 的收集与共享 \{#disable-advertising-id-collection-and-sharing\} 在激活 Adapty 模块时,将 `ios.idfaCollectionDisabled`(iOS)或 `android.adIdCollectionDisabled`(Android)设置为 `true` 即可禁用广告标识符的收集。默认值为 `false`。 如需遵守 App Store/Play Store 政策、避免触发应用跟踪透明度(ATT)弹窗,或者你的应用不需要基于广告 ID 的广告归因或数据分析,可使用此参数。 ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} AdaptyUI 默认会缓存媒体文件(如图片和视频),以提升性能并减少网络流量消耗。你可以通过提供自定义配置来调整缓存设置。 使用 `mediaCache` 覆盖默认缓存设置: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | 参数 | 是否必填 | 描述 | |-----------|----------|-------------| | memoryStorageTotalCostLimit | 可选 | 内存缓存总大小,单位为字节。默认值因平台而异。 | | memoryStorageCountLimit | 可选 | 内存存储的条目数量上限。默认值因平台而异。 | | diskStorageSizeLimit | 可选 | 磁盘上的文件大小上限,单位为字节。默认值因平台而异。 | ### 启用本地访问等级(Android)\{#enable-local-access-levels-android\} 默认情况下,[本地访问等级](local-access-levels)在 iOS 上已启用,在 Android 上处于禁用状态。如需在 Android 上同样启用,请将 `localAccessLevelAllowed` 设置为 `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### 从备份恢复时清除数据 \{#clear-data-on-backup-restore\} 当 `clearDataOnBackup` 设置为 `true` 时,SDK 会检测应用是否从 iCloud 备份中恢复,并删除所有本地存储的 SDK 数据,包括已缓存的用户画像信息、产品详情和付费墙。SDK 随后会以全新状态重新初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录以及 Adapty 服务器上的用户数据不受影响。 ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## 开发环境使用技巧 \{#development-environment-tips\} #### 为 Expo Go / Expo Web 配置模拟模式 \{#set-up-mock-mode-for-expo-go--expo-web\} Expo Go 和 Expo Web 环境无法访问 Adapty 的原生模块。为了在构建和测试应用 UI 及付费墙逻辑时避免运行时错误,Adapty 提供了**模拟模式**。 ::::important 模拟模式**不是**用于测试真实购买的工具: - 它**不会打开** App Store / Google Play 购买流程,也**不会创建**真实交易。 - 它**不会渲染**使用 **Adapty 付费墙编辑工具 (AdaptyUI)** 创建的付费墙/用户引导。 - Adapty 的原生模块会被**完全绕过**——即使 Xcode/Android 构建中缺少原生 SDK 文件或 API key 无效,也不会触发错误。 如需测试真实购买和付费墙编辑工具付费墙,请使用 Expo Dev Client / 生产构建,其中模拟模式会自动禁用。 :::: **默认情况下**,SDK 会自动检测 Expo Go 和 Web 环境并启用模拟模式。除非你想自定义模拟数据,否则无需做任何配置。 模拟模式激活后: - 所有 Adapty 方法均返回模拟数据,不会向 Adapty 服务器发起网络请求。 - 默认情况下,初始模拟用户画像没有任何有效订阅。 - 默认情况下,`makePurchase(...)` 会模拟一次成功的购买并授予高级访问等级。 您可以在激活时通过 `mockConfig` 自定义模拟数据。配置格式和支持的参数请参阅[此处](https://react-native.adapty.io/interfaces/adaptymockconfig)。 ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` 如果需要在激活前调用 SDK 方法(例如 `isActivated()` 或 `setLogLevel()`),请在 `activate()` 之前调用 `enableMock()`。如果 bridge 已经初始化,此方法不会执行任何操作。 ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // 可选:传入 mockConfig 来自定义模拟数据 // 现在可以在激活前调用方法 await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` #### 出于开发目的延迟 SDK 激活 \{#delay-sdk-activation-for-development-purposes\} Adapty 在 SDK 激活时会预先获取所有必要的用户数据,从而更快地访问最新数据。 但在 iOS 模拟器中,这可能会带来问题——开发过程中模拟器经常弹出身份验证提示。虽然 Adapty 无法控制 StoreKit 的身份验证流程,但可以延迟 SDK 获取最新用户数据的请求时机。 启用 `__debugDeferActivation` 属性后,`activate` 调用会被挂起,直到你发起下一次 Adapty SDK 调用。这样一来,如果不需要身份验证数据,就不会触发多余的验证提示。 需要注意的是,**此功能仅供开发阶段使用**,因为它并不涵盖所有潜在的用户场景。在生产环境中,不应延迟激活,因为真实设备通常会记住认证数据,不会反复提示用户输入凭据。 以下是推荐的使用方式: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### 排查 React Native Fast Refresh 导致的 SDK 激活错误 \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} 在 React Native 中使用 Adapty SDK 进行开发时,你可能会遇到以下错误:`Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` 这是因为 React Native 的快速刷新(fast refresh)功能会在开发过程中触发多次激活调用。为了避免这种情况,请将 `__ignoreActivationOnFastRefresh` 选项设置为 `__DEV__`(React Native 的开发模式标志)。 ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## 故障排查 \{#troubleshooting\} #### iOS 最低版本错误 \{#minimum-ios-version-error\} 在为 iOS 构建时,你可能会看到关于 **最低 iOS 版本** 或部署目标的错误。Adapty 要求 **iOS 15.0+**。 由于 Expo 在执行 `expo prebuild` 时会自动生成 iOS 项目(包括 `Podfile`),**请勿直接编辑 `Podfile`**。应通过 `expo-build-properties` 配置插件来设置部署目标。 1. 安装插件: ```sh npx expo install expo-build-properties ``` 2. 更新你的 Expo 配置(`app.json` 或 `app.config.js`),设置 iOS 部署目标: ``` { "expo": { // ...other Expo config... "plugins": [ [ "expo-build-properties", { "ios": { // Adapty requires iOS 15.0+. "deploymentTarget": "15.0" } } ], ] } } ``` 3. 重新生成原生 iOS 项目并重新构建: ``` npx expo prebuild --clean npx expo run:ios # or `eas build -p ios` on your CI ``` #### Android 自动备份清单冲突 \{#android-auto-backup-manifest-conflict\} 当使用 Expo 并集成多个配置 Android Auto Backup 的 SDK(如 Adapty、AppsFlyer 或 expo-secure-store)时,可能会遇到 manifest 合并冲突。 典型的错误如下:`Manifest merger failed : Attribute application@fullBackupContent value=(@xml/secure_store_backup_rules) from AndroidManifest.xml:24:248-306 is also present at [io.adapty:android-sdk:3.12.0] AndroidManifest.xml:9:18-70 value=(@xml/adapty_backup_rules).` 要解决此冲突,您需要让 Adapty 插件管理 Android 备份配置。 如果您的项目也使用了 `expo-secure-store`,请禁用其自身的备份设置以避免冲突。 以下是配置 `app.json` 的方法: ```json title="app.json" { "expo": { "plugins": [ ["react-native-adapty", { "replaceAndroidBackupConfig": true }], ["expo-secure-store", { "configureAndroidBackup": false }] ] } } ``` `replaceAndroidBackupConfig` 选项默认为 `false`。启用后,Adapty 插件将接管 Android 备份规则的控制权。 如果你使用了 `expo-secure-store`,请添加 `"configureAndroidBackup": false` 以避免警告,因为 SecureStore 的备份配置现在将由 Adapty 统一管理。 :::important 此配置仅满足 Adapty、AppsFlyer 和 expo-secure-store 的备份要求。 如果项目中其他库定义了自定义备份规则,你需要手动配置这些规则。 ::: --- # File: sdk-installation-react-native-pure --- --- title: "Install & configure Adapty SDK in a pure React Native project" description: "Step-by-step guide on installing Adapty SDK on React Native for subscription-based apps." --- :::important 本指南仅适用于**纯 React Native(非 Expo)项目**。 如果你使用的是 **Expo**,请参阅 [Expo 安装指南](sdk-installation-react-native-expo)。 ::: Adapty SDK 包含两个核心模块,用于无缝集成到你的 React Native 应用中: - **Core Adapty**:此模块是 Adapty 在您的应用中正常运行所必需的。 - **AdaptyUI**:如果您使用[付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码即可轻松创建跨平台付费墙的工具),则需要此模块。AdaptyUI 会随核心模块一起自动激活。 :::tip 想看看 Adapty SDK 在移动应用中的真实集成示例吗?查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples),其中演示了完整的配置流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 要求 \{#requirements\} Adapty React Native SDK 要求 iOS 15.0 及以上版本。 构建 iOS 应用需要 **Swift 6.0** 或更高版本。[儿童模式](kids-mode-react-native) 需要 **Swift 6.1** 或更高版本。 :::info 从 SDK v3.17 开始,Adapty SDK 默认使用 Google Play Billing Library v8.0.0。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} :::important 从 v4 版本开始,Adapty React Native SDK 不再支持通过 CocoaPods 安装其原生依赖。如果你需要 v4 或更高版本(用于 [Flow Builder](adapty-flow-builder)),请参阅下方的[Adapty SDK 4.0:启用 Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager)。 ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) 1. 安装 Adapty SDK(同时会自动安装 `@adapty/core`): ```sh showLineNumbers title="Shell" # using npm npm install react-native-adapty # or using yarn yarn add react-native-adapty ``` 2. 对于 iOS,安装 pods: ```sh showLineNumbers title="Shell" cd ios && pod install ``` <details> <summary>对于 Android,如果你的 React Native 版本低于 0.73.0(点击展开)</summary> 更新 `/android/build.gradle` 文件,确保其中包含 `kotlin-gradle-plugin:1.8.0` 或更高版本的依赖: ```groovy showLineNumbers title="/android/build.gradle" ... buildscript { ... dependencies { ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0" } } ... ``` </details> ### Adapty SDK 4.0:启用 Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} React Native SDK 4.0——新增了 [Flow Builder](adapty-flow-builder) 支持——要求 **React Native 0.75 或更高版本**。安装 SDK: ```sh showLineNumbers title="Shell" npm install react-native-adapty@^4.0.0 # or using yarn yarn add react-native-adapty@^4.0.0 ``` v4 通过 Swift Package Manager 而非 CocoaPods 子依赖项来拉取原生 iOS SDK(`Adapty`、`AdaptyUI`、`AdaptyPlugin`)([CocoaPods 的 spec 仓库将于 2026 年 12 月进入只读状态](https://blog.cocoapods.org/CocoaPods-Specs-Repo/))。SPM 需要动态框架——在 `ios/Podfile` 目标中添加以下内容,然后重新安装 pods: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```sh showLineNumbers title="Shell" cd ios && pod install --repo-update ``` 如果您之前通过 CocoaPods 将 `Adapty`、`AdaptyUI` 或 `AdaptyPlugin` 作为子依赖项引入,请先从 `Podfile` 中删除所有显式的 `pod 'Adapty'`、`pod 'AdaptyUI'` 或 `pod 'AdaptyPlugin'` 行。 :::warning 从默认的静态链接切换为动态框架可能会与尚不支持模块化头文件的库产生冲突,并且与 Flipper 不兼容。详情请参阅 [将 Adapty React Native SDK 迁移至 v4](migration-to-react-native-sdk-v4)。 ::: ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 将以下代码复制到 `App.tsx` 以激活 Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important 在调用其他任何 Adapty SDK 方法之前,请等待 `activate` 完成。完整调用顺序请参阅 [React Native SDK 的调用顺序](react-native-sdk-call-order)。 ::: 现在在您的应用中配置付费墙: - 如果你使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请参阅[付费墙编辑工具快速入门](react-native-quickstart-paywalls)。 - 如果你自行构建付费墙 UI,请参阅[自定义付费墙快速入门](react-native-quickstart-manual)。 :::tip 如需避免开发环境中的激活错误,请参考[相关技巧](#development-environment-tips)。 ::: ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果你打算使用[付费墙编辑工具](adapty-paywall-builder),则需要 AdaptyUI 模块。激活核心模块时,该模块会自动激活,无需额外操作。 ## 可选配置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 设置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解运行情况。以下是可用的日志级别: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | 仅记录错误日志 | | `warn` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息 | | `info` | 记录错误、警告以及各类信息消息 | | `verbose` | 记录调试时可能有用的任何附加信息,例如函数调用、API 请求等 | 您可以在应用程序中配置 Adapty 之前或配置期间设置日志级别: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### 数据政策 \{#data-policies\} 除非您明确发送,否则 Adapty 不会存储用户的个人数据。您还可以实施额外的数据安全政策,以符合应用商店或所在国家/地区的合规要求。 #### 禁用 IP 地址收集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `ipAddressCollectionDisabled` 设置为 `true` 可禁用用户 IP 地址的收集与共享。默认值为 `false`。 使用此参数可增强用户隐私保护、遵守区域数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据收集。 ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### 禁用广告 ID 的收集与共享 \{#disable-advertising-id-collection-and-sharing\} 激活 Adapty 模块时,将 `ios.idfaCollectionDisabled`(iOS)或 `android.adIdCollectionDisabled`(Android)设置为 `true` 可禁用广告标识符的收集。默认值为 `false`。 如需遵守 App Store/Play Store 政策、避免触发应用追踪透明度提示,或者您的应用不需要基于广告 ID 的广告归因或分析,请使用此参数。 ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} 默认情况下,AdaptyUI 会缓存媒体文件(如图片和视频)以提升性能、减少网络流量。你可以通过自定义配置来调整缓存设置。 使用 `mediaCache` 覆盖默认缓存设置: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | 参数 | 是否必填 | 描述 | |-----------|----------|-------------| | memoryStorageTotalCostLimit | 可选 | 内存缓存总大小(字节)。默认值因平台而异。 | | memoryStorageCountLimit | 可选 | 内存存储的条目数量上限。默认值因平台而异。 | | diskStorageSizeLimit | 可选 | 磁盘文件大小上限(字节)。默认值因平台而异。 | ### 启用本地访问等级(Android)\{#enable-local-access-levels-android\} 默认情况下,[本地访问等级](local-access-levels)在 iOS 上已启用,在 Android 上处于禁用状态。如需在 Android 上同样启用,请将 `localAccessLevelAllowed` 设置为 `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### 恢复备份时清除数据 \{#clear-data-on-backup-restore\} 当 `clearDataOnBackup` 设置为 `true` 时,SDK 会检测应用从 iCloud 备份恢复的情况,并删除所有本地存储的 SDK 数据,包括已缓存的用户画像信息、产品详情和付费墙。之后 SDK 将以全新状态完成初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录以及 Adapty 服务器上的用户数据不受影响。 ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## 开发环境使用技巧 \{#development-environment-tips\} #### 在开发阶段延迟 SDK 激活 \{#delay-sdk-activation-for-development-purposes\} Adapty 在 SDK 激活时会预先拉取所有必要的用户数据,从而加快获取最新数据的速度。 然而,在 iOS 模拟器中,这可能会引发一个问题——开发过程中模拟器会频繁弹出身份验证提示。虽然 Adapty 无法控制 StoreKit 的身份验证流程,但可以推迟 SDK 发出请求以获取最新用户数据的时机。 通过启用 `__debugDeferActivation` 属性,激活调用将被推迟,直到你发起下一次 Adapty SDK 调用。这样可以避免在不需要认证数据时弹出不必要的提示。 需要注意的是,**此功能仅供开发使用**,因为它并不能覆盖所有潜在的用户场景。在生产环境中,不应延迟激活,因为真实设备通常会记住认证数据,不会反复提示输入凭据。 以下是推荐的使用方式: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### 排查 React Native Fast Refresh 中 SDK 激活报错的问题 \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} 在 React Native 中使用 Adapty SDK 开发时,你可能会遇到以下报错:`Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` 这是因为 React Native 的快速刷新功能在开发过程中会多次触发激活调用。要避免这一问题,请将 `__ignoreActivationOnFastRefresh` 选项设置为 `__DEV__`(React Native 的开发模式标志)。 ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### 为本地测试设置模拟模式 \{#set-up-mock-mode-for-local-testing\} 对于本地开发和测试,您可以启用模拟模式,无需沙盒 App Store/Google Play 账号,从而加快迭代速度。模拟模式完全绕过 Adapty 的原生模块,返回模拟数据。 :::important 模拟模式**不是**用于测试真实购买的工具: - 它**不会打开** App Store / Google Play 的购买流程,也**不会创建**真实交易。 - 它**不会渲染**使用 **Adapty Paywall Builder (AdaptyUI)** 创建的付费墙/用户引导。 - Adapty 的原生模块会被**完全绕过**——即使 Xcode/Android 构建中缺少原生 SDK 文件或 API 密钥无效,也不会触发错误。 - 不会向 Adapty 服务器发送任何数据。 如需测试真实购买和付费墙编辑工具创建的付费墙,请禁用模拟模式并使用沙盒账户。 ::: 要启用模拟模式,请将 `enableMock` 设置为 `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { enableMock: true, }); ``` 当模拟模式处于激活状态时: - 所有 Adapty 方法均返回模拟数据,不会向 Adapty 服务器发起网络请求。 - 默认情况下,初始模拟用户画像不包含任何活跃订阅。 - 默认情况下,`makePurchase(...)` 会模拟一次成功的购买并授予高级访问权限。 你可以在激活时通过 `mockConfig` 自定义模拟数据。配置格式和支持的参数详见[此处](https://react-native.adapty.io/interfaces/adaptymockconfig)。 ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` 如果你需要在激活之前调用 SDK 方法(例如 `isActivated()` 或 `setLogLevel()`),请在 `activate()` 之前使用 `enableMock()`。如果 bridge 已经初始化,此方法将不执行任何操作。 ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // Optional: pass mockConfig to customize mock data // Now you can call methods before activation await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` ## 故障排查 \{#troubleshooting\} #### iOS 最低版本错误 \{#minimum-ios-version-error\} 如果遇到 iOS 最低版本错误,请更新你的 Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Android 自动备份清单冲突 \{#android-auto-backup-manifest-conflict\} 部分 SDK(包括 Adapty)会自带 Android 自动备份配置。如果多个 SDK 都定义了备份规则,Android 清单合并工具可能会报错,提示 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup` 相关问题。 常见错误示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note 以下更改应在你的 Android 平台目录(通常位于项目的 `android/` 文件夹)中进行。 ::: 要解决此问题,你需要: - 告知清单合并工具使用应用自身的备份相关属性值。 - 创建备份规则文件,将 Adapty 的规则与其他 SDK 的规则合并。 #### 1. 在清单中添加 `tools` 命名空间 \{#1-add-the-tools-namespace-to-your-manifest\} 在 `AndroidManifest.xml` 文件中,确保根标签 `<manifest>` 包含 tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. 在 `<application>` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在同一个 `AndroidManifest.xml` 文件中,更新 `<application>` 标签,使应用提供最终属性值,并告知清单合并工具替换库中的值: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 Android 项目的 `res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并。Android 根据系统版本使用不同的备份规则格式,同时创建两个文件可确保应用所支持的所有 Android 版本都能正常兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请替换或添加你应用中实际使用的其他 SDK 的规则。 ::: **Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Android 11 及更低版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### 在 Android 上从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app-in-android\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会以错误的方式重新创建或复用该 Activity,导致购买结果丢失或被视为已取消。 为确保购买流程正常运行,请仅为启动购买流程的 Activity 使用 `standard` 或 `singleTop` 启动模式,避免使用其他模式。 在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Podfile 中 SWIFT_VERSION 覆盖导致的 Swift 6 构建错误 \{#swift-6-build-errors-caused-by-podfile-swift-version-override\} 在为 iOS 构建 React Native 应用时,你可能会在 Adapty pod 目标上遇到 Swift 6 编译错误。常见症状包括:`AdaptyUIBuilderLogic` 中的 `@Sendable` 不匹配、Adapty 类型缺少 `Sendable` 一致性,或 actor 隔离错误。 Adapty pod 声明了 `s.swift_version = '6.0'`,需要使用 Swift 6 进行构建。你自己的应用代码可以继续使用 Swift 5——只有 Adapty pod 目标(`Adapty`、`AdaptyUI`、`AdaptyUIBuilder`、`AdaptyLogger`、`AdaptyPlugin`)需要以 Swift 6 构建。 最常见的原因是 `ios/Podfile` 中的 `post_install` 钩子为每个 pod target 重写了 `SWIFT_VERSION`: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **解决方法**:将 Adapty 的 pod target 排除在覆盖范围之外: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` 然后从 `ios/` 目录运行 `pod install` 并重新构建。 如需验证,打开 `ios/Pods/Pods.xcodeproj`,选择 `Adapty` pod 目标 → **Build Settings** → **Swift Language Version**,确认显示为 **Swift 6**。 --- # File: react-native-quickstart-paywalls --- --- title: "在 React Native SDK 中通过 Flow Builder 启用付费功能" description: "通过 Adapty Flow Builder 启用应用内购买的快速入门指南。" --- 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可购买的一切内容(订阅、消耗型商品、永久授权) - [**流程**](adapty-flow-builder) – 向用户展示产品的屏幕序列,在无代码的 Flow Builder 中构建,SDK 通过 `getFlow` 获取。如果你更倾向于用自己的代码构建 UI,请使用付费墙代替——详见[手动实现付费墙](react-native-quickstart-manual)。 - [**版位**](placements) – 在应用中展示流程的位置和时机(例如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 来请求它们。这样可以轻松运行 A/B 测试,并向不同用户展示不同的流程。 Adapty 为您提供三种在应用中开启内购的方式。请根据您的应用需求选择其中一种: | 实现方式 | 复杂度 | 使用场景 | |---|---|---| | Adapty Flow Builder | ✅ 简单 | 你[在无代码编辑工具中创建完整的、可购买的流程](quickstart-paywalls)。Adapty 自动渲染并处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建付费墙 | 🟡 中等 | 你在应用代码中实现付费墙 UI,但仍从 Adapty 获取 flow 对象,以保持产品供给的灵活性。参见[指南](react-native-quickstart-manual)。 | | 观察者模式 | 🔴 复杂 | 你已有自己的购买处理基础设施并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。参见[文章](observer-vs-full-mode)。 | :::important **以下步骤说明如何实现在 Adapty Flow Builder 中创建的流程。** 如果你更倾向于自行构建付费墙 UI,请参阅[手动实现付费墙](react-native-quickstart-manual)。 ::: 要显示在 Adapty Flow Builder 中创建的流程,在你的应用代码中,只需完成以下操作: 1. **获取流程**:从 Adapty 获取流程。 2. **展示流程,Adapty 将自动处理购买**:在应用中显示该视图。 3. **处理按钮操作**:将用户交互与应用的响应关联起来。例如,当用户点击按钮时打开链接或关闭流程。 ## 开始之前 \{#before-you-start\} 在开始之前,请先完成以下步骤: 1. 在 Adapty 看板中将您的应用连接到 [App Store](initial_ios) 和/或 [Google Play](initial-android)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建流程并将产品添加到其中](create-paywall)。 4. [创建版位并将流程添加到其中](create-placement)。 5. 在您的应用代码中[安装并激活 Adapty SDK](sdk-installation-reactnative)。本指南使用 Adapty React Native SDK v4 API。 ## 1. 获取流程 \{#1-get-the-flow\} 你的流程与在看板中配置的版位相关联。版位允许你为不同的目标受众运行不同的流程,或进行 [A/B 测试](ab-tests)。 要获取在 Adapty 付费墙编辑工具中创建的流程,请通过 `getFlow` 方法,使用[版位](placements) ID 获取 `flow` 对象。该流程包含显示所需的 UI 元素和样式。 ```typescript showLineNumbers title="React Native" try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); // the requested flow } catch (error) { // handle the error } ``` ## 2. 展示流程 \{#display-the-flow\} 现在,当你已经获取到流程后,只需添加几行代码即可展示它。 <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 要将流程嵌入现有的组件树中,可以直接在 React Native 组件层级中使用 `AdaptyFlowView` 组件: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onPurchaseCompleted={onPurchaseCompleted} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 要将流程显示为独立屏幕,请使用 `createFlowView` 方法创建一个 `view`,设置其事件处理程序,然后调用 `view.present()`。每个 `view` 只能使用一次。如果需要再次显示该流程,请再次调用 `createFlowView` 以创建新的 `view` 实例。 ```typescript showLineNumbers title="React Native" try { const view = await createFlowView(flow); view.setEventHandlers({ onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> :::tip 有关如何展示流程的更多详情,请参阅我们的[指南](react-native-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#handle-button-actions\} 当用户点击流程中的按钮时,React Native SDK 会自动处理购买、恢复购买、关闭流程以及打开 URL 等操作。 但是,其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作。或者,你可能希望覆盖其默认行为。 例如,以下是关闭按钮的默认行为。你无需在代码中添加它,但这里展示了如何在需要时实现。 <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,直接在 `AdaptyFlowView` 组件中处理操作: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, // allow the flow to close [], ); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>( (actionId) => false, [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="模态呈现"> 对于模态呈现,使用 `setEventHandlers` 实现事件处理程序: ```typescript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` </TabItem> </Tabs> :::tip 阅读我们关于如何处理按钮[操作](react-native-handle-paywall-actions)和[事件](react-native-handling-events-1)的指南。 ::: ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 你的流程已准备好在应用中展示。[测试购买](react-native-test),确保可以在流程中完成测试购买。 接下来,你需要[检查用户的访问等级](react-native-check-subscription-status),以确保向正确的用户展示流程或开放付费功能。 ## 完整示例 \{#full-example\} 以下是将本指南中所有步骤整合到应用中的完整示例。 <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```javascript showLineNumbers title="React Native (TSX)" export default function FlowScreen() { const [flow, setFlow] = useState(null); const loadFlow = async () => { try { const flowData = await adapty.getFlow('YOUR_PLACEMENT_ID'); setFlow(flowData); } catch (error) { console.warn('Error loading flow:', error); } }; const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, [], ); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); useEffect(() => { loadFlow(); }, []); return ( <View style={{ flex: 1 }}> {flow ? ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onPurchaseCompleted={onPurchaseCompleted} /> ) : ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Load Flow" onPress={loadFlow} /> </View> )} </View> ); } ``` </TabItem> <TabItem value="standalone" label="弹窗展示"> ```javascript showLineNumbers title="React Native" export default function FlowScreen() { const showFlow = async () => { try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const view = await createFlowView(flow); view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } }; // you can add a button to manually trigger the flow for testing purposes return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Show Flow" onPress={showFlow} /> </View> ); } ``` </TabItem> </Tabs> --- # File: react-native-check-subscription-status --- --- title: "在 React Native SDK 中检查订阅状态" description: "了解如何在 React Native 应用中使用 Adapty 检查订阅状态。" --- 要决定用户是否可以访问付费内容或查看付费墙,您需要检查用户画像中的[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种方式: - 如果需要立即获取最新的用户画像数据(例如在应用启动时)或希望强制更新,可调用 `getProfile`。 - 设置**自动用户画像更新**,以在订阅状态发生变化时自动刷新本地副本。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方式是使用 `getProfile` 方法访问用户画像: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 要在应用中自动接收用户画像更新: 1. 使用 `adapty.addEventListener('onLatestProfileLoad')` 监听用户画像变化——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。 2. 当该方法被调用时,存储更新后的用户画像数据,以便在整个应用中使用,无需额外发起网络请求。 ```javascript class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addEventListener('onLatestProfileLoad', (profile) => { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['premium']?.isActive ?? false; } } ``` :::note 当您的应用启动时,Adapty 会自动调用 `onLatestProfileLoad` 事件监听器,即使设备处于离线状态也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当您需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。这种方式适用于以下场景:应用启动、进入付费专区,或在展示特定内容之前。 ```javascript const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## 后续步骤 \{#next-steps\} 现在您已了解如何跟踪订阅状态,接下来请学习如何[使用用户画像](react-native-quickstart-identify),以确保用户能够访问他们已付费的内容。 --- # File: react-native-quickstart-identify --- --- title: "在 React Native SDK 中识别用户" description: "在 React Native 中设置 Adapty 进行应用内订阅管理的快速入门指南。" --- :::important 如果您有自己的身份验证系统,本指南正是为您准备的。您将在此学习如何在 Adapty 中管理用户画像,使其与您现有的身份验证系统保持一致。 ::: 您管理用户购买记录的方式取决于应用的身份验证模式: - 如果您的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户](#anonymous-users)部分。 - 如果您的应用已有(或将有)后端身份验证,请参阅[已识别用户](#identified-users)部分。 **核心概念**: - **用户画像**是 SDK 正常运行所必需的实体,由 Adapty 自动创建。 - 用户画像可以是匿名的**(无 customer user ID)**,也可以是已识别的**(有 customer user ID)**。 - 您提供 **customer user ID** 是为了将 Adapty 中的用户画像与您内部的身份验证系统进行交叉关联。 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|-----------------------------------|---------------------------------------------------| | **购买管理** | 通过商店层级恢复购买 | 通过客户用户 ID 在多设备间保留购买记录 | | **用户画像管理** | 每次重新安装时创建新用户画像 | 跨会话和设备使用同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用重新安装后依然保留 | ## 匿名用户 \{#anonymous-users\} 如果你没有后端身份验证,**则无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时被激活,Adapty 会**为该用户创建一个新的用户画像**。 2. 当用户在应用内完成购买后,该购买记录会**关联到其 Adapty 用户画像及商店账号**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建一个新的匿名用户画像**。 4. 如果用户之前在您的应用中有过购买记录,默认情况下,SDK 激活时会自动从 App Store 同步其购买历史。 因此,对于匿名用户,每次安装都会创建新的用户画像,但这并不是问题,因为在 Adapty 分析中,你可以[配置什么情况下会被视为新安装](general#4-installs-definition-for-analytics)。 :::note 备份恢复与重新安装的处理方式不同。默认情况下,当用户从备份恢复时,SDK 会保留缓存数据,不会创建新的用户画像。你可以通过 `clearDataOnBackup` 设置来调整此行为。[了解更多](sdk-installation-react-native-pure#clear-data-on-backup-restore)。 ::: 对于匿名用户,需要按**设备 ID** 统计安装量。此时,每次在设备上安装应用都会被计为一次安装,包括重新安装。 ## 已识别用户 \{#identified-users\} 在应用中识别用户有两种方式: - [**登录/注册时:**](#during-loginsignup) 如果用户在应用启动后才登录,请在他们完成身份验证时调用 `identify()`,并传入 customer user ID。 - [**SDK 初始化时:**](#during-the-sdk-activation) 如果应用启动时已有存储的 customer user ID,可在调用 `activate()` 时直接传入。 :::important 默认情况下,当 Adapty 收到来自某个 Customer User ID 的购买请求,而该 ID 当前已与另一个 Customer User ID 关联时,访问等级将被共享,两个用户画像均可获得付费访问权限。你可以将此设置配置为将付费访问权限从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[此文章](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 登录/注册时 \{#during-loginsignup\} 如果你在应用启动后才识别用户身份(例如用户登录或注册之后),请使用 `identify` 方法设置其 customer user ID。 - 如果你**之前从未使用过该 customer user ID**,Adapty 会自动将其关联到当前用户画像。 - 如果你**之前已使用该 customer user ID 识别过用户**,Adapty 会切换到与该 customer user ID 关联的用户画像。 :::important 每位用户的 Customer User ID 必须唯一。如果将该参数硬编码为固定值,所有用户将被视为同一个人。 ::: 在调用其他 SDK 方法之前,请务必先 `await` `identify`。并发调用会产生 `#3006 profileWasChanged` 错误,或导致操作落在匿名用户画像上。详见 [React Native SDK 调用顺序](react-native-sdk-call-order)。 ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` ### 在 SDK 激活期间 \{#during-the-sdk-activation\} 如果在激活 SDK 时已知客户用户 ID,可以直接在 `activate` 方法中传入,无需单独调用 `identify`。 如果已知客户用户 ID,但在激活之后才设置,则意味着在激活时 Adapty 会创建一个新的匿名用户画像,只有在调用 `identify` 后才会切换到已有的用户画像。 您可以传入已有的客户用户 ID(之前使用过的),也可以传入一个新的。如果传入新 ID,激活时创建的新用户画像将自动与该客户用户 ID 关联。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装量是基于设备 ID 统计的。 设备 ID 代表应用从商店安装到设备上的一次安装实例,仅在应用重新安装后才会重新生成。 它与此次安装是首次还是重复安装无关,也与是否使用了已有的客户用户 ID 无关。 创建用户画像(在 SDK 激活或退出登录时)、登录,或在不重新安装应用的情况下升级应用,均不会产生额外的安装事件。 如果您希望根据唯一用户而非设备来统计安装量,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. }); ``` ### 注销用户 \{#log-users-out\} 如果您有用于注销用户的按钮,请使用 `logout` 方法。 :::important 注销用户将为该用户创建新的匿名用户画像。 ::: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录用户购买 \{#allow-purchases-without-login\} 如果你的用户在登录前和登录后都可以进行购买,你需要确保他们登录后仍能保留访问权限: 1. 当未登录用户发起购买时,Adapty 会将其绑定到匿名用户画像 ID。 2. 当用户登录账户后,Adapty 会切换到使用其已识别的用户画像。 - 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 会将该 customer user ID 分配给当前用户画像,从而保留所有购买历史记录。 - 如果是已存在的 customer user ID(该 customer user ID 已与某个用户画像关联),则需要在用户画像切换后获取实际的访问等级。你可以在识别完成后立即调用 [`getProfile`](react-native-check-subscription-status),也可以[监听用户画像更新](react-native-check-subscription-status)以自动同步数据。 ## 下一步 \{#next-steps\} 恭喜!您已在应用中成功实现了应用内购买逻辑!祝您的应用变现一切顺利! 想要深入探索 Adapty 的更多功能,可以参考以下主题: - [**测试**](troubleshooting-test-purchases):确保一切按预期正常运行 - [**用户引导**](react-native-onboardings):通过用户引导吸引用户并提升留存率 - [**集成**](configuration):只需一行代码即可与营销归因和数据分析服务完成集成 - [**设置自定义用户画像属性**](react-native-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,从而发起 A/B 测试或向不同用户展示不同的付费墙 --- # File: adapty-sdk-integration-skill-react-native --- --- title: "使用 SDK 集成技能将 Adapty 集成到您的 React Native 应用" description: "使用 adapty-sdk-integration 技能,通过 AI 编程工具将 Adapty SDK 端到端集成到您的 React Native 应用中。" --- [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 :::important 该技能目前处于测试阶段。如果出现卡顿或异常行为,请改用[分步集成指南](adapty-cursor-react-native)——它会引导你的 AI 工具逐步完成每个阶段所需的文档。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-react-native --- --- title: "借助 AI 将 Adapty 集成到 React Native 应用" description: "一步步指引你使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具将 Adapty 集成到 React Native 应用中。" --- 本指南将带你一步步完成 React Native 应用与 Adapty 的集成,借助 AI 编程工具——按正确顺序将 Adapty 文档喂给它即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始之前:看板配置 \{#before-you-start-dashboard-setup\} 在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。你可以通过交互式 LLM 技能来完成,也可以通过看板手动操作。 ### 技能方式(推荐) \{#skill-approach-recommended\} Adapty CLI 技能让你的 LLM 可以直接设置应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。你只需在看板中[连接你的应用商店](integrate-payments)即可。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加技能后,在你的 agent 中运行 `/adapty-cli`。它将引导你完成每个步骤——包括何时打开看板连接你的应用商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你更倾向于手动配置,以下是写代码前需要准备的内容。你的 LLM 无法自动查询看板中的值——需要你自行提供。 1. **连接应用商店**:在 Adapty 看板中,前往 **App settings → General**,将 App Store 和 Google Play 都连接上(如果你的应用同时支持两个平台)。这是购买功能正常运行的必要条件。 [连接应用商店](integrate-payments) 2. **复制你的 Public SDK key**:在 Adapty 看板中,进入 **App settings → General**,找到 **API keys** 部分。在代码中,这就是你传给 `adapty.activate("YOUR_PUBLIC_SDK_KEY")` 的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,进入 **Products** 页面。你无需在代码中直接引用产品——Adapty 会通过付费墙将产品下发给用户。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,先在 **Paywalls** 页面创建付费墙,再在 **Placements** 页面将其分配到版位。在代码中,版位 ID 就是传给 `adapty.getPaywall("YOUR_PLACEMENT_ID")` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板的 **Products** 页面中为每个产品进行配置。在代码中,通过 `profile.accessLevels['premium']?.isActive` 检查对应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户根据所购产品可以访问不同的功能(例如 `basic` 方案和 `pro` 方案),请在开始编写代码前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五项信息后,就可以开始写代码了。把以下内容告诉你的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",这样它就能生成正确的初始化和付费墙获取代码。 ::: ### 准备就绪后再设置 \{#set-up-when-ready\} 以下内容不是开始编码的必要条件,但随着集成的成熟,你会需要它们: - **A/B 测试**:在 **Placements** 页面进行配置。无需更改代码。 [A/B 测试](ab-tests) - **额外的付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。 - **分析集成**:在 **Integrations** 页面进行配置。设置因集成而异。参见[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 向你的 LLM 提供 Adapty 文档 \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) [Context7](https://context7.com) 是一个 MCP 服务器,可让你的 LLM 直接访问最新的 Adapty 文档。LLM 会根据你的问题自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 以及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在你的提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the React Native SDK ``` :::warning 尽管 Context7 省去了手动粘贴文档链接的步骤,但实施顺序仍然很重要。请按照下方的[实施步骤](#implementation-walkthrough)逐步操作,确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 你可以以纯文本 Markdown 格式访问任意 Adapty 文档。在其 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-react-native.md](https://adapty.io/docs/zh/adapty-cursor-react-native.md)。 下面[实施演练](#implementation-walkthrough)中的每个阶段都包含一个"发送给你的 LLM"代码块,其中包含可粘贴的 `.md` 链接。 如需一次获取更多文档,请参阅下面的[索引文件和平台专属子集](#plain-text-doc-index-files)。 ## 实施演练 \{#implementation-walkthrough\} 本指南的其余部分按实施顺序介绍 Adapty 集成。每个阶段都包含要发送给 LLM 的文档、完成后应看到的结果以及常见问题。 ### 规划集成方案 \{#plan-your-integration\} 在动手写代码之前,先让 LLM 分析你的项目并制定实施计划。如果你使用的 AI 工具支持规划模式(如 Cursor 或 Claude Code 的 plan mode),建议先用规划模式,让 LLM 在编写代码前同时读取你的项目结构和 Adapty 文档。 告诉 LLM 你采用的购买实现方式——这决定了它应该参考哪些指南: - [**Adapty 付费墙编辑工具**](adapty-paywall-builder):在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染。 - [**手动创建的付费墙**](react-native-making-purchases):自行编写付费墙 UI 代码,但仍使用 Adapty 获取产品并处理购买流程。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅使用 Adapty 进行数据分析和集成。 不确定该选哪种?请查看[快速入门中的对比表格](react-native-quickstart-paywalls)。 ### 安装与配置 SDK \{#install-and-configure-the-sdk\} 使用 npm(或 yarn)添加 Adapty SDK 依赖,并通过你的 Public SDK key 激活它。这是一切的基础——没有这一步,其他功能都无法使用。 我们为 Expo 和纯 React Native 项目分别提供了独立的安装指南——请根据你的项目类型选择对应的指南。 **指南:** - [使用 Expo 安装](sdk-installation-react-native-expo) - [使用纯 React Native 安装](sdk-installation-react-native-pure) :::tip[Checkpoint] - **预期结果:** 应用在 iOS 和 Android 上均能成功构建并运行。Metro bundler 日志中可以看到 Adapty 激活日志。 - **常见问题:** 出现 "Public API key is missing" → 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 展示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙,展示它,并处理购买事件。所需的指南取决于你处理购买的方式。 建议边开发边在沙盒中测试每次购买,不要等到最后再测。设置说明请参阅[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **指南:** - [使用付费墙开启购买功能(快速入门)](react-native-quickstart-paywalls) - [获取付费墙编辑工具付费墙及其配置](react-native-get-pb-paywalls) - [展示付费墙](react-native-present-paywalls) - [处理付费墙事件](react-native-handling-events-1) - [响应按钮操作](react-native-handle-paywall-actions) Read these Adapty docs before writing code: - https://adapty.io/docs/zh/react-native-quickstart-paywalls.md - https://adapty.io/docs/zh/react-native-get-pb-paywalls.md - https://adapty.io/docs/zh/react-native-present-paywalls.md - https://adapty.io/docs/zh/react-native-handling-events-1.md - https://adapty.io/docs/zh/react-native-handle-paywall-actions.md :::tip[Checkpoint] - **预期结果:** 付费墙正常显示,并包含你配置的产品。点击某个产品会触发沙盒购买弹窗。 - **注意事项:** 付费墙为空或 `getPaywall` 报错 → 请确认版位 ID 与看板中完全一致,且该版位已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="手动付费墙"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](react-native-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-react-native) - [渲染基于远程配置的付费墙](present-remote-config-paywalls-react-native) - [发起购买](react-native-making-purchases) - [恢复购买](react-native-restore-purchase) 将以下内容发送给你的 LLM: ``` 在编写代码之前,请先阅读这些 Adapty 文档: - https://adapty.io/docs/zh/react-native-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-react-native.md - https://adapty.io/docs/zh/present-remote-config-paywalls-react-native.md - https://adapty.io/docs/zh/react-native-making-purchases.md - https://adapty.io/docs/zh/react-native-restore-purchase.md ``` :::tip[Checkpoint] - **Expected:** 你的自定义付费墙已显示从 Adapty 获取的产品。点击某个产品会触发沙盒购买对话框。 - **Gotcha:** 产品数组为空 → 请确认该付费墙在看板中已分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **指南:** - [Observer 模式概览](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-react-native) - [在 Observer 模式中上报交易](report-transactions-observer-mode-react-native) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode-react-native.md - https://adapty.io/docs/zh/report-transactions-observer-mode-react-native.md ``` :::tip[检查点] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易应出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认你已向 Adapty 上报交易,并且两个应用商店均已配置服务器通知。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买后,检查用户画像中是否有激活的访问等级,以限制高级内容的访问。 **指南:** [检查订阅状态](react-native-check-subscription-status) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/react-native-check-subscription-status.md ``` :::tip[检查点] - **预期结果:** 沙盒购买后,`profile.accessLevels['premium']?.isActive` 返回 `true`。 - **注意事项:** 购买后 `accessLevels` 为空 → 检查产品在看板中是否已分配访问等级。 ::: ### 关联用户 \{#identify-users\} 将应用的用户账户与 Adapty 用户画像绑定,确保购买记录在多设备间持久保存。 :::important 如果你的应用无需登录,跳过此步骤。 ::: **指南:** [关联用户](react-native-quickstart-identify) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/react-native-quickstart-identify.md ``` :::tip[Checkpoint] - **预期结果:** 调用 `adapty.identify("your-user-id")` 后,看板的 **Profiles** 部分会显示你的自定义用户 ID。 - **注意事项:** 请在激活之后、获取付费墙之前调用 `identify`,以避免匿名用户画像归因问题。 ::: ### 准备发布 \{#prepare-for-release\} 沙盒环境中的集成测试通过后,请逐项检查发布清单,确保一切都已准备好投入生产。 **指南:**[发布清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[Checkpoint] - **预期:** 所有检查项均已确认:商店连接、服务器通知、购买流程、访问等级检查以及隐私合规要求。 - **注意:** 服务器通知缺失 → 请在 **App settings → iOS SDK** 中配置 App Store 服务器通知,并在 **App settings → Android SDK** 中配置 Google Play 实时开发者通知。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单个页面范围的更广泛上下文,我们提供了列出或汇总所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面的 `.md` 链接,是让网站对 LLM 友好的[新兴标准](https://llmstxt.org/)。注意,对于某些 AI 智能体(如 ChatGPT),你需要先下载 `llms.txt` 再上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):整个 Adapty 文档站合并为单个文件。体积很大——仅在需要完整内容时使用。 - React Native 专属的 [`react-native-llms.txt`](https://adapty.io/docs/zh/react-native-llms.txt) 和 [`react-native-llms-full.txt`](https://adapty.io/docs/zh/react-native-llms-full.txt):平台专属子集,相比完整站点可节省 token 消耗。 --- # File: react-native-get-pb-paywalls --- --- title: "获取 flow 与付费墙 - React Native" description: "在 React Native 应用中从 Adapty 获取 flow 和付费墙。" --- <SDKv4> <MethodPromo method="getFlow" /> 在[设计好流程或付费墙编辑工具付费墙](adapty-paywall-builder)之后,您可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体步骤如下所述。 请注意,本主题涉及流程和付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙和产品](fetch-paywalls-and-products-react-native)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在开始于移动应用中展示流程和付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建流程/付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将您的流程/付费墙添加到其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-reactnative)。 </details> ## 获取流程/付费墙 \{#fetch-flowpaywall\} 如果你已通过 Flow Builder 或付费墙编辑工具设计了流程或付费墙,则无需在移动端代码中手动处理其渲染逻辑来向用户展示它。这类流程或付费墙已包含展示内容与展示方式的完整定义。即便如此,你仍需通过版位获取其 ID 和视图配置,然后在移动端进行呈现。 尽早获取流程或付费墙并创建其[视图](react-native-get-pb-paywalls#fetch-the-view-configuration) — 最好在展示之前提前完成。`createFlowView` 方法会加载视图配置,并在后台开始下载和缓存图片。调用越早,这些下载就有越充裕的时间完成。等到真正展示流程或付费墙时,其配置和图片可能已经缓存完毕,随时可以显示。 要获取流程或付费墙,请使用 `getFlow` 方法: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(placementId); // the requested flow/paywall } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |-------------------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,当缓存存在时优先返回缓存数据。这种情况下,用户可能无法获取最新数据,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保你始终获取最新版本的付费墙,同时即便在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若达到超时时间,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。</p><p>对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。若不设置限制,请使用 `TimeInterval.INFINITE`。</p> | 响应参数: | 参数 | 描述 | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | 一个 `AdaptyFlow` 对象,包含流程的标识符(`id`、`variationId`)、名称、版位、付费墙实验变体(`paywalls`)以及任何远程配置(`remoteConfigs`)。 | ## 获取视图配置 \{#fetch-the-view-configuration\} :::important 请确保在编辑工具中启用 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 如果版位是在 **Flow Builder** 或 **Paywall Builder** 中设计的,Adapty 会为你渲染 UI。使用 `createFlowView` 创建视图,然后[展示流程或付费墙](react-native-present-paywalls)。如果版位是没有编辑工具 UI 的自定义付费墙,请改为[将其作为远程配置付费墙处理](present-remote-config-paywalls-react-native)。 在 React Native SDK 中,直接调用 `createFlowView` 即可——无需提前获取视图配置。 :::warning `createFlowView` 方法的返回结果只能使用一次。如需再次使用,请重新调用 `createFlowView` 方法。若不重新创建而直接调用两次,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | 必填 | 一个 `AdaptyFlow` 对象,用于获取所需流程/付费墙的控制器。 | | **customTags** | 可选 | 定义自定义标签及其解析值的字典。自定义标签作为内容中的占位符,在流程/付费墙中动态替换为指定字符串,以实现个性化内容。详情请参阅付费墙编辑工具中自定义标签相关主题。 | | **prefetchProducts** | 可选 | 开启后可优化产品在屏幕上的显示时机。设为 `true` 时,AdaptyUI 将自动预取所需产品。默认值:`false`。 | | **android.enableSafeArea** | 可选 | 仅限 Android(iOS 上会被忽略)。以嵌套对象形式传入:`android: { enableSafeArea: true }`。设为 `true` 时,流程视图会应用安全区域内边距。在模态弹窗(`createFlowView` + `present()`)场景下默认为 `true`,在嵌入式 `AdaptyFlowView` 组件中默认为 `false`。默认值适用于大多数场景。 | :::note 如果您使用多种语言,请了解如何添加[流程本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码([查看详情](react-native-localizations-and-locale-codes))。 ::: 获取视图后,请[展示流程/付费墙](react-native-present-paywalls)。 ## 为默认目标受众获取流程或付费墙以加快获取速度 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} 通常,流程和付费墙的获取几乎是即时的,无需担心速度问题。但当你拥有大量目标受众和版位,且用户网络连接较弱时,获取流程或付费墙的时间可能会比预期更长。在这种情况下,你可能希望展示一个默认的流程或付费墙,以确保良好的用户体验,而不是让用户看到空白页面。 为了解决这个问题,你可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程或付费墙。但请务必了解,推荐的做法是通过 `getFlow` 方法来获取流程或付费墙,详情请参阅上方的[获取流程/付费墙](#fetch-flowpaywall)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺陷: - **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么必须设计能够支持当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家/地区、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的 flow 或付费墙获取速度,请按以下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow/paywall } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>不过,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后依然保留,只有在应用重新安装或手动清除时才会被清空。</p> | ## 自定义素材 \{#customize-assets\} 要自定义流程/付费墙中的图片和视频,请实现自定义素材。 主图和视频有预定义 ID:`hero_image` 和 `hero_video`。在自定义素材包中,你通过这些 ID 来定位对应元素并自定义其行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载时先显示本地预览图。 - 在播放视频前先展示预览图。 :::important 要使用此功能,请将 Adapty React Native SDK 更新至 3.8.0 或更高版本。 ::: 以下是通过简单字典提供自定义素材资源的示例: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createFlowView(flow, { customAssets }) ``` :::note 如果找不到某个资源,流程/付费墙将回退到其默认外观。 ::: </SDKv4> <SDKv3> 在 Adapty 看板中使用新版付费墙编辑工具[完成付费墙的视觉设计](adapty-paywall-builder)后,你可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体如下所述。 :::warning 新版付费墙编辑工具需要 React Native SDK 3.0 或更高版本。 ::: 在开始在移动应用中展示付费墙之前,请注意本主题适用于通过付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中为远程配置付费墙获取付费墙和产品](fetch-paywalls-and-products-react-native)主题。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中开始展示付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品加入其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙加入其中](create-placement)。 4. 在移动应用中安装 [Adapty SDK](sdk-installation-reactnative)。 </details> ## 获取用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),就无需在移动应用代码中手动编写渲染逻辑来向用户展示它。这类付费墙同时包含展示内容和展示方式。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动应用中将其呈现出来。 为确保最佳性能,请尽早获取付费墙及其[视图配置](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够的时间下载图片。 使用 `getPaywall` 方法获取付费墙: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(placementId, locale); // the requested paywall } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |-------------------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为语言代码,由一个或两个子标签组成,之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关区域代码及推荐使用方式的更多信息,请参阅[本地化与区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全可靠的。</p><p></p><p>请注意,缓存在应用重启后依然保留,只有在应用卸载重装或手动清理时才会清除。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙加载,并在 CDN 不可用时提供独立的备用服务器。该机制旨在确保您始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>该值限制此方法的超时时间。达到超时后,将返回缓存数据或本地备用数据。</p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。</p><p>对于 Android:您可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。如需不设限制,请使用 `TimeInterval.INFINITE`。</p> | ## 响应参数 \{#response-parameters\} | 参数 | 描述 | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | 一个 [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙的视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中启用了 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 获取付费墙后,检查它是否包含 `ViewConfiguration`,这表明它是使用付费墙编辑工具创建的。这将指导您如何展示该付费墙。如果存在 `ViewConfiguration`,将其作为付费墙编辑工具付费墙处理;否则,[将其作为远程配置付费墙处理](present-remote-config-paywalls-react-native)。 在 React Native SDK 中,直接调用 `createPaywallView` 方法,无需手动预先获取视图配置。 :::warning `createPaywallView` 方法的返回结果只能使用一次。如果需要再次使用,请重新调用 `createPaywallView` 方法。不重新创建而重复调用可能导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createPaywallView} from 'react-native-adapty/dist/ui'; if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { //use your custom logic } ``` 参数: | 参数 | 是否必填 | 说明 | | :------------------- | :------- | :----------------------------------------------------------- | | **paywall** | 必填 | `AdaptyPaywall` 对象,用于获取目标付费墙的控制器。 | | **customTags** | 可选 | 定义自定义标签及其对应值的字典。自定义标签作为付费墙内容中的占位符,在运行时动态替换为特定字符串,实现付费墙内容的个性化展示。详情请参阅付费墙编辑工具中的自定义标签相关主题。 | | **prefetchProducts** | 可选 | 启用后可优化产品在屏幕上的显示时机。设为 `true` 时,AdaptyUI 将自动预取所需产品。默认值:`false`。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder)以及如何正确使用语言区域代码,详见[此处](react-native-localizations-and-locale-codes)。 ::: 获取视图后,[展示付费墙](react-native-present-paywalls)。 ## 为默认目标受众获取付费墙以加快获取速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} 通常情况下,付费墙几乎可以立即获取,因此您无需担心加快此过程。但是,当您拥有大量目标受众和付费墙,且用户网络连接较弱时,获取付费墙可能需要比预期更长的时间。在这种情况下,您可能希望展示默认付费墙,以确保流畅的用户体验,而非不展示任何付费墙。 为解决这一问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法获取指定版位针对**所有用户**目标受众的付费墙。但请务必了解,推荐的方式是通过 `getPaywall` 方法获取付费墙,详见上方[获取付费墙信息](#fetch-paywall-designed-with-paywall-builder)部分。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在一些显著缺点: - **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会面临挑战。您要么必须设计支持当前(旧版)版本的付费墙,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。 - **失去定向能力**:所有用户都将看到针对**所有用户**目标受众设计的同一付费墙,这意味着您将失去个性化定向(包括基于国家/地区、营销归因或自定义属性的定向)。 如果您愿意接受这些缺点以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请使用[上述](#fetch-paywall-designed-with-paywall-builder) `getPaywall` 方法。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note `getPaywallForDefaultAudience` 方法从 React Native SDK 2.11.2 版本开始可用。 ::: | 参数 | 是否必需 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必需 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个以减号(**-**)分隔的子标签组成。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及推荐用法,请参阅[本地化与语言区域代码](react-native-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐使用此方式,因为它可确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,以便在缓存数据存在时直接返回。这种方式下,用户可能无法获取最新数据,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在卸载应用或手动清理时才会被清除。</p> | ## 自定义素材资源 \{#customize-assets\} 要自定义付费墙中的图片和视频,请实现自定义素材资源。 主图和视频具有预定义的 ID:`hero_image` 和 `hero_video`。在自定义素材资源包中,您通过这些 ID 定位对应元素并自定义其行为。 对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,您可以: - 向部分用户展示不同的图片或视频。 - 在远程主图加载期间展示本地预览图。 - 在播放视频前展示预览图。 :::important 要使用此功能,请将 Adapty React Native SDK 更新至 3.8.0 或更高版本。 ::: 以下示例展示了如何通过简单的字典来提供自定义资源: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }) ``` :::note 如果找不到资源,付费墙将回退到其默认外观。 ::: </SDKv3> --- # File: react-native-present-paywalls --- --- title: "展示流程与付费墙 - React Native" description: "在 React Native 应用中使用 Adapty 向用户展示流程与付费墙。" --- <SDKv4> <MethodPromo method="getFlow" label="展示流程与付费墙" /> 如果您已在 Flow Builder 中创建了流程或付费墙,则无需在移动端应用代码中手动处理其渲染逻辑来向用户展示它。此类流程已包含展示内容及展示方式的完整配置。 开始之前,请确认以下几点: 1. 您已[创建流程或付费墙](create-paywall)。 2. 您已将其添加到[版位](placements)。 3. 您已[获取流程并准备好视图](react-native-get-pb-paywalls)。 :::warning 本指南仅适用于**流程和付费墙编辑工具付费墙**,需要 SDK v4.0 或更高版本。展示流程的方式与远程配置付费墙有所不同。 - 如需展示**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls)。 ::: Adapty React Native SDK 提供两种展示流程和付费墙的方式: - **React 组件**:嵌入式组件,可集成到应用的架构和导航体系中。 - **模态弹窗** ## React 组件 \{#react-component\} 要将流程嵌入到现有的组件树中,可以直接在 React Native 组件层级里使用 `AdaptyFlowView` 组件。嵌入式组件让你能够将其集成到应用的架构和导航系统中。 :::tip `AdaptyFlowView` 组件在渲染时会创建视图,此时会加载配置和图片。如需预加载,可在应用更早的位置针对同一流程调用 [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration)。组件随后会复用缓存数据,无需等待下载即可完成渲染。 ::: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const flowParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} params={flowParams} style={styles.flow} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## 模态展示 \{#modal-presentation\} 要将流程作为独立屏幕显示,请在由 [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次显示该流程,请重新调用 `createFlowView` 创建一个新的 `view` 实例。 :::warning 禁止在不重新创建的情况下复用同一个 `view`,否则将导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important 多次调用 `setEventHandlers` 会覆盖你之前设置的处理函数,替换掉这些特定事件的默认处理函数及先前设置的处理函数。 ::: ### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,可以配置流程在 iOS 上的呈现方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 两个值。 ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请使用 `timerId`,本例中为 `CUSTOM_TIMER_NY`,即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。它能确保应用动态更新计时器并显示正确的值,例如 `13d 09h 03m 34s`(计算方式为计时器的结束时间(如元旦)减去当前时间)。 <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const flowParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyFlowView flow={flow} params={flowParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` </TabItem> </Tabs> 在此示例中,`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 会确保你的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(由计时器的结束时间(如元旦)减去当前时间计算得出)。 ## 显示对话框 \{#show-dialog\} 在 Android 上展示流程视图时,请使用此方法代替原生 alert 对话框。在 Android 上,普通 RN 弹窗会显示在流程视图后面,导致用户看不到。此方法可确保对话框在所有平台上都能正确显示在流程上方。 ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## 用新订阅替换旧订阅 \{#replace-one-subscription-with-another\} 当用户在 Android 上已有活跃订阅的情况下尝试购买新订阅时,你可以通过在创建 flow 视图时传入订阅更新参数来控制新购买的处理方式。若要用新订阅替换当前订阅,请在 `createFlowView` 中使用 `productPurchaseParams`,并传入 `oldSubVendorProductId` 和 `prorationMode` 参数。 ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = flow.paywalls .flatMap((variation) => variation.productIdentifiers) .map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> 如果你使用付费墙编辑工具自定义了付费墙,就无需在移动应用代码中手动渲染它来向用户展示。这类付费墙已包含展示内容和展示方式的完整配置。 开始之前,请确认: 1. 你已[创建付费墙](create-paywall)。 2. 你已将付费墙添加到[版位](placements)。 3. 你已[获取付费墙并准备好视图](react-native-get-pb-paywalls)。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 SDK v3.0 或更高版本。不同版本付费墙编辑工具设计的付费墙以及远程配置付费墙的展示流程有所不同。 - 如需展示**远程配置付费墙**,请参阅[展示远程配置设计的付费墙](present-remote-config-paywalls)。 ::: Adapty React Native SDK 提供两种展示付费墙的方式: - **React 组件**:嵌入式组件,可集成到应用的架构和导航系统中。 - **模态展示** ## React 组件 \{#react-component\} :::note **React 组件**方式需要 SDK 3.14.0 或更高版本。 ::: 要将付费墙嵌入到现有的组件树中,可以直接在 React Native 组件层级中使用 `AdaptyPaywallView` 组件。嵌入式组件允许你将其集成到应用的架构和导航系统中。 :::note 在 Android 上,如果付费墙未延伸到状态栏后方,其顶部可能会出现视觉遮罩。我们建议您为付费墙关闭此效果。请参阅[付费墙顶部的视觉遮罩(Android)](#visual-overlay-at-the-top-of-the-paywall-android)。 ::: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const paywallParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} params={paywallParams} style={styles.paywall} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## 弹窗展示 \{#modal-presentation\} 要将付费墙作为独立页面展示,请在由 [`createPaywallView`](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 上调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示付费墙,请重新调用 `createPaywallView` 创建一个新的 `view` 实例。 :::warning 禁止在不重新创建的情况下复用同一个 `view`,否则会触发 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); // Optional: handle paywall events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important 多次调用 `setEventHandlers` 会覆盖你之前设置的处理器,包括默认处理器和针对特定事件已设置的处理器。 ::: ### 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传入 `iosPresentationStyle` 参数,可以配置付费墙在 iOS 上的展示方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 两个值。 ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## 使用开发者自定义计时器 \{#use-developer-defined-timer\} 要在移动应用中使用开发者自定义计时器,请使用 `timerId`——在本示例中为 `CUSTOM_TIMER_NY`,即你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。这样可以确保应用动态地以正确的值更新计时器——例如 `13d 09h 03m 34s`(由计时器的结束时间(如元旦)减去当前时间计算得出)。 <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const paywallParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyPaywallView paywall={paywall} params={paywallParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` </TabItem> </Tabs> 在此示例中,`CUSTOM_TIMER_NY` 是你在 Adapty 看板中设置的开发者自定义计时器的 **Timer ID**。`timerResolver` 会确保你的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(由计时器的结束时间(如元旦)减去当前时间得出)。 ## 显示对话框 \{#show-dialog\} 在 Android 上展示付费墙视图时,请使用此方法替代原生的 alert 对话框。在 Android 上,普通的 RN alert 会出现在付费墙视图的后面,导致用户看不到。此方法可确保对话框在所有平台上都能正确显示在付费墙的上层。 ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## 用新订阅替换旧订阅 \{#replace-one-subscription-with-another\} 当用户在 Android 上已有有效订阅时尝试购买新订阅,你可以在创建付费墙视图时传入订阅更新参数,控制新购买的处理方式。如需将当前订阅替换为新订阅,请在 `createPaywallView` 中使用 `productPurchaseParams`,并传入 `oldSubVendorProductId` 和 `prorationMode` 参数。 ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = paywall.productIdentifiers.map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createPaywallView(paywall, { productPurchaseParams }); ``` ## 故障排查 \{#troubleshooting\} ### 付费墙顶部的视觉遮罩层(Android)\{#visual-overlay-at-the-top-of-the-paywall-android\} :::note 此设置从 React Native SDK 3.15.5 起支持,且仅适用于纯 React Native 项目。 如果你使用的是 Expo 托管工作流,则无法直接添加此 Android 资源。要应用此设置,必须创建一个自定义 Expo config plugin,将相应的 Android 资源添加其中,并在 app.config.js 中注册。这是必要的,因为 Expo 会替你管理原生 Android 项目。 ::: 如果 `AdaptyPaywallView` 没有延伸到状态栏后面,其顶部可能仍会出现视觉遮罩。要去除该遮罩,请在你的应用中添加以下布尔资源: 1. 前往 `android/app/src/main/res/values`。如果不存在 `bools.xml` 文件,请新建一个。 2. 添加以下资源: ```xml <resources> <bool name="adapty_paywall_enable_safe_area_paddings">false</bool> </resources> ``` 请注意,该更改会全局应用于应用中的所有付费墙。 </SDKv3> --- # File: react-native-handle-paywall-actions --- --- title: "响应流程操作 - React Native" description: "使用 Adapty 在 React Native 中处理流程和付费墙的按钮操作,提升应用变现效果。" --- <SDKv4> 如果你正在使用 Adapty 的 Flow Builder 或付费墙编辑工具构建流程或付费墙,正确设置按钮至关重要: 1. 在[流程/付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中处理每个已分配的操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **购买、恢复、流程/付费墙关闭以及 URL 跳转均由系统自动处理。** 你可以配置这些默认行为,或为自定义操作实现相应的响应逻辑。 ::: :::note SDK 提供了一个 `onRequestPermission` 流程处理器,用于处理系统权限请求(如推送通知或相机访问权限)。目前流程尚未触发此类请求,所以暂时无需实现它。 ::: ## 关闭流程和付费墙 \{#close-flows-and-paywalls\} 要添加一个关闭流程或付费墙的按钮: 1. 在编辑工具中,添加一个按钮并为其分配 **Close** 动作。 2. 在应用代码中,实现一个处理 `close` 动作的处理器,用于关闭流程或付费墙。 :::info 在 React Native SDK 中,`close` 动作默认会触发关闭流程或付费墙。不过,如果需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。 ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过各自的事件处理器 props 来处理关闭操作: ```javascript function MyPaywall({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现关闭处理函数: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow flow or paywall closing } }); ``` </TabItem> </Tabs> ## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\} :::tip 如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加 **Link** 元素,并像处理带有 **Open URL** 操作的按钮一样处理它。 ::: 要添加一个从流程或付费墙中打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在编辑工具中,添加一个按钮,为其指定 **Open URL** 操作,并输入要打开的 URL。 2. 在应用代码中,实现一个 `openUrl` 操作的处理程序,用于在浏览器中打开接收到的 URL。 :::info 在 React Native SDK 中,`openUrl` 动作默认会触发 URL 的打开操作。不过,你可以在代码中根据需要覆盖此行为。 ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React component,通过事件处理器 prop 来处理 URL 打开操作: ```javascript function MyPaywall({ flow }) { const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,实现 URL 处理器: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep flow or paywall open }, }); ``` </TabItem> </Tabs> ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在编辑工具中,添加一个按钮,为其指定 **Custom** 操作,并分配一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现一个处理器。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮来显示另一个流程或付费墙: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过事件处理器 prop 来处理自定义操作: ```javascript function MyPaywall({ flow }) { const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态弹出形式,实现自定义操作处理程序: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </TabItem> </Tabs> </SDKv4> <SDKv3> 如果你使用 Adapty 付费墙编辑工具构建付费墙,请务必正确设置按钮: 1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。 2. 在应用代码中编写逻辑,处理你分配的每个操作。 本指南介绍如何在代码中处理自定义操作和已有操作。 :::warning **只有购买、恢复购买、关闭付费墙和打开 URL 这四类操作会自动处理。** 其他所有按钮操作都需要在应用代码中自行实现响应逻辑。 ::: ## 关闭付费墙 \{#close-paywalls\} 要为付费墙添加关闭按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在应用代码中,实现 `close` 操作的处理函数以关闭付费墙。 :::info 在 React Native SDK 中,`close` 操作默认会触发关闭付费墙。不过,如有需要,你可以在代码中覆盖这一行为。例如,关闭一个付费墙时可以触发打开另一个付费墙。 ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过各自的事件处理器 props 来处理关闭操作: ```javascript function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现关闭处理器: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow paywall closing } }); ``` </TabItem> </Tabs> ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果您想添加一组链接(例如使用条款和购买恢复),可以在付费墙编辑工具中添加一个 **Link** 元素,其处理方式与具有 **Open URL** 操作的按钮相同。 ::: 要添加一个可以从付费墙打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入您想要打开的 URL。 2. 在应用代码中,实现一个处理 `openUrl` 操作的处理器,用于在浏览器中打开接收到的 URL。 :::info 在 React Native SDK 中,`openUrl` 操作默认会触发打开 URL 的行为。不过,如有需要,你可以在代码中覆盖此行为。 ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过事件处理器 prop 来处理 URL 打开操作: ```javascript function MyPaywall({ paywall }) { const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,实现 URL 处理器: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, }); ``` </TabItem> </Tabs> ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 动作。 2. 在应用代码中,实现一个处理 `login` 动作的处理器来识别用户身份。 <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过事件处理器 prop 来处理登录: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'login') { navigation.navigate('Login'); } }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现登录处理程序: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { navigation.navigate('Login'); } } }); ``` </TabItem> </Tabs> ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并为其指定一个 ID。 2. 在应用代码中,为你创建的操作 ID 实现相应的处理逻辑。 例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮来展示另一个付费墙: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,通过事件处理器 prop 来处理自定义操作: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another paywall } }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="模态展示"> 对于模态展示,请实现自定义动作处理器: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </TabItem> </Tabs> </SDKv3> --- # File: react-native-handling-events-1 --- --- title: "处理流程与付费墙事件 - React Native" description: "使用 Adapty SDK 在 React Native 应用中处理流程和付费墙事件。" --- <SDKv4> :::important 本指南涵盖购买、恢复、产品选择和流程渲染的事件处理。你也可以设置按钮处理(关闭流程、打开链接、自定义操作等)。详情请参阅[按钮操作处理指南](react-native-handle-paywall-actions)。 ::: 流程和使用 Flow Builder 构建的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,你的应用可以对这些事件做出响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及流程中与购买相关操作的通知。请参阅以下内容了解如何响应这些事件。 要在移动应用中控制或监控流程页面上发生的操作,请实现事件处理器: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,你可以通过 `AdaptyFlowView` 组件中各自的事件处理器 props 来处理事件: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url, openIn) => { adapty.openWebUrl(url, openIn); return false; }, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现事件处理方法。 :::important 多次调用 `setEventHandlers` 会覆盖你设置的处理器,替换掉这些特定事件的默认处理器和之前设置的处理器。 ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl(url, openIn); return false; // Keep flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "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", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "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", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "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" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onError { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onAppeared { //Record the event } // onDisappeared { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> 您可以只注册需要的事件处理器,忽略不需要的。这样就不会创建多余的事件监听器。所有事件处理器均为可选项。 事件处理器返回一个布尔值。若返回 `true`,则视为展示流程已完成,流程页面随即关闭,并移除该视图的所有事件监听器。 某些事件处理器有默认行为,你可以按需覆盖: - `onCloseButtonPress`:按下关闭按钮时关闭流程。 - `onUrlPress`:打开点击的 URL 并保持流程开启。 - `onAndroidSystemBack`(仅适用于模态展示):按下 **Back** 键时保持流程开启。返回 `true` 可关闭流程。 - `onRestoreCompleted`:恢复购买成功后保持流程开启。返回 `true` 可关闭流程。 - `onPurchaseCompleted`:购买完成后保持流程开启。返回 `true` 可关闭流程。 - `onError`:流程渲染失败时关闭流程。 ### 事件处理器 \{#event-handlers\} | 事件处理器 | 描述 | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | 当用户执行自定义操作时触发,例如点击[自定义按钮](paywall-buttons)。 | | **onUrlPress** | 当用户点击流程中的 URL 时触发。 | | **onAndroidSystemBack** | 仅限模态弹出模式:当用户点击 Android 系统 **Back** 按钮时触发。 | | **onCloseButtonPress** | 当关闭按钮可见且用户点击时触发。建议在此处理器中关闭流程页面。 | | **onPurchaseCompleted** | 购买完成时触发,无论成功、被用户取消还是待审批。购买成功时会返回更新后的 `AdaptyProfile`。用户取消和待支付(如需要家长审批)也会触发此事件,而非 `onPurchaseFailed`。 | | **onPurchaseStarted** | 当用户点击"购买"操作按钮以开始购买流程时触发。 | | **onPurchaseFailed** | 因错误导致购买失败时触发(如支付限制、无效产品、网络故障、交易验证失败)。用户取消或待支付不会触发此事件,而是触发 `onPurchaseCompleted`。 | | **onRestoreStarted** | 当用户开始恢复购买流程时触发。 | | **onRestoreCompleted** | 购买恢复成功时触发,并返回更新后的 `AdaptyProfile`。如果用户已具备所需的 `accessLevel`,建议关闭页面。请参阅[订阅状态](react-native-listen-subscription-changes)了解如何检查。 | | **onRestoreFailed** | 恢复流程失败时触发,并返回 `AdaptyError`。 | | **onProductSelected** | 当用户在流程视图中选择任意产品时触发,可监控用户购买前的选择情况。 | | **onError** | 视图渲染过程中发生错误时触发,并返回 `AdaptyError`。此类错误不应出现,如遇到请告知我们。 | | **onLoadingProductsFailed** | 产品加载失败时触发,并返回 `AdaptyError`。如果在创建视图时未设置 `prefetchProducts: true`,AdaptyUI 会自行从服务器获取所需对象。 | | **onAppeared** | 当流程显示给用户时触发。在 iOS 上,当用户点击流程内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button)并在应用内浏览器中打开网页付费墙时也会触发。 | | **onDisappeared** | 仅限模态弹出模式:当用户关闭流程时触发。在 iOS 上,从流程中打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)在应用内浏览器中消失时也会触发。 | | **onWebPaymentNavigationFinished** | 尝试打开[网页付费墙](web-paywall)进行购买后触发,无论成功或失败。 | | **onAnalytics** | 保留用于流程中的自定义分析事件。目前流程不会向您的代码发送此类事件,无需实现。 | | **onRequestAppReview** | 保留用于流程中的应用评价请求。目前流程不会触发应用评价请求,无需实现。 | | **onRequestPermission** | 保留用于流程中的系统权限请求(如推送通知或相机访问)。目前流程不会触发权限请求,无需实现。 | | **onObserverPurchaseInitiated** | 仅限观察者模式:当用户在流程中点击购买按钮时触发。Adapty 不会执行购买——请使用您自己的购买代码完成购买,然后将交易报告给 Adapty。详见下方[在观察者模式下处理购买](#handle-purchases-in-observer-mode)。 | | **onObserverRestoreInitiated** | 仅限观察者模式:当用户在流程中点击恢复按钮时触发。Adapty 不会执行恢复——请自行完成恢复,然后报告已恢复的交易。详见下方[在观察者模式下处理购买](#handle-purchases-in-observer-mode)。 | ### 在观察者模式下处理购买 \{#handle-purchases-in-observer-mode\} 如果你以[观察者模式](implement-observer-mode-react-native)(`observerMode: true`)激活了 SDK 并展示了 Adapty 渲染的流程,SDK 不会自动为你发起购买。当用户点击购买或恢复按钮时,SDK 会调用 `onObserverPurchaseInitiated` 或 `onObserverRestoreInitiated`。请用你自己的代码执行购买或恢复操作,通过提供的回调驱动流程的加载指示器,并在完成后[向 Adapty 上报交易](report-transactions-observer-mode-react-native)。 ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // show the flow's loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction(transactionId)) .finally(() => onFinishPurchase()); // hide the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi() .finally(() => onFinishRestore()); return false; }, }); ``` </SDKv4> <SDKv3> :::important 本指南介绍购买、恢复、产品选择和付费墙渲染的事件处理方式。你还需要实现按钮处理功能(关闭付费墙、打开链接等)。详情请参阅[按钮操作处理指南](react-native-handle-paywall-actions)。 ::: 通过[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。不过,这些付费墙会生成一些事件供应用响应,包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上购买相关操作的通知。请参阅下文了解如何响应这些事件。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.0 或更高版本。 ::: 要控制或监控移动应用中付费墙屏幕上发生的流程,请实现以下事件处理器: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,你可以通过 `AdaptyPaywallView` 组件中各自对应的事件处理器属性来处理事件: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现事件处理方法。 :::important 多次调用 `setEventHandlers` 会覆盖你提供的处理程序,同时替换这些特定事件的默认处理程序和之前设置的处理程序。 ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onRenderingFailed(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, onPaywallShown() { /***/ }, onPaywallClosed() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "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", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "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", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "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" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onPaywallShown { //Record the event } // onPaywallClosed { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> 您可以只注册所需的事件处理器,忽略不需要的。这样就不会创建未使用的事件监听器。所有事件处理器均为可选。 事件处理器返回一个布尔值。若返回 `true`,则视为展示流程已完成,付费墙界面随即关闭,该视图的事件监听器也会被移除。 某些事件处理程序具有默认行为,您可以根据需要覆盖它们: - `onCloseButtonPress`:点击关闭按钮时关闭付费墙。 - `onUrlPress`:打开点击的 URL 并保持付费墙不关闭。 - `onAndroidSystemBack`(仅适用于模态展示):按下 **Back** 按钮时关闭付费墙。 - `onRestoreCompleted`:恢复成功后关闭付费墙。 - `onPurchaseCompleted`:除非用户取消,否则关闭付费墙。 - `onRenderingFailed`:付费墙渲染失败时关闭付费墙。 ### 事件处理程序 \{#event-handlers\} | 事件处理器 | 描述 | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | 当用户执行自定义操作时触发,例如点击[自定义按钮](paywall-buttons)。 | | **onUrlPress** | 当用户点击付费墙中的 URL 时触发。 | | **onAndroidSystemBack** | 仅限模态展示:当用户点击 Android 系统的 **Back** 按钮时触发。 | | **onCloseButtonPress** | 当关闭按钮可见且用户点击它时触发。建议在此处理器中关闭付费墙页面。 | | **onPurchaseCompleted** | 当购买完成时触发,无论是成功、被用户取消还是待审批。购买成功时会提供更新后的 `AdaptyProfile`。用户取消和待处理的付款(例如需要家长批准)会触发此事件,而非 `onPurchaseFailed`。 | | **onPurchaseStarted** | 当用户点击"购买"操作按钮以启动购买流程时触发。 | | **onPurchaseFailed** | 当购买因错误失败时触发(例如付款限制、无效产品、网络故障、交易验证失败)。用户取消或待处理付款不会触发此事件,那些情况会触发 `onPurchaseCompleted`。 | | **onRestoreStarted** | 当用户开始购买恢复流程时触发。 | | **onRestoreCompleted** | 当购买恢复成功时触发,并提供更新后的 `AdaptyProfile`。如果用户已获得所需的 `accessLevel`,建议关闭页面。请参阅[订阅状态](react-native-listen-subscription-changes)主题了解如何检查。 | | **onRestoreFailed** | 当恢复流程失败时触发,并提供 `AdaptyError`。 | | **onProductSelected** | 当付费墙视图中的任意产品被选中时触发,可让你监控用户在购买前选择的内容。 | | **onRenderingFailed** | 当视图渲染过程中发生错误时触发,并提供 `AdaptyError`。此类错误通常不应发生,如果遇到,请告知我们。 | | **onLoadingProductsFailed** | 当产品加载失败时触发,并提供 `AdaptyError`。如果你在创建视图时未设置 `prefetchProducts: true`,AdaptyUI 将自行从服务器获取所需对象。 | | **onPaywallShown** | 当付费墙展示给用户时触发。在 iOS 上,当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button)并在应用内浏览器中打开网页付费墙时,也会触发此事件。 | | **onPaywallClosed** | 仅限模态展示:当用户关闭付费墙时触发。在 iOS 上,当从付费墙在应用内浏览器中打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)从屏幕上消失时,也会触发此事件。 | | **onWebPaymentNavigationFinished** | 在尝试打开[网页付费墙](web-paywall)进行购买后触发,无论成功或失败。 | </SDKv3> --- # File: react-native-use-fallback-paywalls-expo --- --- title: "在 Expo 项目中使用备用付费墙" description: "通过 react-native-adapty 配置插件在 Expo React Native 项目中配置备用付费墙。" --- :::important 本指南适用于 **Expo 项目**。 如果你使用的是**纯 React Native(非 Expo)**,请参阅[纯 React Native 备用付费墙指南](react-native-use-fallback-paywalls-pure)。 ::: 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: Adapty SDK 从**原生**包中读取备用文件——iOS 资源存放在 `.app` 包内,Android 条目位于 `android/app/src/main/assets/` 下。在 Expo 项目中,`npx expo prebuild --clean` 每次运行都会重新生成这些目录,因此无法手动将文件放入其中。`react-native-adapty` 配置插件会自动将文件注入原生包。 :::tip 可以在 [`FocusJournalExpo` 示例应用](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo)中查看完整的可运行示例。 ::: ## 配置 \{#configuration\} 1. 将备用付费墙 JSON 文件放在项目的任意位置——通常与其他资源文件放在一起: ``` <your-project>/ └── assets/ ├── ios_fallback.json └── android_fallback.json ``` 2. 在 `app.json`(或 `app.config.js`)中,为 `react-native-adapty` 条目添加 `fallbackFile` 选项。每个平台的键都是可选的——只需配置你需要的平台即可: ```json title="app.json" { "expo": { "plugins": [ [ "react-native-adapty", { "fallbackFile": { "ios": "./assets/ios_fallback.json", "android": "./assets/android_fallback.json" } } ] ] } } ``` :::note Adapty 为每个平台导出不同的备用付费墙 JSON 文件——iOS 使用 Apple 产品 ID,Android 使用 Google Play 产品 ID。请为每个平台指定对应的文件。 ::: 3. 重新生成原生项目: ```sh title="Shell" npx expo prebuild ``` 该插件会将 iOS 文件添加到 Xcode 项目的 bundle 资源中,并将 Android 文件复制到 `android/app/src/main/assets/`。预构建输出中会包含如下信息: ``` [react-native-adapty] Registered ios_fallback.json as iOS bundle resource [react-native-adapty] Copied android_fallback.json to android assets/ ``` 4. 在运行时向 SDK 注册该文件: ```typescript showLineNumbers title="App.tsx" import { adapty } from 'react-native-adapty'; await adapty.activate('PUBLIC_SDK_KEY'); await adapty.setFallback({ ios: { fileName: 'ios_fallback.json' }, android: { relativeAssetPath: 'android_fallback.json' }, }); ``` 传给 `setFallback` 的文件名必须与 `fallbackFile` 中配置的文件基本名称保持一致。 :::important `setFallback` 必须在 SDK 获取目标流程、付费墙或用户引导之前执行。 ::: ## 验证 \{#verification\} 运行 `npx expo prebuild` 后,检查两个平台: - **Android**:列出 `android/app/src/main/assets/` 的内容。`fallbackFile.android` 配置的文件应该存在,且仅适用于 iOS 的文件名不应出现在这里。 - **iOS**:在 `ios/<ProjectName>.xcodeproj/project.pbxproj` 中搜索 iOS 文件名。它应出现在 `PBXFileReference`、`Resources` 组和 `PBXResourcesBuildPhase` 中。仅适用于 Android 的文件名不应出现在 `project.pbxproj` 中。 --- # File: react-native-use-fallback-paywalls-pure --- --- title: "在纯 React Native 项目中使用备用付费墙" description: "在纯 React Native(非 Expo)项目中配置备用付费墙。" --- :::important 本指南适用于**纯 React Native(非 Expo)项目**。 如果你使用的是 **Expo**,请参阅 [Expo 备用付费墙指南](react-native-use-fallback-paywalls-expo)。 ::: 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} ### Android 1. 将备用配置文件添加到您的应用程序中。选择以下目录之一: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** 注意:`res/raw` 文件夹有特殊的文件命名规范(必须以字母开头,不能使用大写字母,不能使用下划线以外的特殊字符,文件名中不能有空格)。 2. 更新 `FileLocation` 常量的 `android` 属性: * 如果文件位于 `assets` 目录下,传入文件相对于该目录的路径。 * 如果文件位于 `res/raw` 目录下,传入不含扩展名的文件名。 ### iOS 1. 将备用 JSON 文件添加到项目包中:在 XCode 中打开 **File** 菜单,选择 **Add Files to "YourProjectName"** 选项。 2. 将配置文件的名称传递给 `FileLocation` 常量的 `ios` 属性。 ## 示例 \{#example\} <Tabs groupId="current-os" queryString> <TabItem value="current" label="Current (v3.8+)" default> ```typescript showLineNumbers //after v3.8 const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallback(fileLocation); ``` </TabItem> <TabItem value="old" label="Legacy (before v3.8)"> ```typescript showLineNumbers //Legacy (before v3.8) const paywallsLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallbackPaywalls(paywallsLocation); ``` </TabItem> </Tabs> | 参数 | 描述 | | :------------------- | :------------------------------------------------------- | | **fileLocation** | 表示备用配置文件位置的对象。 | --- # File: react-native-localizations-and-locale-codes --- --- title: "在 React Native SDK 中使用本地化和语言代码" description: "了解如何使用 Adapty SDK 在 React Native 应用中对付费墙进行本地化。" --- <SDKv4> ## 为什么这很重要 \{#why-this-is-important\} 当 Adapty 为流程选择本地化内容,以及当你读取自定义付费墙的远程配置时,语言区域代码就会发挥作用。 语言区域代码比较复杂,在不同平台上可能存在差异,因此 Adapty 在其支持的所有平台上统一采用一套内部标准。了解这套标准,有助于你预判用户最终会收到哪种本地化内容。 ## Adapty 中的语言代码标准 \{#locale-code-standard-at-adapty\} Adapty 的语言代码采用略微修改过的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。例如:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 查找与用户语言区域匹配的本地化内容时,会按以下步骤处理: 1. 将语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. Adapty 查找与完整语言区域代码完全匹配的本地化内容 3. 如果未找到匹配项,Adapty 取第一个连字符之前的子字符串(例如 `pt-br` 取 `pt`),并查找匹配的本地化内容 4. 如果仍未找到匹配项,Adapty 返回默认的 `en` 本地化内容 这样一来,`'pt_BR'`、`pt-BR` 和 `pt-br` 都会解析为同一个本地化内容。 ## 实现本地化 \{#implementing-localizations\} 在 SDK v4 中,获取 flow 时无需传入语言代码。 - **流程编辑工具和付费墙编辑工具付费墙**:Adapty 会根据设备语言以及您在编辑工具中配置的本地化自动解析本地化设置。使用 `createFlowView` 渲染 flow,无需传入语言代码。 - **自定义(远程配置)付费墙**:`getFlow` 会在 `flow.remoteConfigs` 中返回所有已配置的本地化内容。每条记录包含一个 `lang` 语言代码和一个 `data` 对象。请自行选择与用户匹配的条目,并实现相应的回退逻辑: ```typescript showLineNumbers const flow = await adapty.getFlow('placement_id'); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` 以上语言代码匹配规则描述了 Adapty 如何规范化每个远程配置中存储的 `lang` 代码。 </SDKv4> <SDKv3> ## 为什么这很重要 \{#why-this-is-important\} 有几种场景会用到语言区域代码——例如,当你需要为应用的当前语言版本获取正确的付费墙时。 由于语言区域代码较为复杂,且在不同平台之间可能存在差异,我们为所有支持的平台制定了统一的内部标准。但正因为这些代码比较复杂,了解你实际发送给服务器的内容是什么、以及后续处理流程如何,就显得尤为重要——只有这样,你才能始终获取到预期的语言版本。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} 在语言代码方面,Adapty 使用略经调整的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。示例:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 发来的语言区域代码请求,并开始查找对应的付费墙本地化版本时,会依次执行以下步骤: 1. 将传入的语言区域字符串转换为小写,并将所有下划线(`_`)替换为连字符(`-`) 2. 查找与完整语言区域代码完全匹配的本地化版本 3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 取 `pt`),再次查找匹配的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 这样,发送 `'pt_BR'` 的 iOS 设备、发送 `pt-BR` 的 Android 设备以及发送 `pt-br` 的另一台设备都会得到相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果你在考虑本地化问题,很可能已经在项目中处理过本地化字符串文件了。如果是这样,我们建议在每个本地化文件中添加一个键值对,其值为对应的 Adapty 语言区域代码,然后在调用 SDK 时提取该键对应的值,示例如下: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` 这样,您可以完全掌控应用中每位用户获取到的本地化内容。 ## 另一种实现本地化的方式 \{#implementing-localizations-the-other-way\} 你也可以不为每个本地化显式指定语言区域代码,而是从设备中提取语言区域代码,例如通过 [`react-native-localize`](https://github.com/zoontek/react-native-localize) 来实现类似(但并不完全相同)的效果: ```javascript showLineNumbers const fetchPaywall = async () => { // getLocales() returns the user's preferred locales in BCP-47 format (e.g., 'en-US', 'pt-BR') const locale = RNLocalize.getLocales()[0].languageTag; // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` 请注意,由于以下几个原因,我们不推荐使用这种方式: 1. 在 iOS 上,首选语言与当前区域语言环境并不相同。如果想让本地化正确匹配,要么依赖 Apple 的解析逻辑——使用推荐的本地化字符串文件方式时开箱即用——要么自行实现同样的逻辑。 2. 设备语言环境可能与你在 Adapty 中配置的任何本地化都不匹配。在这种情况下,SDK 会回退到语言标签前缀匹配,若仍无匹配则最终回退到 `en`——但这未必是你希望为该用户显示的默认语言。 Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: react-native-web-paywall --- --- title: "实现网页付费墙" description: "了解如何使用 Adapty SDK 在 React Native 应用中实现网页付费墙。" --- :::important 在开始之前,请确保您已[在看板中配置了网页付费墙](web-paywall),并安装了 Adapty SDK 3.6.1 或更高版本。 ::: ## 打开网页付费墙 \{#open-web-paywalls\} 如果你使用的是自己开发的付费墙,需要通过 SDK 方法来处理网页付费墙。`.openWebPaywall` 方法会执行以下操作: 1. 生成一个唯一 URL,使 Adapty 能够将展示给特定用户的付费墙与其被重定向到的网页关联起来。 2. 追踪用户何时返回应用,并以较短的时间间隔调用 `.getProfile`,以判断用户画像的访问权限是否已更新。 这样,如果付款成功且访问权限已更新,订阅几乎会立即在应用中激活。 ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall(product); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` :::note `openWebPaywall` 方法有两个版本: 1. `openWebPaywall(product)` 根据付费墙生成 URL,并将产品数据添加到 URL 中。 2. `openWebPaywall(paywall)` 根据付费墙生成 URL,但不添加产品数据到 URL。当 Adapty 付费墙中的产品与 Web 付费墙中的产品不同时,请使用此方法。 ::: #### 处理错误 \{#handle-errors\} | 错误 | 描述 | 建议操作 | |-----------------------------------------|--------------------------------------------------------|---------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | 付费墙未配置网页购买 URL | 检查付费墙是否已在 Adapty 看板中正确配置 | | AdaptyError.productWithoutPurchaseUrl | 产品没有网页购买 URL | 在 Adapty 看板中验证产品配置 | | AdaptyError.failedOpeningWebPaywallUrl | 无法在浏览器中打开该 URL | 检查设备设置,或提供其他购买方式 | | AdaptyError.failedDecodingWebPaywallUrl | 无法正确编码 URL 中的参数 | 验证 URL 参数是否有效且格式正确 | ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} :::important 从 Adapty SDK v3.15 起支持在应用内浏览器中打开网页付费墙。 ::: 默认情况下,网页付费墙会在外部浏览器中打开。 为了提供更流畅的用户体验,你可以在应用内浏览器中打开网页付费墙。这样,购买页面将直接显示在应用内,用户无需切换应用即可完成交易。 要启用此功能,请将 `WebPresentation.BrowserInApp` 作为第二个参数传入 `openWebPaywall`: ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall( product, WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp ); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` --- # File: react-native-troubleshoot-paywall-builder --- --- title: "在 React Native SDK 中排查付费墙编辑工具问题" description: "在 React Native SDK 中排查付费墙编辑工具问题" --- 本指南帮助您解决在 React Native SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:获取流程或付费墙的视图配置失败。 **原因**:付费墙未在付费墙编辑工具中启用设备显示。 **解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。 <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 付费墙浏览量数字过大 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙浏览次数显示的是预期数值的两倍。 **原因**:你可能在代码中调用了 `logShowFlow`(React Native SDK v4+)/ `logShowPaywall`,如果你使用的是付费墙编辑工具或 Flow Builder,这会导致浏览次数重复计算。对于通过这些工具构建的 flow 和付费墙,数据分析会自动追踪,无需手动调用此方法。 **解决方案**:如果你使用的是付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(React Native SDK v4+)/ `logShowPaywall`。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。 **解决方案**:如有需要,请参考[迁移指南](react-native-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新版本的 SDK 中得到修复。 --- # File: react-native-quickstart-manual --- --- title: "在 React Native SDK 中为自定义付费墙启用购买功能" description: "将 Adapty SDK 集成到您的 React Native 自定义付费墙中,以启用应用内购买功能。" --- 本指南介绍如何将 Adapty 集成到您的自定义付费墙中。您可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。 :::important **本指南适用于自行实现自定义付费墙的开发者。** 如果您希望以最简便的方式开启购买功能,请使用 [Adapty Flow Builder](react-native-quickstart-paywalls)。使用 Flow Builder,您可以在无代码可视化编辑器中创建流程,Adapty 自动处理所有购买逻辑,无需重新发布应用即可测试不同设计方案。 ::: ## 开始之前 \{#before-you-start\} ### 配置产品 \{#set-up-products\} 要启用应用内购买,你需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,这种设计让你无需改动应用代码就能修改产品、价格和优惠。 - [**版位**](placements) – 应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求。这让你能轻松运行 A/B 测试,并向不同用户展示不同的付费墙。 即使使用自定义付费墙,也请务必理解这些概念——它们本质上是管理应用内销售产品的方式。 要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到**版位**中。这样才能获取你的产品信息。如需了解在看板中的具体操作步骤,请参阅[快速入门指南](quickstart)。 ### 管理用户 \{#manage-users\} 您可以选择使用或不使用后端身份验证。 但是,Adapty SDK 对匿名用户和已识别用户的处理方式不同。请阅读[身份识别快速入门指南](react-native-quickstart-identify),了解其中的差异,确保正确处理用户数据。 ## 第一步:获取产品 \{#step-1-get-products\} 要为自定义付费墙获取产品,你需要: 1. 通过将[版位](placements) ID 传入 `getFlow` 方法来获取 `flow` 对象。 2. 使用 `getPaywallProducts` 方法获取该流程的产品数组。 ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts(flow); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## 第二步:处理购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,调用 `makePurchase` 方法并传入所选产品。该方法会处理购买流程并返回更新后的用户画像。 ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const purchaseResult: AdaptyPurchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': // Purchase successful, profile updated break; case 'user_cancelled': // User canceled the purchase break; case 'pending': // Purchase is pending (e.g., user will pay offline with cash) break; } } catch (error) { // Handle the error } } ``` ## 第三步:恢复购买 \{#step-3-restore-purchases\} 应用商店要求所有包含订阅功能的应用提供恢复购买的入口。 当用户点击恢复购买按钮时,调用 `restorePurchases` 方法。该方法会将用户的购买历史与 Adapty 同步,并返回最新的用户画像。 ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试购买流程,确保您可以从付费墙完成测试购买。如需查看生产就绪实现的示例,请参考我们示例应用中的 [CustomPurchaseScreen.tsx](https://github.com/adaptyteam/AdaptySDK-React-Native/blob/master/examples/ExpoGoWebMock/src/CustomPurchaseScreen.tsx),其中演示了包含完善错误处理、加载状态和 UI 状态管理的购买处理流程。 接下来,[检查用户是否已完成购买](react-native-check-subscription-status),以确定是否应显示付费墙或授予付费功能访问权限。 --- # File: fetch-paywalls-and-products-react-native --- --- title: "在 React Native SDK 中获取远程配置付费墙的付费墙和产品" description: "通过 Adapty React Native SDK 获取付费墙和产品,提升用户变现效果。" --- <SDKv4> 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。如需了解如何获取在 **Flow Builder** 或 **Paywall Builder** 中自定义的流程或付费墙,请参阅[获取 Flow Builder 流程和 Paywall Builder 付费墙及其配置](react-native-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中获取流程和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建流程或付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将流程或付费墙添加到版位中](create-placement)。 4. 在移动应用中[安装 Adapty SDK](sdk-installation-reactnative)。 </details> ## 获取流程信息 \{#fetch-flow-information\} 在 Adapty 中,[产品](product) 是 App Store 和 Google Play 产品的组合体。这些跨平台产品被整合到流程和付费墙中,使你能够在移动应用的特定版位中展示它们。 要展示产品,你需要使用 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。流程是远程配置的,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果今天流程返回两个产品,明天返回三个,应在不修改代码的情况下全部展示。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(id); // the requested flow } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |-------------------|--------|-----------| | **placementId** | 必填 | [版位](placements) 的标识符。该值是您在 Adapty 看板中创建版位时指定的。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时优先返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。</p><p></p><p>请注意,重启应用后缓存仍会保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储 flow 和付费墙:上述定期更新的缓存,以及[备用付费墙](react-native-use-fallback-paywalls)。我们还使用 CDN 来加快 flow 和付费墙的加载速度,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保您始终获取最新版本的 flow,同时在网络条件较差的情况下也能保持可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能由多个请求组成。</p> | :::note 在 v4 中,`getFlow` 不再接受 `locale` 参数。对于自定义付费墙,所有可用的语言环境都会在流程的远程配置(`flow.remoteConfigs`)中返回——选择与用户设备或应用设置相匹配的那个即可。 ::: 不要硬编码产品 ID!由于流程是远程配置的,可用产品的数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果后来获取到 3 个产品,应用无需修改任何代码即可显示全部 3 个。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、付费墙变体(`paywalls`)以及 `remoteConfigs` 数组(每个已配置的语言区域对应一个条目)。如需获取该流程的产品,请调用 `getPaywallProducts(flow)`。 | ## 获取产品 \{#fetch-products\} 获取流程后,你可以查询与之对应的产品数组: ```typescript showLineNumbers try { // ...flow const products = await adapty.getPaywallProducts(flow); // the requested products list } catch (error) { // handle the error } ``` 响应参数: | 参数 | 描述 | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 对象中的以下属性。下面列出了最常用的属性,完整属性列表请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户在商店中选择的国家/地区,而非设备本身的语言环境。 | | **Price** | 要显示本地化价格,请使用 `product.price?.localizedString`。该本地化基于设备的语言环境信息。您也可以通过 `product.price?.amount` 以数字形式获取价格,值将以当地货币表示。要获取对应的货币符号,请使用 `product.price?.currencySymbol`。 | | **Subscription Period** | 要显示订阅周期(如周、月、年等),请使用 `product.subscription?.localizedSubscriptionPeriod`,该本地化基于设备语言环境。若需以编程方式获取订阅周期,请使用 `product.subscription?.subscriptionPeriod`,通过其 `unit` 属性可获取周期单位(即 `'day'`、`'week'`、`'month'`、`'year'` 或 `'unknown'`),`numberOfUnits` 则表示周期单位的数量。例如,对于按季度订阅,`unit` 属性值为 `'month'`,`numberOfUnits` 值为 `3`。 | | **Introductory Offer** | 要显示订阅包含新用户优惠的标识或其他提示,请查看 `product.subscription?.offer?.phases` 属性。这是一个列表,最多包含两个折扣阶段:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:字符串类型,可选值为 `'free_trial'`、`'pay_as_you_go'`、`'pay_up_front'` 和 `'unknown'`。免费试用对应 `'free_trial'` 类型。<br/>• `price`:折扣价格(数字)。免费试用时此值为 `0`。<br/>• `localizedNumberOfPeriods`:根据设备语言环境本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `'3 days'`。<br/>• `subscriptionPeriod`:您也可以通过此属性获取优惠周期的详细信息,其使用方式与上一节中描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:按用户语言环境格式化的折扣订阅周期。 | ## 使用默认目标受众流程加速流程获取 \{#speed-up-flow-fetching-with-default-audience-flow\} 通常情况下,流程的获取几乎是即时完成的,因此无需担心速度问题。但如果你设置了大量目标受众和版位,且用户的网络连接较弱,流程获取可能会比预期花费更长时间。在这种情况下,你可能希望展示一个默认流程,以确保用户体验流畅,而不是什么都不显示。 为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的流程。但请务必了解,推荐的做法是使用 `getFlow` 方法获取流程,详情请参阅上方的[获取流程信息](fetch-paywalls-and-products-react-native#fetch-flow-information)章节。 :::warning 为什么我们推荐使用 `getFlow` `getFlowForDefaultAudience` 方法存在以下几个明显的缺陷: - **潜在的向后兼容性问题**:如果需要针对不同应用版本(当前版本和未来版本)展示不同的流程,可能会面临挑战。你要么设计出同时兼容当前(旧版)的流程,要么接受使用当前(旧版)的用户可能遇到流程无法渲染的问题。 - **目标定向缺失**:所有用户都将看到为 **All Users** 目标受众设计的同一套流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些限制以换取更快的 flow 获取速度,可按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-react-native#fetch-flow-information)介绍的 `getFlow`。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——有缓存时直接返回缓存数据。这样用户获取的数据可能不是最新的,但加载速度更快,无论网络状况如何都能流畅体验。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清除时才会被清空。</p> | </SDKv4> <SDKv3> 在展示远程配置和自定义付费墙之前,您需要获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。有关获取付费墙编辑工具自定义付费墙的指南,请参阅[获取付费墙编辑工具付费墙及其配置](react-native-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在您开始获取移动应用中的付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-reactnative)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被集成到付费墙中,使你能够在移动应用的特定版位中展示它们。 要展示产品,你需要使用 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。 :::important **不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。你的应用必须动态处理这些变化——如果今天付费墙返回两个产品,明天返回三个,应在不修改代码的情况下全部展示。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(id, locale); // the requested paywall } catch (error) { // handle the error } ``` | 参数 | 是否必填 | 描述 | |-------------------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。| | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,各子标签之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言区域代码及其推荐用法的更多信息,请参阅[本地化与语言区域代码](react-native-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户网络状况不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然保留,只有在卸载应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 通过两层机制存储付费墙:上述定期更新的缓存,以及[备用付费墙](react-native-use-fallback-paywalls)。我们还使用 CDN 加速付费墙的拉取,并在 CDN 不可达时启用独立的备用服务器。该机制旨在确保您始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保持可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>该值用于限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。</p> | 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品的数量以及特别优惠(如免费试用)随时可能发生变化。请确保你的代码能够处理这些情况。 例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果之后获取到 3 个产品,应用无需任何代码改动就应显示全部 3 个。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,你可以查询与之对应的产品数组: ```typescript showLineNumbers try { // ...paywall const products = await adapty.getPaywallProducts(paywall); // the requested products list } catch (error) { // handle the error } ``` 响应参数: | 参数 | 描述 | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 对象中的以下属性。下面列出了最常用的属性,完整的属性说明请参阅链接文档。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 | | **Price** | 要显示本地化价格,请使用 `product.price?.localizedString`。本地化基于设备的语言区域信息。您也可以通过 `product.price?.amount` 以数字形式获取价格,该值将以本地货币为单位。要获取对应的货币符号,请使用 `product.price?.currencySymbol`。 | | **Subscription Period** | 要显示订阅周期(如周、月、年等),请使用 `product.subscription?.localizedSubscriptionPeriod`,本地化基于设备的语言区域。如需以编程方式获取订阅周期,请使用 `product.subscription?.subscriptionPeriod`,通过其 `unit` 属性可获取周期单位(即 `'day'`、`'week'`、`'month'`、`'year'` 或 `'unknown'`),`numberOfUnits` 则表示周期单位数量。例如,对于按季度订阅,`unit` 属性显示 `'month'`,`numberOfUnits` 显示 `3`。 | | **Introductory Offer** | 要显示徽章或其他指示器以表明订阅包含新用户优惠,请查看 `product.subscription?.offer?.phases` 属性。这是一个最多包含两个折扣阶段的列表:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下实用属性:<br/>• `paymentMode`:字符串类型,可选值为 `'free_trial'`、`'pay_as_you_go'`、`'pay_up_front'` 和 `'unknown'`。免费试用类型为 `'free_trial'`。<br/>• `price`:折扣价格(数字类型)。免费试用时该值为 `0`。<br/>• `localizedNumberOfPeriods`:根据设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段显示 `'3 days'`。<br/>• `subscriptionPeriod`:您也可以通过此属性获取优惠周期的详细信息,用法与上一节中描述的订阅周期相同。<br/>• `localizedSubscriptionPeriod`:针对用户语言区域格式化后的折扣订阅周期字符串。 | ## 利用默认目标受众付费墙加快付费墙加载速度 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙的加载几乎是瞬间完成的,无需为此担心。但当你配置了大量目标受众和付费墙,且用户网络连接较差时,加载付费墙可能会比预期花费更长的时间。在这种情况下,你可能希望先展示一个默认付费墙,以确保良好的用户体验,而不是让用户看到空白。 为了解决这个问题,你可以使用 `getPaywallForDefaultAudience` 方法,该方法会为 **All Users** 目标受众获取指定版位的付费墙。但请务必注意,推荐的做法是使用 `getPaywall` 方法获取付费墙,详情请参阅上方的[获取付费墙信息](fetch-paywalls-and-products-react-native#fetch-paywall-information)章节。 :::warning 为什么我们推荐使用 `getPaywall` `getPaywallForDefaultAudience` 方法存在以下几个明显的缺点: - **潜在的向后兼容性问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么必须设计兼容当前(旧版)的付费墙,要么接受使用当前(旧版)的用户可能遇到付费墙无法渲染的问题。 - **丢失定向能力**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。 如果你愿意接受这些不足之处以换取更快的付费墙获取速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-react-native#fetch-paywall-information)介绍的 `getPaywall`。 ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note `getPaywallForDefaultAudience` 方法从 React Native SDK 2.11.2 版本开始支持。 ::: | 参数 | 是否必填 | 描述 | |---------|--------|-----------| | **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言代码及推荐使用方式的更多信息,请参阅[本地化与语言代码](react-native-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清除时才会被清空。</p> | </SDKv3> --- # File: present-remote-config-paywalls-react-native --- --- title: "在 React Native SDK 中渲染远程配置设计的付费墙" description: "了解如何在 Adapty React Native SDK 中展示远程配置付费墙,以个性化用户体验。" --- <SDKv4> 如果你使用远程配置自定义了流程,则需要在移动应用代码中实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,你可以自由决定包含哪些内容以及流程视图的呈现方式。我们提供了一个获取远程配置的方法,让你能够自主展示通过远程配置设置的自定义流程。 ## 获取 flow 远程配置并展示 \{#get-flow-remote-config-and-present-it\} 在 v4 中,每个 flow 的 `remoteConfigs` 数组中,每种已配置语言对应一个 `AdaptyRemoteConfig` 条目。选择与用户偏好匹配的语言,然后从其 `data` 中读取所需的值。 ```typescript showLineNumbers try { const flow = await adapty.getFlow("YOUR_PLACEMENT_ID"); const config = flow.remoteConfigs?.find((c) => c.lang === "en") ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.["header_text"]; } catch (error) { // handle the error } ``` 现在,您已获取所有必要的值,接下来需要将它们渲染并组装成一个美观的页面。请确保设计能够适配各种手机屏幕尺寸和方向,为不同设备的用户提供流畅且友好的体验。 :::warning 请务必按照以下说明[记录付费墙展示事件](present-remote-config-paywalls-react-native#track-paywall-view-events),以便 Adapty 分析系统能够采集漏斗和 A/B 测试所需的数据。 ::: 在完成流程展示后,继续设置购买流程。当用户发起购买时,只需使用流程中的产品调用 `.makePurchase()` 方法。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](react-native-making-purchases)。 我们建议[创建一个备用付费墙(即备用付费墙)](react-native-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,此备用付费墙将自动展示给用户,确保在这些情况下依然提供流畅的体验。 ## 追踪付费墙查看事件 \{#track-paywall-view-events\} Adapty 帮助你衡量流程的效果。购买数据会自动收集,但流程查看事件需要你手动记录,因为只有你知道用户何时看到了流程。 要记录流程查看事件,只需调用 `.logShowFlow(flow)`,该事件将反映在付费墙漏斗和 A/B 测试的数据指标中。 :::important 如果你通过 [Flow Builder](adapty-flow-builder) 或 [付费墙编辑工具](adapty-paywall-builder) 渲染流程或付费墙,无需调用 `.logShowFlow(flow)`。Adapty 在这些情况下会自动追踪展示次数。 ::: ```typescript showLineNumbers await adapty.logShowFlow(flow); ``` 请求参数: | 参数 | 是否必填 | 描述 | | :-------- | :------- |:-------------------------------------------------------------------------------------| | **flow** | 必填 | 通过 `adapty.getFlow(placementId)` 获取的 `AdaptyFlow` 对象。 | </SDKv4> <SDKv3> 如果您通过远程配置自定义了付费墙,则需要在移动应用的代码中实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由您掌控,付费墙视图的内容和样式都取决于您的设计。我们提供了一个获取远程配置的方法,让您能够自主展示通过远程配置设置的自定义付费墙。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const headerText = paywall.remoteConfig?.data?.["header_text"]; } catch (error) { // handle the error } ``` 至此,获取所有必要的值后,就可以开始渲染并将它们组合成一个视觉上美观的页面了。确保设计能够适配各种移动设备屏幕和屏幕方向,在不同设备上提供流畅且友好的用户体验。 :::warning 请务必按照以下说明[记录付费墙查看事件](present-remote-config-paywalls-react-native#track-paywall-view-events),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。 ::: 在展示付费墙之后,继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()` 即可。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](react-native-making-purchases)。 我们建议[创建一个备用付费墙](react-native-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,该备用付费墙将会展示给用户,确保在这些情况下仍能提供流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 帮助你衡量付费墙的表现。购买数据会自动收集,但付费墙浏览事件需要你手动记录,因为只有你知道用户何时看到了付费墙。 要记录付费墙浏览事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据指标中。 :::important 如果你使用[付费墙编辑工具](adapty-paywall-builder)创建付费墙,则无需调用 `.logShowPaywall(paywall)`。 ::: ```typescript showLineNumbers await adapty.logShowPaywall(paywall); ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:--------------------------------------------------------------------------------------------| | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) 对象。 | </SDKv3> --- # File: react-native-making-purchases --- --- title: "在 React Native SDK 中发起移动应用内购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在移动应用中展示付费墙,是向用户提供高级内容或服务访问权限的关键步骤。不过,仅仅展示付费墙本身,只有在你使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙时,才足以支持购买流程。 如果你不使用付费墙编辑工具,则需要调用名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成交易的入口。 如果付费墙针对用户正在购买的产品设置了有效的促销活动,Adapty 会在购买时自动应用该优惠。 :::warning 请注意,新用户优惠仅在使用付费墙编辑工具搭建的付费墙时才会自动应用。 在其他情况下,您需要[验证用户是否有资格享受 iOS 上的新用户优惠](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布时被拒绝,还可能向本应享受新用户优惠的用户收取全价。 ::: 请确保已完成[初始配置](quickstart),每一步都不能跳过。否则我们将无法验证购买。 ## 发起购买 \{#make-purchase\} :::note **使用[付费墙编辑工具](adapty-paywall-builder)?** 购买流程将自动处理——可以跳过此步骤。 **需要逐步操作指南?** 请查看[快速入门指南](react-native-implement-paywalls-manually),获取包含完整背景信息的端到端实现说明。 ::: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:------------------------------------------------------------------------------------------------------------------------------| | **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) 对象。 | 响应参数: | 参数 | 描述 | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>请求成功后,响应中会包含此对象。[AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) 对象提供了用户在应用内的访问等级、订阅及一次性购买的完整信息。</p><p>请检查访问等级状态,以确认用户是否拥有所需的应用访问权限。</p> | :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。该方法目前已被 Apple 弃用。 ::: ## 购买时变更订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而非续订当前订阅时,具体行为取决于应用商店: - 对于 App Store,订阅会在同一订阅组内自动更新。如果用户在已有某个组的订阅的情况下购买了另一个组的订阅,两个订阅将同时处于激活状态。 - 对于 Google Play,订阅不会自动更新。你需要按照以下说明在移动应用代码中手动处理切换逻辑。 要在 Android 上将订阅替换为另一个订阅,请调用 `.makePurchase()` 方法并传入额外参数: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product, params); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // 授予付费功能访问权限 } break; case 'user_cancelled': // 处理用户取消购买的情况 break; case 'pending': // 处理延迟购买(例如,用户将以现金线下支付) break; } } catch (error) { // 处理错误 } ``` 额外请求参数: | 参数 | 是否必填 | 描述 | | :--------- | :------- | :----------------------------------------------------------- | | **params** | 必填 | [`MakePurchaseParamsInput`](https://react-native.adapty.io/types/makepurchaseparamsinput) 类型的对象。 | :::info **3.8.2+ 版本**:`MakePurchaseParamsInput` 结构已更新。`oldSubVendorProductId` 和 `prorationMode` 现已嵌套在 `subscriptionUpdateParams` 下,`isOfferPersonalized` 已移至上层。 ```javascript makePurchase(product, { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } }); ``` ::: 如需了解更多关于订阅和替换模式的内容,请参阅 Google 开发者文档: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 对替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更仅在当前订阅计费周期结束时才会生效。 ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 要在应用中显示优惠码兑换页面: ```typescript showLineNumbers adapty.presentCodeRedemptionSheet(); ``` :::danger 根据我们的观察,某些应用中的优惠码兑换页面可能无法稳定运行。我们建议直接将用户重定向至 App Store。 为此,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## 管理预付费计划(Android) \{#manage-prepaid-plans-android\} 如果您的应用用户可以购买[预付费计划](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月的非续期订阅),您可以为预付费计划启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。 ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { android: { pendingPrepaidPlansEnabled: true } }); ``` --- # File: react-native-restore-purchase --- --- title: "在 React Native SDK 中恢复移动应用内购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 在 iOS 和 Android 中恢复购买是一项功能,允许用户重新获得对之前购买内容的访问权限,例如订阅或应用内购买,而无需再次付费。此功能对于那些可能已卸载并重新安装应用,或切换到新设备并希望访问之前购买内容而无需再次付费的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您使用的是这种方式,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // restore access } } catch (error) { // handle the error } ``` 响应参数: | 参数 | 描述 | |---------|-----------| | **Profile** | <p>一个 [`AdaptyProfile`](https://react-native.adapty.io/interfaces/adaptyprofile) 对象。该模型包含访问等级、订阅和非订阅购买的相关信息。</p><p>检查**访问等级状态**以确定用户是否有权访问应用。</p> | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: implement-observer-mode-react-native --- --- title: "在 React Native SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以跟踪 React Native SDK 中的用户订阅事件。" --- 如果您已有自己的购买基础设施,暂时不打算完全切换到 Adapty,可以了解[观察者模式](observer-vs-full-mode)。在基本形式下,观察者模式提供高级分析功能,并与归因和分析系统无缝集成。 如果这满足您的需求,您只需: 1. 在配置 Adapty SDK 时将 `observerMode` 参数设置为 `true` 来开启观察者模式。请参阅 [React Native](sdk-installation-reactnative) 的设置说明。 2. 从您现有的购买基础设施向 Adapty [上报交易](report-transactions-observer-mode-react-native)。 ### 观察者模式设置 \{#observer-mode-setup\} 如果您自行处理购买和订阅状态,仅使用 Adapty 发送订阅事件和分析数据,请开启观察者模式。 :::important 在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保您自行处理这一操作。 ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { observerMode: true, // Enable observer mode }); ``` 参数: | 参数 | 描述 | | --------------------------- | ------------------------------------------------------------ | | observerMode | 一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 | ## 在观察者模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还希望使用 Adapty 的付费墙和 A/B 测试功能,也是可以的——但在观察者模式下需要一些额外的设置。除了上述步骤之外,您还需要: 1. 按照[远程配置付费墙](present-remote-config-paywalls-react-native)的常规方式展示付费墙。 3. 将付费墙与购买交易[关联](report-transactions-observer-mode-react-native)。 --- # File: report-transactions-observer-mode-react-native --- --- title: "在 React Native SDK 的 Observer Mode 中上报交易" description: "在 React Native SDK 中通过 Adapty Observer Mode 上报购买交易,用于用户洞察和收入追踪。" --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+(当前版本)" default> 在 Observer 模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店主动上报交易。为避免分析数据出现错误,务必在发布应用**之前**完成此配置。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别。 :::warning **请勿跳过交易上报!** 如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会发送至集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时附带 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` 参数说明: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | <ul><li>iOS:交易的标识符。</li><li>Android:购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是账单库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x(旧版)" default> 在 Observer 模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店上报或恢复交易。为避免分析数据出现错误,务必在发布应用**之前**完成此配置。 在两个平台上均使用 `reportTransaction` 显式上报每笔交易,并在 Android 上额外使用 `restorePurchases`,以确保 Adapty 能够识别该交易。 :::warning **请勿跳过交易上报!** 如果您不调用这些方法,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会发送至集成渠道。 ::: 如果您使用 Adapty 付费墙,请在上报交易时附带 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```typescript showLineNumbers if (Platform.OS === 'android') { try { await adapty.restorePurchases(); } catch (error) { // handle the error } } ... const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` 参数说明: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | <ul><li>iOS,StoreKit 1:[SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</li><li>iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</li><li>Android:购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是账单库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old2" label="Adapty SDK 3.2.x 及以下(旧版)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **上报交易** - 3.1.x 及以下版本会自动监听 App Store 中的交易,无需手动上报。 - 3.2 版本不支持 Observer 模式。 </TabItem> <TabItem value="kotlin" label="Android 及基于 Android 的跨平台" default> **上报交易** 在 Observer 模式下,使用 `restorePurchases` 向 Adapty 上报交易,具体说明请参阅[在移动端代码中恢复购买](react-native-restore-purchase)页面。 :::warning **请勿跳过交易上报!** 如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会发送至集成渠道。 ::: </TabItem> </Tabs> **将付费墙与交易关联** 由于购买由您自行处理,Adapty SDK 无法确定购买来源。因此,如果您计划在 Observer 模式下使用付费墙和/或 A/B 测试,需要在移动端代码中将来自应用商店的交易与对应的付费墙关联起来。在发布应用之前务必正确配置此项,否则将导致分析数据出现错误。 ```typescript const variationId = paywall.variationId; try { await adapty.setVariationId('transactionId', variationId); } catch (error) { // handle the `AdaptyError` } ``` 请求参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | 必填 | <p>iOS,StoreKit 1:[SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</p><p>iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</p><p>Android:购买的字符串标识符(purchase.getOrderId),其中 purchase 是账单库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</p> | | variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall) 对象的 `variationId` 属性获取。 | </TabItem> </Tabs> --- # File: react-native-troubleshoot-purchases --- --- title: "排查 React Native SDK 中的购买问题" description: "排查 React Native SDK 中的购买问题" --- 本指南帮助您解决在 React Native SDK 中手动实现购买时遇到的常见问题。 ## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **问题**:`makePurchase` 方法成功完成,但用户的用户画像和订阅状态在 Adapty 中未更新。 **原因**:这通常表明 Google Play Store 设置不完整或存在配置问题。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\} **问题**:`makePurchase` 方法针对同一笔购买被多次调用。 **原因**:这通常发生在由于 UI 状态管理问题或用户快速操作导致购买流程被多次触发的情况下。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## 观察者模式下出现 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时收到 `AdaptyError.cantMakePayments` 错误。 **原因**:在观察者模式下,您应该在您自己的端处理购买,而不是使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 进行购买,请关闭观察者模式。您需要选择其一:使用 `makePurchase`,或在观察者模式下自行处理购买。详情请参阅[实现观察者模式](implement-observer-mode-react-native)。 ## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **问题**:您收到来自 Google Play Store 的计费不可用错误。 **原因**:此错误与 Adapty 无关,它是 Google Play 计费库的错误,表明设备上的计费功能不可用。 **解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查找更多相关信息:[处理 BillingResult 响应代码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。 ## 未找到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:您遇到了找不到 `makePurchasesCompletionHandlers` 的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建新的沙盒用户后重试。这通常可以解决与沙盒相关的购买完成处理程序问题。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他购买相关问题。 **解决方案**:如有需要,请参考[迁移指南](react-native-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新的 SDK 版本中得到修复。 --- # File: react-native-identifying-users --- --- title: "在 React Native SDK 中识别用户" description: "了解如何使用 Adapty SDK 在 React Native 应用中识别用户。" --- Adapty 会为每位用户创建一个内部用户画像 ID。但如果你有自己的身份验证系统,则应设置你自己的 Customer User ID。你可以在[用户画像](profiles-crm)部分通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 将被发送至所有集成渠道。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果您在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" }); ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 在配置完成后设置客户用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以在之后任意时间通过 `.identify()` 方法进行设置。最常见的使用场景是在注册或授权完成后,即用户从匿名状态切换为已认证状态时。 ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // successfully identified } catch (error) { // handle the error } ``` 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重新提交重要用户数据 在某些情况下,例如用户再次登录其账户时,Adapty 的服务器已经存储了该用户的信息。在这种情况下,Adapty SDK 会自动切换到新用户。如果你曾向匿名用户传递过任何数据(例如自定义属性或第三方网络的归因数据),则需要为已识别的用户重新提交这些数据。 同样需要注意的是,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 登出与登入 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法使用户登出: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` 之后,您可以使用 `.identify()` 方法使用户重新登录。 ## 分配 `appAccountToken`(iOS)\{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,用于将 App Store 交易与你的内部用户身份关联起来。 StoreKit 会将此 token 附加到每笔交易上,这样你的后端就能将 App Store 数据与用户进行匹配。 建议为每个用户生成一个稳定的 UUID,并在同一账号的不同设备上复用它。 这样可以确保购买记录和 App Store 通知始终与正确的用户关联。 您可以通过两种方式设置 token——在 SDK 激活时或在用户识别时。 :::important 您必须始终将 `appAccountToken` 与 `customerUserId` 一起传递。 如果只传递 token,它将不会包含在交易中。 ::: ```typescript showLineNumbers // 配置时: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, }); // 或在识别用户时 try { await adapty.identify("YOUR_USER_ID", { ios: {appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN'} }); // 识别成功 } catch (error) { // 处理错误 } ``` ### 设置混淆账户 ID(Android)\{#set-obfuscated-account-ids-android\} Google Play 要求在某些场景下提供混淆账户 ID,以增强用户隐私和安全性。这些 ID 可帮助 Google Play 在保持用户信息匿名的同时识别购买记录,对于防欺诈和数据分析尤为重要。 如果你的应用处理敏感用户数据,或需要遵守特定隐私法规,可能就需要设置这些 ID。混淆 ID 允许 Google Play 在不暴露真实用户标识符的情况下追踪购买行为。 ```typescript showLineNumbers // 在配置时: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // 或在识别用户时 try { await adapty.identify("YOUR_USER_ID", { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // 识别成功 } catch (error) { // 处理错误 } ``` ## 跨设备识别用户 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: react-native-setting-user-attributes --- --- title: "在 React Native SDK 中设置用户属性" description: "了解如何使用 Adapty SDK 在 React Native 应用中更新用户属性和用户画像数据。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。然后,您可以使用这些属性来创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```typescript showLineNumbers // Only for TypeScript validation const params: AdaptyProfileParameters = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); } catch (error) { // handle `AdaptyError` } ``` 请注意,之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 的允许键 `<Key>` 及其对应值 `<Value>` 如下所示: | 键 | 值 | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | 枚举类型,允许的值为:`female`、`male`、`other` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性。这些属性通常与应用使用情况相关。例如,对于健身应用,可能是每周锻炼次数;对于语言学习应用,可能是用户的知识水平等。您可以在市场细分中使用它们来创建有针对性的付费墙和优惠,也可以在分析中使用它们来了解哪些产品指标对收入影响最大。 ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); } catch (error) { // handle `AdaptyError` } ``` 要删除已有的键,请使用 `.withRemoved(customAttributeForKey:)` 方法: ```typescript showLineNumbers try { // to remove a key, pass null as its value await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); } catch (error) { // handle `AdaptyError` } ``` 有时您需要查看之前已设置了哪些自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能不是最新的,因为用户属性可以随时从不同设备发送,服务器上的属性可能在上次同步后已发生变化。 ::: ### 限制 \{#limits\} - 每位用户最多 30 个自定义属性 - 键名最长 30 个字符。键名可包含字母数字字符及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,最多 50 个字符。 --- # File: react-native-listen-subscription-changes --- --- title: "在 React Native SDK 中检查订阅状态" description: "在 Adapty 中跟踪和管理用户订阅状态,提升 React Native 应用的用户留存率。" --- 借助 Adapty,订阅状态的跟踪变得轻而易举。您无需在代码中手动插入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可轻松确认其订阅状态。 <details> <summary>开始检查订阅状态前(点击展开)</summary> - iOS 端,请配置 [App Store Server Notifications](enable-app-store-server-notifications) - Android 端,请配置 [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) 对象的属性。我们建议在应用启动时获取用户画像(例如在[识别用户](react-native-identifying-users#setting-customer-user-id-on-configuration)时),并在发生变更时及时更新。这样您就可以直接使用用户画像对象,而无需反复请求。 如需在用户画像更新时收到通知,请按照下方[监听用户画像更新(包括访问等级)](react-native-listen-subscription-changes)章节的说明监听用户画像变更事件。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 使用 `.getProfile()` 方法从服务器获取访问等级: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` 响应参数: | 参数 | 描述 | | --------- | ------------------------------------------------------------ | | Profile | <p>[AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) 对象。通常情况下,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。</p><p></p><p>`.getProfile` 方法始终尝试查询 API,因此返回的结果是最新数据。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。值得注意的是,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的实时性。</p> | `.getProfile()` 方法会返回用户画像,您可以从中获取访问等级状态。每个应用可以设置多个访问等级。例如,如果您运营一款新闻应用,并针对不同主题单独销售订阅,可以创建"sports"和"science"等访问等级。但在大多数情况下,您只需要一个访问等级,此时可以直接使用默认的"premium"访问等级。 以下是检查默认"premium"访问等级的示例: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.["premium"]?.isActive; if (isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化时,Adapty 都会触发一个事件。 要接收来自 Adapty 的消息,您需要进行一些额外配置: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addEventListener('onLatestProfileLoad', profile => { // handle any changes to subscription state }); ``` Adapty 也会在应用启动时触发一次事件,此时传递的是缓存中的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 实现的缓存机制会存储用户画像的订阅状态。这意味着即使服务器不可用,也可以通过缓存数据获取用户画像的订阅状态信息。 但需要注意的是,无法直接从缓存中请求数据。SDK 会每分钟定期向服务器发起查询,检查用户画像是否有任何更新或变更。如果存在修改(例如新的交易记录或其他更新),这些变更将同步写入缓存数据,以确保缓存与服务器保持一致。 --- # File: react-native-deal-with-att --- --- title: "在 React Native SDK 中处理 ATT" description: "在 React Native 上开始使用 Adapty,以简化订阅设置和管理。" --- 如果您的应用程序使用了 AppTrackingTransparency 框架并向用户呈现应用追踪授权请求,则您应将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 ```typescript showLineNumbers try { await adapty.updateProfile({ // you can also pass a string value (validated via tsc) if you prefer appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); } catch (error) { // handle `AdaptyError` } ``` :::warning 我们强烈建议您在该值发生变化时尽早发送此值,只有这样,数据才能及时发送到您已配置的集成渠道。 ::: --- # File: kids-mode-react-native --- --- title: "React Native SDK 中的儿童模式" description: "轻松启用儿童模式,符合 Apple 和 Google 政策。React Native SDK 不会收集 IDFA、GAID 或广告数据。" --- 如果你的 React Native 应用面向儿童,则必须遵守 [Apple](https://developer.apple.com/kids/) 和 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果你正在使用 Adapty SDK,只需几个简单的步骤即可将其配置为符合这些政策,并顺利通过应用商店审核。 :::important 在 iOS 上,Kids Mode 通过 `KidsMode` Swift package trait 启用,该 trait 会在编译时移除所有 IDFA、AdSupport 和 AppTrackingTransparency 相关代码。此功能需要 v4 SDK(通过 Swift Package Manager 安装原生 iOS SDK)以及 **Xcode 26** 或更高版本。详见下方的[更新 iOS Podfile](#updates-in-your-ios-podfile)。 ::: ## 需要做什么?\{#whats-required\} 你需要配置 Adapty SDK,禁用以下数据的收集: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers)(iOS) - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)(Android) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用 customer user ID。格式为 `<FirstName.LastName>` 的用户 ID 与使用邮箱一样,肯定会被视为收集个人数据。在儿童模式下,最佳实践是使用随机或匿名标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。为此,请前往 [App settings](https://app.adapty.io/settings/general),并在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 更新您的移动应用代码 \{#updates-in-your-mobile-app-code\} 为符合相关政策,请在激活 Adapty SDK 时禁止收集用户的 IDFA(iOS)、GAID/AAID(Android)以及 IP 地址: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true, }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true, }, }); ``` ### 更新你的 iOS Podfile \{#updates-in-your-ios-podfile\} 为了满足 App Store 儿童分类(或 COPPA 合规)的要求,原生 iOS SDK 必须使用 `KidsMode` Swift 包特性进行构建,该特性会在编译时移除所有 IDFA、AdSupport 和 AppTrackingTransparency 相关代码。React Native 通过 Swift Package Manager 安装原生 SDK,但 Swift Package Manager 无法传递包特性,因此 SDK 提供了一个 Podfile 辅助工具来帮你应用该特性。此步骤需要 **Xcode 26** 或更高版本。 在 `ios/Podfile` 中,引入辅助工具并在 `react_native_post_install` **之后**调用它: ```ruby showLineNumbers title="ios/Podfile" require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native-adapty/ios/adapty_kids_mode.rb", {paths: [process.argv[1]]}, )', __dir__]).strip # ... post_install do |installer| react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false ) adapty_enable_kids_mode(installer) end ``` 然后运行 `pod install`: ```sh showLineNumbers title="Shell" cd ios && pod install ``` 要确认 Kids Mode 已激活,请检查 `adapty.activate(...)` 日志行是否显示 `kids_mode_enabled: true`。请将辅助调用永久保留在 `post_install` 中——React Native 每次执行 `pod install` 时都会重新创建 Swift Package 引用,辅助函数会在每次执行时重新应用该特性。 ### 更新 Android 清单 \{#updates-in-your-android-manifest\} :::note 如果你的应用**仅面向儿童**,且编译目标为 Android 13(API 33)或更高版本,Google Play 要求你不得请求 `AD_ID` 权限。应用中的其他 SDK(如分析、归因或广告类 SDK)可能通过清单合并自动添加此权限。设置 `adIdCollectionDisabled` 可以阻止 Adapty 收集该 ID,但不会移除其他 SDK 已声明的权限。 ::: 要移除该权限,请在 `android/app/src/main/AndroidManifest.xml` 的 `<manifest>` 元素内添加以下内容。`<manifest>` 元素必须声明 `xmlns:tools="http://schemas.android.com/tools"`。 ```xml showLineNumbers title="AndroidManifest.xml" <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> ``` --- # File: react-native-get-onboardings --- --- title: "在 React Native SDK 中获取用户引导" description: "了解如何在 Adapty 的 React Native 中获取用户引导。" --- :::warning **用户引导功能已在 SDK v4 中弃用,并将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](react-native-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上进行原生渲染,带来更流畅的动画、一致的原生外观、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取 flows 与付费墙](react-native-get-pb-paywalls) 和 [展示 flows 与付费墙](react-native-present-paywalls) 快速上手。 ::: 在 Adapty 看板中[使用编辑工具完成用户引导的视觉设计](design-onboarding)之后,您可以在 React Native 应用中展示它。此过程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所示。 开始之前,请确保: 1. 您已安装 [Adapty React Native SDK](sdk-installation-reactnative) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 ## 获取用户引导 \{#fetch-onboarding\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器形式存储,包含您的应用需要获取并展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(例如测验答案或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。 为了获得最佳性能,请尽早获取用户引导配置,以便在向用户展示之前有足够的时间下载图片。 要获取用户引导,请使用 `getOnboarding` 方法: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboarding(placementId, locale); // the requested onboarding } catch (error) { // handle the error } ``` 然后,调用 `createOnboardingView` 方法创建视图实例。 :::warning `createOnboardingView` 方法的返回结果只能使用一次。如果需要再次使用,请重新调用 `createOnboardingView` 方法。在不重新创建的情况下调用两次可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createOnboardingView} from 'react-native-adapty/dist/ui'; if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); } catch (error) { // handle the error } } else { //use your custom logic } ``` 参数: | 参数 | 是否必填 | 描述 | |-------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为语言代码,由一个或两个子标签组成,用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言区域代码及其推荐用法的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此选项,因为它确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能获取不到最新数据,但无论网络状况如何,都能获得更快的加载速度。缓存会定期更新,因此在会话期间使用它来避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后依然保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可访问时提供独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不稳定的情况下也能保证可靠性。</p> | | **loadTimeoutMs** | 默认值:5 秒 | <p>该值限制此方法的超时时间。如果达到超时时间,将返回缓存数据或本地备用内容。</p><p>请注意,在极少数情况下,此方法的超时时间可能比 `loadTimeout` 中指定的时间稍长,因为该操作在底层可能由多个不同的请求组成。</p> | 响应参数: | 参数 | 描述 | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://react-native.adapty.io/interfaces/adaptyonboarding) 对象,包含:用户引导标识符和配置、远程配置以及其他若干属性。 | ## 使用默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常情况下,用户引导的获取几乎是即时完成的,无需担心加速问题。但是,当您拥有大量目标受众和用户引导,且用户网络连接较弱时,获取用户引导的时间可能比预期更长。在这种情况下,您可能希望显示默认的用户引导,以确保流畅的用户体验,而不是不显示任何内容。 为解决这一问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法会获取指定版位中**所有用户**目标受众的用户引导。但请务必了解,推荐的方式是使用 `getOnboarding` 方法获取用户引导,详见上方[获取用户引导](#fetch-onboarding)部分。 :::warning 建议使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者存在以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要向后兼容的设计,否则旧版本可能显示异常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向。 如果对您的使用场景而言更快的获取速度超过了上述缺点,请按如下所示使用 `getOnboardingForDefaultAudience`。否则,请按[上述](#fetch-onboarding)方式使用 `getOnboarding`。 ::: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboardingForDefaultAudience(placementId, locale); // the requested onboarding } catch (error) { // handle the error } ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为语言代码,由一个或两个子标签组成,用减号(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言区域代码及其推荐用法的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐此选项,因为它确保用户始终获得最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能获取不到最新数据,但无论网络状况如何,都能获得更快的加载速度。缓存会定期更新,因此在会话期间使用它来避免网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后依然保留,只有在重新安装应用或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 加速用户引导的获取,并在 CDN 不可访问时提供独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不稳定的情况下也能保证可靠性。</p> | --- # File: react-native-present-onboardings --- --- title: "在 React Native SDK 中展示用户引导" description: "了解如何在 React Native 中展示用户引导,以提升转化率和收入。" --- :::warning **用户引导功能在 SDK v4 中已弃用,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](react-native-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上进行原生渲染——带来更流畅的动画、一致的原生外观体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅 [获取 flows 和付费墙](react-native-get-pb-paywalls) 和 [展示 flows 和付费墙](react-native-present-paywalls) 以开始使用。 ::: 如果你使用编辑工具自定义了用户引导,就无需在移动端代码中手动处理渲染逻辑来向用户展示它。这类用户引导已经包含了展示内容和展示方式的完整配置。 开始之前,请确认: 1. 已安装 [Adapty React Native SDK](sdk-installation-reactnative) 3.8.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 Adapty React Native SDK 提供两种展示用户引导的方式: - **React 组件**:嵌入式组件,可将其集成到应用的架构和导航系统中。 - **模态呈现** ## React 组件 \{#react-component\} 如需将用户引导嵌入现有组件树,可在 React Native 组件层级中直接使用 `AdaptyOnboardingView` 组件。嵌入式组件让你能够将其集成到应用的架构和导航系统中。 :::note 在 Android 上,我们建议对 `AdaptyOnboardingView` 进行额外配置,以避免视觉渲染异常。详见[系统界面遮挡 Android 用户引导内容](#system-ui-overlaps-onboarding-content-on-android)。 ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK 版本 3.14 或更高" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="old" label="SDK 版本 < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { return ( <AdaptyOnboardingView onboarding={onboarding} style={{ flex: 1 }} eventHandlers={{ onAnalytics(event, meta) { // Handle analytics events }, onClose(actionId, meta) { // Handle close actions }, onCustom(actionId, meta) { // Handle custom actions }, onPaywall(actionId, meta) { // Handle paywall actions }, onStateUpdated(action, meta) { // Handle state updates }, onFinishedLoading(meta) { // Handle when onboarding finishes loading }, onError(error) { // Handle errors }, }} /> ); } ``` </TabItem> </Tabs> ## 模态展示 \{#modal-presentation\} 要将用户引导作为独立页面展示并允许用户关闭,请对 `createOnboardingView` 方法创建的 `view` 调用 `view.present()` 方法。每个 `view` 只能使用一次。如果需要再次展示用户引导,请重新调用 `createOnboardingView` 创建新的 `view` 实例。 :::warning 禁止在不重新创建的情况下复用同一个 `view`,否则会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); // Optional: handle onboarding events (close, custom actions, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> <TabItem value="old" label="SDK 版本 < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); view.setEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ### 配置 iOS 呈现样式 \{#configure-ios-presentation-style\} 通过向 `present()` 方法传递 `iosPresentationStyle` 参数,配置用户引导在 iOS 上的呈现方式。该参数接受 `'full_screen'`(默认值)或 `'page_sheet'` 两个值。 ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## 用户引导加载动画 \{#loader-during-onboarding\} 在 React Native 中展示用户引导时,你可能会注意到在用户引导显示之前出现短暂的白屏或加载画面。这是由于底层原生视图正在初始化。你可以根据自己的需求和工作流程,选择不同的处理方式。 #### 使用 onFinishedLoading 控制启动页 \{#control-splash-screen-using-onfinishedloading\} :::note 此方法仅在使用 React 组件时可用,不支持模态展示方式。 ::: 推荐在 React Native 中保持启动页或自定义遮罩层可见,直到用户引导完全加载完成,再手动隐藏它。 使用 React 组件(`AdaptyOnboardingView`)时,请等待 `onFinishedLoading` 事件触发后再隐藏启动页或遮罩层: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK 版本 3.14 或更高" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { // Hide your splash screen or custom overlay here setIsLoading(false); }, []); return ( <> <AdaptyOnboardingView onboarding={onboarding} onFinishedLoading={onFinishedLoading} // ... other callbacks /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> <TabItem value="old" label="SDK 版本 < 3.14"> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); return ( <> <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onFinishedLoading(meta) { // Hide your splash screen or custom overlay here setIsLoading(false); }, // ... other handlers }} /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> </Tabs> #### 自定义原生加载器 \{#customize-native-loader\} :::important Expo 托管工作流不支持放置自定义原生布局(例如 Android 上的 `res/layout`)。对于 Expo 应用,控制启动页或使用 React Native 覆盖层是唯一可行的方案。 ::: 你可以在 Android 和 iOS 上使用平台特定布局来替换原生加载器。如果你使用的是模态展示方式,这是唯一可行的方案。 不过,这种方式对于 React Native 应用来说通常不太方便: - 需要分别为 Android 和 iOS 单独实现 - 与 Expo 托管工作流不兼容 为每个平台定义一个占位符: - **iOS**:将 `AdaptyOnboardingPlaceholderView.xib` 添加到你的 Xcode 项目中。[了解更多](ios-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding)。 - **Android**:在 `res/layout` 中创建 `adapty_onboarding_placeholder_view.xml`,并在其中定义占位符。[了解更多](android-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding)。 ## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接的打开方式需要 Adapty SDK v3.15.1 及以上版本。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开。这样用户无需切换应用即可查看网页,体验更流畅。 如果你希望改为在外部浏览器中打开链接,可以将 `externalUrlsPresentation` 参数设置为 `WebPresentation.BrowserOutApp` 来自定义此行为: <Tabs groupId="rn-onboarding-views" queryString> <TabItem value="component" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} externalUrlsPresentation={WebPresentation.BrowserOutApp} // default – BrowserInApp onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="modal" label="模态呈现"> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView( onboarding, { externalUrlsPresentation: WebPresentation.BrowserOutApp } // default – BrowserInApp ); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## 故障排查 \{#troubleshooting\} ### Android 上系统 UI 遮挡用户引导内容 \{#system-ui-overlaps-onboarding-content-on-android\} :::note 此设置仅在裸 React Native 项目中受支持。 如果你使用的是 Expo 托管工作流,则无法直接添加此 Android 资源。要应用此设置,必须创建一个自定义 Expo 配置插件,将相应的 Android 资源添加进去,并在 `app.config.js` 中注册。这是必要的,因为 Expo 会替你管理原生 Android 项目。 ::: 在 Android 上使用 `AdaptyOnboardingView` 时,状态栏和导航栏等系统 UI 元素可能会覆盖在付费墙内容之上。要解决这个问题,请在应用中添加以下布尔资源: 1. 进入 `android/app/src/main/res/values` 目录。如果没有 `bools.xml` 文件,请创建一个。 2. 添加以下资源: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` 请注意,此更改会对应用中的所有用户引导全局生效。 ## 后续步骤 \{#next-steps\} 展示用户引导后,您需要[处理用户交互和事件](react-native-handling-onboarding-events)。了解如何处理用户引导事件,以响应用户操作并跟踪分析数据。 --- # File: react-native-handling-onboarding-events --- --- title: "在 React Native SDK 中处理用户引导事件" description: "使用 Adapty 在 React Native 中处理用户引导相关事件。" --- :::warning **SDK v4 中用户引导功能已废弃,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](react-native-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染——带来更流畅的动画、一致的原生外观、更快的加载速度,且无需依赖 WebView 运行时。请参阅 [获取 flows 与付费墙](react-native-get-pb-paywalls) 和 [展示 flows 与付费墙](react-native-present-paywalls) 以开始使用。 ::: 在使用编辑工具配置用户引导时,会生成一系列事件供应用响应。事件的处理方式取决于你使用的呈现方式: - **模态呈现**:需要设置事件处理器,统一处理所有用户引导视图的事件 - **React 组件**:通过组件内联回调参数直接处理事件 开始之前,请确保: 1. 您已安装 [Adapty React Native SDK](sdk-installation-reactnative) 3.8.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 如需在移动应用中控制或监听用户引导页面上发生的事件,请实现事件处理程序: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> 对于 React 组件,你可以通过 `AdaptyOnboardingView` 组件中各自的事件处理器 props 来处理事件: ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 对于模态展示,请实现事件处理方法。 :::important 多次调用 `setEventHandlers` 会覆盖你提供的处理器,替换这些特定事件的默认处理器和之前设置的处理器。 ::: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { // 追踪分析事件 }, onClose(actionId, meta) { // 处理关闭操作 view.dismiss(); return true; }, onCustom(actionId, meta) { // 处理自定义操作 }, onPaywall(actionId, meta) { // 处理付费墙操作 }, onStateUpdated(action, meta) { // 处理用户输入更新 }, onFinishedLoading(meta) { // 用户引导加载完成 }, onError(error) { // 处理加载错误 }, }); try { await view.present(); } catch (error) { // 处理错误 } ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK 版本 < 3.14"> 对于 SDK 版本 < 3.14,仅支持模态展示方式: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { // 追踪分析事件 }, onClose(actionId, meta) { // 处理关闭操作 view.dismiss(); return true; }, onCustom(actionId, meta) { // 处理自定义操作 }, onPaywall(actionId, meta) { // 处理付费墙操作 }, onStateUpdated(action, meta) { // 处理用户输入更新 }, onFinishedLoading(meta) { // 用户引导加载完成 }, onError(error) { // 处理加载错误 }, }); try { await view.present(); } catch (error) { // 处理错误 } ``` </TabItem> </Tabs> ## 事件类型 \{#event-types\} 以下各节介绍了您可以处理的不同类型的事件,无论您使用哪种展示方式。 ### 处理自定义动作 \{#handle-custom-actions\} 在编辑工具中,你可以为按钮添加 **custom** 动作并为其指定一个 ID。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 然后,你可以在代码中使用这个 ID,并将其作为自定义操作来处理。例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,事件处理程序会被触发,并附带与编辑工具中 **Action ID** 对应的 `actionId` 参数。你可以自定义 ID,比如 "allowNotifications"。 <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onCustom={onCustom} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### 完成加载用户引导 \{#finishing-loading-onboarding\} 当用户引导完成加载时,会触发以下事件: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK 版本 3.14 或更高" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React 组件" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { console.log('Onboarding loaded:', meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onFinishedLoading={onFinishedLoading} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK 版本 < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### 关闭用户引导 \{#closing-onboarding\} 当用户点击已分配 **Close** 动作的按钮时,用户引导即视为关闭。 <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,当用户关闭用户引导时,你需要自行处理后续逻辑,例如停止显示用户引导界面。 ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK 版本 3.14 或更高" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React 组件" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding, navigation }) { const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => { navigation.goBack(); }, [navigation]); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onClose={onClose} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### 打开付费墙 \{#opening-a-paywall\} :::tip 如果想在用户引导内打开付费墙,请处理此事件。如果想在付费墙关闭后再打开新的付费墙,有一种更直接的方式——处理关闭操作,直接打开付费墙,无需依赖事件数据。 ::: 在用户引导中使用付费墙最顺畅的方式,是将操作 ID 设置为与付费墙版位 ID 相同。 <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React 组件" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => { openPaywall(actionId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onPaywall={onPaywall} /> ); } const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> 请注意,在 iOS 上,同一时间只能显示一个视图(付费墙或用户引导)。如果你在用户引导上方展示付费墙,则无法以编程方式控制后台的用户引导。尝试关闭用户引导时,实际上会关闭付费墙,导致用户引导仍然可见。为避免此问题,请始终在展示付费墙之前先关闭用户引导视图。 ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> :::note 请注意,在 iOS 上,同一时间只能显示一个视图(付费墙或用户引导)。如果在用户引导之上呈现付费墙,则无法通过代码控制后台的用户引导。此时尝试关闭用户引导,实际上会关闭付费墙,导致用户引导仍然可见。为避免此问题,请务必在呈现付费墙之前先关闭用户引导视图。 ::: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // 在此处实现你的付费墙打开逻辑 }; ``` </TabItem> </Tabs> <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### 追踪导航 \{#tracking-navigation\} 在用户引导流程中,当各类导航相关事件发生时,你会收到相应的分析事件: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK version 3.14 or later" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => { trackEvent(event.name, meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> `event` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `onboardingStarted` | 用户引导加载完成时触发 | | `screenPresented` | 任意页面显示时触发 | | `screenCompleted` | 页面完成时触发。包含可选字段 `elementId`(已完成元素的标识符)和可选字段 `reply`(用户的响应)。当用户执行任意操作离开当前页面时触发。 | | `secondScreenPresented` | 第二个页面显示时触发 | | `userEmailCollected` | 用户通过输入框提交邮箱时触发 | | `onboardingCompleted` | 用户到达 ID 为 `final` 的页面时触发。如需使用此事件,请[为最后一个页面分配 `final` ID](design-onboarding)。 | | `unknown` | 用于任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据)| 每个事件都包含 `meta` 信息,具体字段如下: | 字段 | 说明 | |------------|-------------| | `onboardingId` | 用户引导流程的唯一标识符 | | `screenClientId` | 当前屏幕的标识符 | | `screenIndex` | 当前屏幕在流程中的位置 | | `screensTotal` | 流程中的屏幕总数 | <Details> <summary>事件示例(点击展开)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: react-native-onboarding-input --- --- title: "在 React Native SDK 中处理用户引导数据" description: "使用 Adapty SDK 在 React Native 应用中保存并使用用户引导数据。" --- :::warning **用户引导功能在 SDK v4 中已弃用,将在未来版本中移除。** 该功能不再接受修复或改进。请改用 [flows](react-native-get-pb-paywalls):与在 WebView 中运行的用户引导不同,flows 直接在设备上原生渲染——带来更流畅的动画、一致的原生外观体验、更快的加载速度,以及无需依赖 WebView 运行时。请参阅 [获取 flows 与付费墙](react-native-get-pb-paywalls) 和 [展示 flows 与付费墙](react-native-present-paywalls) 以开始使用。 ::: 当用户回答测验问题或在输入字段中填写数据时,`onStateUpdatedAction` 方法将被调用。您可以在代码中保存或处理字段类型。 例如: ```javascript // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Process data }, }} /> ``` 查看操作格式,请参阅[此处](https://react-native.adapty.io/types/onboardingstateupdatedaction)。 <Details> <summary>已保存数据示例(格式可能因具体实现而有所不同)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "elementType": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "totalScreens": 3 } } // Example of a saved multi-select action { "elementId": "interests_selector", "elementType": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ], "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "totalScreens": 3 } } // Example of a saved input action { "elementId": "name_input", "elementType": "input", "value": { "type": "text", "value": "John Doe" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } // Example of a saved date picker action { "elementId": "birthday_picker", "elementType": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用用户数据丰富用户画像 \{#enrich-user-profiles-with-data\} 如果你想立即将输入数据与用户画像关联,避免重复询问相同信息,可以在处理操作时通过[更新用户画像](react-native-setting-user-attributes)来保存这些输入数据。 例如,你让用户在 ID 为 `name` 的文本字段中输入姓名,并希望将该字段的值设置为用户的名字;同时,你还让用户在 `email` 字段中输入电子邮件。在你的应用代码中,实现方式如下: ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` ### 根据用户回答定制付费墙 \{#customize-paywalls-based-on-answers\} 在用户引导中加入问卷测验,还可以根据用户完成用户引导后的回答,为其展示定制化的付费墙。 例如,你可以询问用户的运动经验,然后向不同的用户群体展示不同的 CTA 和产品。 1. 在用户引导编辑器中[添加问卷测验](onboarding-quizzes),并为每个选项分配有意义的 ID。 2. 根据选项 ID 处理问卷回答,并为用户[设置自定义属性](react-native-setting-user-attributes)。 ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为你创建的每个市场细分添加[目标受众](audience)。 5. 在你的应用代码中为该版位[展示付费墙](react-native-paywalls)。如果你的用户引导中有一个打开付费墙的按钮,请将付费墙代码作为[该按钮动作的响应](react-native-handling-onboarding-events#opening-a-paywall)来实现。 --- # File: react-native-sdk-call-order --- --- title: "React Native SDK 调用顺序" description: "按正确顺序调用 Adapty SDK 方法,避免付费权限丢失、归因缺失及间歇性 #2002 错误。" --- `adapty.activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其 resolve 之前,SDK 没有任何状态。在 `activate()` 之前或与其并行发出的任何调用都会以 [`#2002 notActivated`](react-native-handle-errors#custom-network-codes) 错误失败。 如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到后调用 `adapty.identify()`。在 `identify` 完成之前,不要调用任何用户操作相关的方法。与 `identify` 并发执行的调用要么会以 [`#3006 profileWasChanged`](react-native-handle-errors#custom-network-codes) 错误失败,要么会落到激活时创建的匿名用户画像上。一旦出现这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属并不总能迁移到已识别的用户画像上。如果你的应用不需要用户认证,则跳过 `identify`,继续使用匿名用户画像即可。 MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待其 UID 回调后再调用 `adapty.activate`。否则 MMP ID 会绑定到一个短暂的匿名用户画像上,不一定能转移到已识别的用户画像。有关 AppsFlyer 的具体信息,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 您的路径取决于两件事:何时获取用户 ID,以及是否使用 MMP 或分析 SDK。 - **步骤 2 和 5**:每个应用都必须完成。先激活 SDK,再调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。 - **步骤 4**:仅在应用需要用户身份验证、且在启动后才能获取用户 ID 时需要。 如果在应用启动时已有客户用户 ID,可直接将其传入 `activate()`(步骤 2a)。这种方式不会创建匿名用户画像,因此无需执行步骤 4。 | 步骤 | 调用 | 时机 | 备注 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动,最先执行 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 | | 2a | `adapty.activate('YOUR_PUBLIC_SDK_KEY', { customerUserId: 'YOUR_USER_ID' })` | 应用启动,步骤 1 之后,如果已有 customer user ID | 推荐。不会创建任何匿名用户画像。 | | 2b | `adapty.activate('YOUR_PUBLIC_SDK_KEY')` 不传 `customerUserId` | 应用启动,步骤 1 之后,如果没有 customer user ID(或从不收集) | Adapty 会创建匿名用户画像。 | | 3 | 为每个 MMP 调用 `adapty.updateAttribution(data, source, networkUserId)` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,以确保 MMP ID 关联到正确的用户画像。 | | 4 | `await adapty.identify('YOUR_USER_ID')` | 步骤 3 之后(如无 MMP 则在步骤 2 之后),步骤 5 之前——仅适用于路径 2b 且需要身份验证时 | 必须使用 `await`。`identify` 期间并发调用会产生 `#3006 profileWasChanged`。 | | 5 | `getPaywall`、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 如果调用了 `identify`,则在步骤 4 之后;否则在步骤 3 之后(如无 MMP 则在步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回归用户丢失高级访问权限、用户画像缺少 `appsflyer_id`,以及付费墙按错误的目标受众返回。 ::: ## Web2app 与网页漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在网页端结账(Stripe、Paddle),之后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与网页端的用户画像关联。如果你能在应用启动前(通过身份验证流程或安装来源引用)确定用户 ID,请直接将其传入 `activate()`。否则,在你调用 `identify('YOUR_USER_ID')` 并执行 `restorePurchases` 之前,设备上将无法看到网页端的购买记录。 关于每次网页结账需要发送的元数据,请参阅: - [Stripe](stripe) - [Paddle](paddle) --- # File: react-native-optimize-paywall-fetching --- --- title: "优化 React Native SDK 中的付费墙获取" description: "可靠地获取 Adapty 付费墙:React Native 的时机、缓存与备用模式。" --- 在 React Native 中可靠地获取付费墙需要做到三点:渲染速度快、返回针对目标受众的付费墙、以及在网络较慢时优雅降级。以下规则涵盖了实现这些目标所需的时机、缓存和备用模式。 :::tip 以下规则假设 `adapty.activate()` 和 `adapty.identify()` 已执行完毕。详见 [React Native SDK 的调用顺序](react-native-sdk-call-order)。 ::: ## 规则与注意事项 \{#rules-and-pitfalls\} | 推荐做法 | 避免做法 | 原因 | |---|---|---| | 仅在即将展示的版位时才去获取。 | 在启动时并发预取所有版位。 | 批量预取会阻塞 JS 线程,导致启动过程中出现黑屏。 | | 在归因有机会完成解析之后再调用 `getPaywall`——例如在 `activate` 后等待 1–2 秒,或等待 `onProfileUpdate` 触发。 | 在根组件挂载时调用 `getPaywall`。 | 归因数据尚未落地,付费墙将按默认目标受众解析,静默跳过市场细分和 ASA 个性化。 | | 设置 `loadTimeoutMs`,并为每个版位配置[备用付费墙](fallback-paywalls)。 | 无限期等待 `getPaywall` 返回。 | 若没有超时限制,网络较差的用户会看到空白屏幕,直到网络恢复——或者直接关闭应用。 | 有关 `fetchPolicy` 和 `loadTimeoutMs` 参数的说明,请参阅[获取付费墙和产品](fetch-paywalls-and-products-react-native);有关如何选择合适的版位,请参阅[版位](placements)。 ## 针对网络较差的情况进行调优 \{#tune-for-poor-connectivity\} 对于网络持续较差的市场(偏远地区、交通途中、受路由影响的地区): - 除首次请求外,所有请求都设置 `fetchPolicy: .returnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeoutMs` 设置为 3–5 秒,并在超时触发时接受备用付费墙。 - 不要让付费墙的展示依赖 `getProfile()`。将 `getPaywall` 独立调用,避免因 profile 加载缓慢而阻塞 UI。 --- # File: react-native-show-aa-targeted-paywall --- --- title: "在 React Native SDK 中首次启动时展示 AA 定向付费墙" description: "在 React Native 中,通过 AdaptyProfile.appliedAttributionSources 立即展示付费墙,并在归因应用后为 Apple Ads 用户升级付费墙。" --- Apple Ads (AA) 归因数据在 `adapty.activate()` 之后异步到达。首次启动时,归因数据通常尚未到位,因此 `getPaywall` 会按默认目标受众解析,Apple Ads 用户将错过为 AA 市场细分设置的付费墙。与其等待归因数据到位后再展示付费墙,不如先立即展示一个,等 AA 归因应用后再刷新——这样 Apple Ads 用户能看到精准定向的实验变体,其他用户也无需等待。`AdaptyProfile.appliedAttributionSources` 可告知你 AA 归因何时已被应用。 ## 开始之前 \{#before-you-start\} 你需要: - Adapty React Native SDK **3.17.1** 或更高版本。 - 在 Adapty 中为应用配置好 Apple Ads。请参阅 [Apple Ads](apple-search-ads)。 ## 工作原理 \{#how-it-works\} 调用 `adapty.activate()` 后,SDK 会在后台向 Apple 请求 Apple Ads 归因数据,并将结果转发至 Adapty 后端。当 AA 成为该用户画像的有效归因来源时,SDK 会向你的 `onLatestProfileLoad` 监听器推送更新后的 `AdaptyProfile`,其 `appliedAttributionSources` 数组中会包含 `'apple_search_ads'`。 这样你就可以分两步加载付费墙: 1. 立即调用 `getPaywall`。由于此时尚未应用归因数据,Adapty 会根据默认目标受众解析请求,用户可立即看到付费墙。 2. 当出现 `'apple_search_ads'` 时,再次调用 `getPaywall`。Adapty 此时会根据 Apple Ads 目标受众解析请求,并返回针对性付费墙,替换第一个付费墙。 `appliedAttributionSources` 可以为空或不存在,这意味着: - 该用户画像的 Apple Ads 归因尚未处理完成,或 - 完全没有收到任何归因数据。 无论如何,第一步都是安全的——Adapty 会根据当前用户画像状态匹配的目标受众(通常是默认受众)来处理请求。第二步仅在 `'apple_search_ads'` 出现后才会执行。 :::important 在每次后续启动时,缓存的用户画像已经在 `appliedAttributionSources` 中包含 `'apple_search_ads'`,因此第一次 `getPaywall` 就会直接返回针对 Apple Ads 市场细分的付费墙——不会有第二次请求,也不会有任何可见变化。这个两步流程只在首次启动时有意义,因为那时归因数据仍在处理中。 ::: ## 实现 \{#implementation\} 立即展示付费墙,然后监听 `'apple_search_ads'` 事件,待其到达后刷新付费墙。 1. **激活 SDK。** 请参阅[安装与配置 React Native SDK](sdk-installation-reactnative)。 2. **使用 `getPaywall` 正常加载并展示付费墙** — 不要等待归因数据才展示。 3. **通过 `adapty.addEventListener('onLatestProfileLoad', …)` 订阅用户画像更新**,并监听 `'apple_search_ads'`。当该字段出现时,重新获取付费墙并展示更新后的版本。如果你还没有设置监听器,请参阅[监听订阅更新](react-native-check-subscription-status#listen-to-subscription-updates): ```typescript const subscription = adapty.addEventListener('onLatestProfileLoad', async profile => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getPaywall(placementId); // present the targeted paywall in place of the first one }); // Call subscription.remove() after the upgrade, or after a timeout (see below). ``` 4. **超时后停止监听。** 大多数用户不会获得 Apple Ads 归因数据,因此请在一段时间后移除监听器,而不是在整个会话中保持其开启。为版位配置[备用付费墙](react-native-use-fallback-paywalls),这样即使请求失败,用户也能看到内容。 ## 完整示例 \{#complete-example\} `onAppleAdsAttribution` 在 Apple Ads 归因成功应用后 resolve,或在 `timeoutMs` 超时后 reject。下面的示例会立即加载付费墙,然后在归因数据到达时重新获取——Apple Ads 用户将看到定向付费墙,若归因始终未到达,则继续使用首次加载的付费墙: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let subscription: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); subscription?.remove(); }; subscription = adapty.addEventListener('onLatestProfileLoad', profile => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let paywall = await adapty.getPaywall(placementId); onAppleAdsAttribution(30_000) .then(() => adapty.getPaywall(placementId)) .then(updated => { paywall = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` 首次启动时,Apple Ads 用户会短暂看到默认付费墙,随后被替换。如果你使用付费墙编辑工具展示付费墙,请决定是否接受重新展示,或仅在付费墙显示之前进行更新。根据你愿意等待的时长来调整 `timeoutMs`——通常情况下,归因数据会在启动后几秒内到达。 如果你的应用已经出于其他目的监听 `onLatestProfileLoad`(例如[检查订阅状态](react-native-check-subscription-status#listen-to-subscription-updates)),则无需做任何修改。`adapty.addEventListener` 支持多个独立监听器,因此这里只是新增一个,不会影响其他监听器。 --- # File: react-native-test --- --- title: "在 React Native SDK 中测试与发布" description: "了解如何使用 Adapty SDK 测试和发布您的 React Native 应用。" --- 如果您已经在 React Native 应用中实现了 Adapty SDK,您将需要测试所有内容是否正确配置,以及购买功能是否在 iOS 和 Android 平台上均按预期运行。这包括使用 Apple 沙盒环境和 Google Play 测试环境,对 SDK 集成和实际购买流程进行测试。 ## 测试您的应用 \{#test-your-app\} 有关应用内购买的全面测试,请参阅我们的平台专项测试指南:[iOS 测试指南](test-purchases-in-sandbox) 和 [Android 测试指南](testing-on-android)。 ## 准备发布 \{#prepare-for-release\} 在将应用提交至应用商店之前,请按照[发布检查清单](release-checklist)确认以下事项: - 已配置商店连接和服务器通知 - 购买已完成并上报至 Adapty - 访问等级解锁与恢复功能正常 - 满足隐私和审核要求 --- # File: InvalidProductIdentifiers-react-native --- --- title: "修复 React Native SDK 中的 Code-1000 noProductIDsFound 错误" description: "解决在 Adapty 中管理订阅时出现的无效产品标识符错误。" --- 1000 代码错误 `noProductIDsFound` 表示你在付费墙中请求的产品,尽管已在 App Store 中列出,但目前无法购买。此错误有时会附带 `InvalidProductIdentifiers` 警告。如果只出现警告而没有错误,可以忽略。 如果你遇到了 `noProductIDsFound` 错误,请按以下步骤排查解决: ## 步骤 1:检查 Bundle ID \{#step-2-check-bundle-id\} 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 第 2 步:检查产品 \{#step-3-check-products\} 1. 前往 **App Store Connect**,在左侧菜单中导航至 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions)。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,在 **Subscriptions** 部分可以看到你的产品列表。 3. 确认你要测试的产品已标记为 **Ready to Submit**。 <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将表格中的产品 ID 与 Adapty 看板中 [**Products**](https://app.adapty.io/products) 标签页内的产品 ID 进行对比。如果 ID 不匹配,请从表格中复制产品 ID,并在 Adapty 看板中[创建产品](create-product)。 <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 3 步:检查产品可用性 \{#step-4-check-product-availability\} 1. 返回 **App Store Connect**,打开同一个 **Subscriptions** 页面。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称以查看您的产品。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 滚动至 **Availability** 部分,确认所有所需的国家和地区均已列出。 <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第四步:检查产品价格 \{#step-5-check-product-prices\} 1. 再次前往 **App Store Connect** 中的 **Monetization** → **Subscriptions** 板块。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动至 **Subscription Pricing**,展开 **Current Pricing for New Subscribers** 部分。 <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 确保列出所有必要的价格。 <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 5. 检查应用付费状态、银行账户及税务表格是否有效 \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. 在 **App Store Connect**](https://appstoreconnect.apple.com/) 主页,点击 **Business**。 <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择您的公司名称。 3. 向下滚动,确认您的 **Paid Apps Agreement**、**Bank Account** 和 **Tax forms** 均显示为 **Active**。 按照以上步骤,您应该能够解决 `InvalidProductIdentifiers` 警告,并使您的产品在商店中正常上线。 ## 第六步:如果产品卡住了,尝试删除重建 \{#step-6-recreate-the-product-if-its-stuck\} 前五步可能全部通过——状态为 `Approved`、Bundle ID 匹配、API Key 有效——但 SDK 仍然返回 `1000 noProductIDsFound`。这种情况下,产品可能卡在了 Apple 的注册表中。Apple 的产品注册表偶尔会出现这样的状态:产品在 App Store Connect 的界面中存在,但无法通过 StoreKit 的查找路径访问到。 在 App Store Connect 中删除该产品,然后用相同的产品 ID 重新创建。重新创建后,最多等待 24 小时以完成数据同步。 --- # File: cantMakePayments-react-native --- --- title: "修复 React Native SDK 中的 Code-1003 cantMakePayment 错误" description: "解决在 Adapty 中管理订阅时出现的支付错误。" --- 1003 错误 `cantMakePayments` 表示该设备无法进行应用内购买。 如果你遇到了 `cantMakePayments` 错误,通常是由以下原因之一导致的: - 设备限制:该错误与 Adapty 无关。请参阅下方的修复方法。 - 观察者模式配置:`makePurchase` 方法与观察者模式不能同时使用。请参阅下方相关章节。 ## 问题:设备限制 \{#issue-device-restrictions\} | 问题 | 解决方案 | |---------------------------|---------------------------------------------------------| | 屏幕使用时间限制 | 在 [Screen Time](https://support.apple.com/en-us/102470) 中关闭应用内购买限制 | | 账户被暂停 | 联系 Apple 支持以解决账户问题 | | 地区限制 | 使用受支持地区的 App Store 账户 | ## 问题:同时使用观察者模式和 makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} 如果你使用 `makePurchases` 来处理购买,则无需启用观察者模式。[观察者模式](observer-vs-full-mode) 仅在你自行实现购买逻辑时才需要使用。 因此,如果你正在使用 `makePurchase`,可以安全地从 SDK 激活代码中移除对观察者模式的启用。 --- # File: migration-to-react-native-sdk-v4 --- --- title: "将 Adapty React Native SDK 迁移至 v. 4.0" description: "通过将付费墙 API 替换为 flow API,迁移至 Adapty React Native SDK v4.0(测试版),兼容 Flow Builder 和 付费墙编辑工具。" --- Adapty React Native SDK 4.0(测试版)引入了 flow 功能,并相应地重命名了付费墙 API。新 API 同时兼容全新的 Flow Builder 和现有的付费墙编辑工具——无需在 Adapty 看板端进行任何配置变更。 ## 快速参考 \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall(placementId, locale?, params?)` | `adapty.getFlow(placementId, params?)` | | `adapty.getPaywallForDefaultAudience(placementId, locale?, params?)` | `adapty.getFlowForDefaultAudience(placementId, params?)` | | `adapty.getPaywallProducts(paywall)` | `adapty.getPaywallProducts(flow)` | | `adapty.logShowPaywall(paywall)` | `adapty.logShowFlow(flow)` | | `AdaptyPaywall`(类型) | `AdaptyFlow` | | `createPaywallView(paywall)` | `createFlowView(flow)` | | `AdaptyPaywallView`(组件) | `AdaptyFlowView` | | `EventHandlers`(类型) | `FlowEventHandlers` | | `onPaywallShown` | `onAppeared` | | `onPaywallClosed` | `onDisappeared` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` 保持原有命名——产品仍归属于流程,`getPaywallProducts` 现在接收 `AdaptyFlow` 参数。`getFlow` 和 `getFlowForDefaultAudience` 方法不再接受 `locale` 参数。视图方法 `present`、`dismiss`、`setEventHandlers`、`showDialog`,以及事件处理器 `onCloseButtonPress`、`onUrlPress`、`onCustomAction`、`onProductSelected`、`onPurchaseStarted`、`onPurchaseCompleted`、`onPurchaseFailed`、`onRestoreStarted`、`onRestoreCompleted`、`onRestoreFailed`、`onLoadingProductsFailed`、`onWebPaymentNavigationFinished` 和 `onAndroidSystemBack` 均与 v3 保持相同命名。部分默认行为有所变更——详见[默认行为变更](#default-behavior-changes)。 ## 最低 iOS 版本 \{#minimum-ios-version\} Adapty React Native SDK 4.0 将最低 iOS 部署目标从 iOS 13.0 提升至 **iOS 15.0**。升级前,请将您的 iOS 部署目标设置为 15.0 或更高版本。 ## 安装 \{#installation\} ### 更新软件包 \{#update-the-package\} v4.0 为预发布版本,请固定精确版本号——npm 不会通过 caret/tilde 范围选取预发布版本: ```bash showLineNumbers npm install react-native-adapty@4.0.0 # or yarn add react-native-adapty@4.0.0 ``` ### iOS:原生 SDK 现在通过 Swift Package Manager 分发 \{#ios-native-sdks-now-come-through-swift-package-manager\} [CocoaPods 的 spec 仓库将于 2026 年 12 月变为只读](https://blog.cocoapods.org/CocoaPods-Specs-Repo/),因此从 v4 开始,原生的 `Adapty`、`AdaptyUI` 和 `AdaptyPlugin` SDK **不再作为 CocoaPods 子依赖项引入** —— podspec 改为通过 **Swift Package Manager**(借助 `spm_dependency` 帮助函数)来拉取它们。这需要满足以下两个条件: - **React Native 0.75 或更高版本** — 需要 `spm_dependency` podspec 辅助函数。在旧版本上,`pod install` 会报明确错误;请先升级 React Native,或继续使用 `react-native-adapty` 3.x。 - **动态框架** — SPM 依赖项需要动态链接。启用方式因 Expo 和裸 React Native 而有所不同。 #### Expo 添加 [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/) 配置插件,并在 `app.json`(或 `app.config.js`)中将 iOS 框架设置为动态: ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` 然后安装插件并重新生成原生项目: ```bash showLineNumbers npx expo install expo-build-properties npx expo prebuild --clean ``` #### Bare React Native 在你的 iOS target 中添加动态框架,然后重新安装 pods: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```bash showLineNumbers cd ios && pod install --repo-update ``` 如果你之前通过 CocoaPods 子依赖的方式引入了 `Adapty`、`AdaptyUI` 或 `AdaptyPlugin`,请先从 `Podfile` 中删除所有显式的 `pod 'Adapty'`、`pod 'AdaptyUI'` 或 `pod 'AdaptyPlugin'` 行。 :::warning 从默认静态链接切换到动态框架可能与尚不支持模块化头文件的库产生冲突,且与 Flipper 不兼容。如果遇到构建问题,请参阅这篇[关于将 Swift Package Manager 与 React Native 库集成的文章](https://www.callstack.com/blog/integrating-swift-package-manager-with-react-native-libraries)。 ::: 完整的安装步骤,请参阅[安装 Adapty SDK](sdk-installation-reactnative)。 ## 获取流程 \{#fetching-flows\} ### getPaywall → getFlow 返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,同时移除了 `locale` 参数——渲染 flow 时,语言环境会自动解析;对于自定义付费墙,所有语言环境均通过 `flow.remoteConfigs` 返回: ```diff showLineNumbers - const paywall = await adapty.getPaywall('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` 也以同样的方式重命名: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlowForDefaultAudience('YOUR_PLACEMENT_ID'); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` 保持名称不变,但现在接受 `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts(paywall); + const products = await adapty.getPaywallProducts(flow); ``` ## 数据模型 \{#data-model\} `getFlow` 返回的是 `AdaptyFlow` 而非 `AdaptyPaywall`,且对象结构有所变化: | v3 `AdaptyPaywall` 字段 | v4 `AdaptyFlow` 字段 | 操作 | |---|---|---| | `remoteConfig?`(单个) | `remoteConfigs?: AdaptyRemoteConfig[]`(数组) | 一个流程为每种已配置的语言各携带一份远程配置。读取与用户匹配的那份:`flow.remoteConfigs?.find((c) => c.lang === 'en')`。 | | `products` | `flow.paywalls[i].productIdentifiers` | 产品标识符现在位于每个流程变体上,而非流程本身。 | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | 从流程移至每个付费墙变体。 | | `version?: number` | `flowVersionId?: string` | 已重命名,类型从 `number` 改为 `string`。 | | `hasViewConfiguration` | 已移除 | 从代码中删除所有 `hasViewConfiguration` 检查。 | | `requestLocale` | 已移除 | 语言区域不再是模型的一部分。 | | _(新增)_ | `paywalls: AdaptyFlowPaywall[]` | 每个条目代表流程中的一个付费墙变体。 | | _(新增)_ | `responseCreatedAt: number` | 服务器响应时间戳,单位为毫秒。 | 产品标识符已从流程移至每个实验变体: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Web 付费墙方法 \{#web-paywall-methods\} `openWebPaywall` 和 `createWebPaywallUrl` 方法名保持不变,但第一个参数现在是 `AdaptyFlowPaywall`(流程变体),而非 `AdaptyPaywall`。你仍然可以传入 `AdaptyPaywallProduct`。 ```diff showLineNumbers const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); - await adapty.openWebPaywall(paywall); + await adapty.openWebPaywall(flow.paywalls[0]); ``` ## 追踪流程查看次数 \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` 已重命名为 `logShowFlow`,现在接收一个 `AdaptyFlow` 参数。事件仍会记录到同一实验变体,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。 ```diff showLineNumbers - await adapty.logShowPaywall(paywall); + await adapty.logShowFlow(flow); ``` 与 v3 相同,当使用 [Flow Builder](adapty-flow-builder) 或[付费墙编辑工具](adapty-paywall-builder)渲染流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些页面的展示。 ## 展示流程 \{#displaying-flows\} ### createPaywallView → createFlowView 重命名工厂函数并传入 `AdaptyFlow`。返回的控制器方法(`present`、`dismiss`、`setEventHandlers`、`showDialog`)保持不变: ```diff showLineNumbers - import { createPaywallView } from 'react-native-adapty'; + import { createFlowView } from 'react-native-adapty'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` ### AdaptyPaywallView → AdaptyFlowView 如果你使用 React 组件渲染,请将其重命名并传入 `flow` prop: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty'; + import { AdaptyFlowView } from 'react-native-adapty'; - <AdaptyPaywallView paywall={paywall} /* … */ /> + <AdaptyFlowView flow={flow} /* … */ /> ``` :::note 使用 `createFlowView` 创建的流程视图只能使用一次:调用 `dismiss()` 后,该视图会被销毁,如需再次展示流程,请重新调用 `createFlowView`。嵌入式 `AdaptyFlowView` 通过卸载组件来关闭——从处理函数中返回 `true` 并不会关闭嵌入式视图,因此请改为修改自身状态,例如在 `onCloseButtonPress` 中进行处理。 ::: ## 处理事件 \{#handling-events\} 事件处理器接口从 `EventHandlers` 更名为 `FlowEventHandlers`,同时有三个回调也进行了重命名。现有的处理器逻辑无需改动——只需重命名即可: ```diff showLineNumbers - onPaywallShown: () => { /* … */ }, + onAppeared: () => { /* … */ }, - onPaywallClosed: () => { /* … */ }, + onDisappeared: () => { /* … */ }, - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` 所有其他事件处理程序保持原有名称不变。其中两个新增了第二个参数:`onPurchaseCompleted` 变为 `(purchaseResult, product)`,`onPurchaseFailed` 变为 `(error, product)`,其中 `product` 是参与操作的 `AdaptyPaywallProduct`。完整列表请参阅[处理 flow 与付费墙事件](react-native-handling-events-1)。 :::note `onDisappeared` 仅在通过 `createFlowView().present()` 以模态方式呈现的 flow 中触发。`AdaptyFlowView` 组件不将其作为 prop 暴露——如需关闭嵌入式视图,请通过卸载组件来实现。 ::: v4 还新增了一些可按需启用的功能: - `adapty.openWebUrl(url, openIn?)` 和 `adapty.requestAppReview()` 方法 —— 这两个方法支持默认的 `onUrlPress` 和 `onRequestAppReview` 处理器,因此 URL 跳转和应用评价弹窗均可开箱即用地原生处理。只有在你覆盖这些处理器时,才需要直接调用它们。 - 通过新的 `onObserverPurchaseInitiated` / `onObserverRestoreInitiated` 处理器,在流程中支持观察者模式下的购买处理。详见[在观察者模式下处理购买](react-native-handling-events-1#handle-purchases-in-observer-mode)。 ## 已移除和废弃的 API \{#removed-and-deprecated-apis\} ### setFallbackPaywalls → setFallback `setFallbackPaywalls` 已被移除。请使用 `setFallback`,参数保持不变: ```diff showLineNumbers - await adapty.setFallbackPaywalls(fileLocation); + await adapty.setFallback(fileLocation); ``` ### 已移除的导出 \{#removed-exports\} 这些符号已不再从 `react-native-adapty` 导出,请移除相关导入: - **`AdaptyPaywall`**:请改用 `AdaptyFlow`。 - **`ProductReference`**:请改用 `AdaptyProductIdentifier`,从 `flow.paywalls[i].productIdentifiers` 读取。 - **`AdaptyPaywallBuilder`**:已移除。流程和付费墙均以原生方式渲染。 - **`AdaptyAndroidSubscriptionUpdateParameters`**:请改用嵌套的 `subscriptionUpdateParams` 结构(详见下文)。 ### activate: lockMethodsUntilReady `lockMethodsUntilReady` 已被移除,该行为现在默认始终开启。请从 `activate` 调用中删除它——保留该参数将导致编译错误: ```diff showLineNumbers - await adapty.activate('PUBLIC_SDK_KEY', { lockMethodsUntilReady: true }); + await adapty.activate('PUBLIC_SDK_KEY'); ``` ### makePurchase:Android 订阅更新 \{#makepurchase-android-subscription-update\} 原有的 Android 订阅更新扁平结构已移除。请将 `oldSubVendorProductId` 和 `prorationMode` 移入嵌套的 `subscriptionUpdateParams` 对象中,并将 `isOfferPersonalized` 保留在顶层。完整示例请参阅[发起购买](react-native-making-purchases)。 ### Android:安全区域内边距 \{#android-safe-area-paddings\} Android 布尔资源 `<bool name="adapty_paywall_enable_safe_area_paddings">…</bool>` 已被移除。请从 `res/values/bools.xml` 中删除该条目,并在创建流程视图时通过 `enableSafeArea` 参数在运行时控制安全区域内边距。该参数在模态展示时默认为 `true`,在嵌入式组件中默认为 `false`。 ### 模拟模式 \{#mock-mode\} 如果你在模拟模式下运行 SDK(Expo Go 或 Web 预览),请将模拟配置的键名 `paywalls` 改为 `flows`。 ## 默认行为变更 \{#default-behavior-changes\} 以下变更不会引起编译错误,请在运行时进行测试: - **`onAndroidSystemBack`**: 默认行为已从关闭视图改为保持视图打开。若要恢复之前的行为,请在处理程序中返回 `true`。 - **`onPurchaseCompleted`**: 默认行为已从关闭视图(除非用户取消购买)改为始终保持视图打开。若要恢复之前的行为,请在处理程序中返回 `purchaseResult.type !== 'user_cancelled'`。 - **`onRestoreCompleted`**: 默认行为已从恢复成功后关闭视图改为保持视图打开。若要恢复之前的行为,请在处理程序中返回 `true`。 - **`onUrlPress`**: 现在默认通过原生层打开 URL,遵循看板中设置的应用内或外部浏览器选项。如需自行控制 URL 的打开方式,请覆盖该处理程序。 ## 用户引导 API 弃用 \{#onboarding-api-deprecation\} 旧版用户引导 API 已在 v4.0 中弃用,请改用 [Flow Builder](adapty-flow-builder)。该 API 目前仍可正常使用,IDE 会通过 `@deprecated` 注解标记已弃用的符号——不会产生任何运行时警告。这些符号将在未来版本中移除,请提前将您的用户引导迁移至 Flow Builder。 已弃用的符号:`getOnboarding`、`getOnboardingForDefaultAudience`、`createOnboardingView` 和 `AdaptyOnboardingView`。 --- # File: migration-react-native-314 --- --- title: "迁移 Adapty React Native SDK 至 v3.14" description: "迁移至 Adapty React Native SDK v3.14,享受更好的性能与全新的变现功能。" --- Adapty React Native SDK 3.14.0 是一个主要版本,引入了一些需要你进行迁移操作的改进: - `registerEventHandlers` 方法已替换为 `setEventHandlers` 方法。 - 在 `AdaptyOnboardingView` 中,事件处理器现在以独立 props 的形式传入,而不再使用 `eventHandlers` 对象。 - 为 UI 组件引入了全新的简化导入方式。 - `logShowOnboarding` 方法已删除。 - React Native 最低版本要求已更新至 0.73.0。 - 付费墙和用户引导的 iOS 默认展示样式已从页面表单更改为全屏。 ## 将 `registerEventHandlers` 替换为 `setEventHandlers` \{#replace-registereventhandlers-with-seteventhandlers\} 用于 Adapty 付费墙编辑工具和用户引导编辑工具的 `registerEventHandlers` 方法已被替换为 `setEventHandlers` 方法。 如果你使用了 Adapty 付费墙编辑工具和/或 Adapty 用户引导编辑工具,请在应用代码中找到 `registerEventHandlers` 并将其替换为 `setEventHandlers`。 此更改旨在让方法行为更加清晰:每个处理器返回 `true`/`false`,因此处理器采用单一生效的方式运行,为同一事件注册多个处理器会导致最终行为难以预测。 请注意,使用 `AdaptyOnboardingView` 或 `AdaptyPaywallView` 等 React 组件时,无需在事件处理函数中返回 `true`/`false`,因为你可以通过自己的状态管理来控制组件的显示与隐藏。返回值仅在以模态框形式展示页面时才需要,此时 SDK 负责管理视图的生命周期。 :::important 多次调用 `setEventHandlers` 会覆盖你已设置的处理函数,替换掉这些特定事件的默认处理函数及之前设置的处理函数。 ::: ```diff showLineNumbers - const unsubscribe = view.registerEventHandlers({ - // your event handlers - }) const unsubscribe = view.setEventHandlers({ // your event handlers }) ``` ## 更新 UI 组件的导入路径 \{#update-import-paths-for-ui-components\} Adapty SDK 3.14.0 引入了更简洁的 UI 组件导入方式。现在你可以直接从 `react-native-adapty` 导入,无需再从 `react-native-adapty/dist/ui` 导入。 新的导入方式更符合标准的 React Native 使用习惯,也让导入语句更加简洁。如果你正在使用 `AdaptyPaywallView` 或 `AdaptyOnboardingView` 等 UI 组件,请按照以下示例更新你的导入语句: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty/dist/ui'; + import { AdaptyPaywallView } from 'react-native-adapty'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; - import { createPaywallView } from 'react-native-adapty/dist/ui'; + import { createPaywallView } from 'react-native-adapty'; - import { createOnboardingView } from 'react-native-adapty/dist/ui'; + import { createOnboardingView } from 'react-native-adapty'; ``` :::note 为保持向后兼容性,旧的导入方式(`react-native-adapty/dist/ui`)仍然受支持。但我们建议使用新的导入方式,以保持一致性和清晰度。 ::: ## 更新 React 组件中的用户引导事件处理器 \{#update-onboarding-event-handlers-in-the-react-component\} 用户引导的事件处理器已从 `AdaptyOnboardingView` 的 `eventHandlers` 对象中移出。如果你正在使用 `AdaptyOnboardingView` 展示用户引导,请更新事件处理结构。 :::important 请注意我们推荐的事件处理器实现方式。为避免每次渲染时重新创建对象,请对处理事件的函数使用 `useCallback`。 ::: ```diff showLineNumbers import React, { useCallback } from 'react'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; + import type { OnboardingEventHandlers } from 'react-native-adapty'; + + function MyOnboarding({ onboarding }) { + const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); + const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); + const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); + const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); + const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); + const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); + const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); + return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} - eventHandlers={{ - onAnalytics(event, meta) { /* ... */ }, - onClose(actionId, meta) { /* ... */ }, - onCustom(actionId, meta) { /* ... */ }, - onPaywall(actionId, meta) { /* ... */ }, - onStateUpdated(action, meta) { /* ... */ }, - onFinishedLoading(meta) { /* ... */ }, - onError(error) { /* ... */ }, - }} + onAnalytics={onAnalytics} + onClose={onClose} + onCustom={onCustom} + onPaywall={onPaywall} + onStateUpdated={onStateUpdated} + onFinishedLoading={onFinishedLoading} + onError={onError} /> ); + } ``` :::note 为了向后兼容,`eventHandlers` prop 仍受支持,但已被弃用。我们建议迁移到如上所示的独立事件处理器 props。 ::: ## 删除 `logShowOnboarding` \{#delete-logshowonboarding\} 在 Adapty SDK 3.14.0 中,我们已从 SDK 中删除了 `logShowOnboarding` 方法。 如果您一直在使用此方法,将 SDK 升级到 3.14 或更高版本后,该方法将不再可用。 作为替代,您可以[在 Adapty 无代码用户引导编辑工具中创建用户引导](onboardings)。这些用户引导的分析数据会自动追踪,并且您有大量的自定义选项。 ## 更新 React Native \{#update-react-native\} 从 Adapty SDK 3.14.0 开始,React Native 的最低支持版本为 0.73.0。如果您使用的是较早版本,请将 React Native 更新至 0.73.0 或更高版本,以确保您使用 Adapty SDK 的体验保持一致和可靠。 ## 更新模态付费墙和用户引导的 iOS 呈现样式 \{#update-ios-presentation-style-for-modal-paywalls-and-onboardings\} 在 Adapty SDK 3.14.0 中,使用 `view.present()` 方法显示的付费墙和用户引导的默认 iOS 呈现样式已从页面表单更改为全屏。 如果您想保留之前的页面表单呈现样式,请将 `iosPresentationStyle` 参数传递给 `present()` 方法: ```typescript showLineNumbers title="React Native (TSX)" try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` --- # File: react-native-migration-guide-380 --- --- title: "迁移 Adapty React Native SDK 至 v3.8" description: "迁移至 Adapty React Native SDK v3.8,获得更好的性能和新的变现功能。" --- Adapty SDK 3.8.0 是一个重大版本更新,带来了一些改进,但可能需要你执行一些迁移步骤。 ## 更新获取版位参数的输入类型 \{#update-input-type-for-getting-placement-params\} `GetPaywallParamsInput` 已重命名为 `GetPlacementParamsInput`: ```diff showLineNumbers - type GetPaywallParamsInput = { + type GetPlacementParamsInput = { placementId: string; locale?: string; fetchPolicy?: AdaptyPlacementFetchPolicy; loadTimeoutMs?: number; } ``` ## 更新备用付费墙方法 \{#update-fallback-method\} 设置备用付费墙的方法已更新,用于指定备用付费墙位置的类型也已重命名: ```diff showLineNumbers - adapty.setFallbackPaywalls(paywallsLocation: Input.FallbackPaywallsLocation); + adapty.setFallback(fileLocation: Input.FileLocation); ``` ## 更新付费墙属性访问方式 \{#update-paywall-property-access\} 以下属性已从 `AdaptyPaywall` 移至 `AdaptyPlacement`: ```diff showLineNumbers - paywall.abTestName - paywall.audienceName - paywall.revision - paywall.placementId + paywall.placement.abTestName + paywall.placement.audienceName + paywall.placement.revision + paywall.placement.id ``` --- # File: migration-to-react-native-sdk-34 --- --- title: "迁移 Adapty React Native SDK 至 v3.4" description: "迁移至 Adapty React Native SDK v3.4,获得更好的性能和新的变现功能。" --- Adapty SDK 3.4.0 是一个重要版本,引入了若干需要你进行迁移操作的改进。 ## 更新备用付费墙文件 \{#update-fallback-paywall-files\} 更新您的备用付费墙文件以确保与新 SDK 版本的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 用新文件[替换移动应用中现有的备用付费墙](react-native-use-fallback-paywalls)。 ## 更新 Observer Mode 的实现方式 \{#update-implementation-of-observer-mode\} 如果你正在使用 Observer Mode,请务必更新其实现方式。 此前,向 Adapty 上报交易时使用的方法各不相同。新版本统一使用 `reportTransaction` 方法,在 Android 和 iOS 上保持一致。该方法会明确将每笔交易上报给 Adapty,确保交易被正确识别。如果使用了付费墙,请传入 variation ID,以便将该交易与对应的付费墙关联起来。 :::warning **不要跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成渠道。 ::: ```diff showLineNumbers - if (Platform.OS === 'android') { - try { - await adapty.restorePurchases(); - } catch (error) { - // handle the error - } - } const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native330 --- --- title: "迁移 Adapty React Native SDK 至 v3.3" description: "迁移至 Adapty React Native SDK v3.3,享受更佳性能与新的变现功能。" --- Adapty SDK 3.3.1 是一个重要版本,带来了一些改进,可能需要你完成相应的迁移步骤。 1. 升级到 Adapty SDK v3.3.x。 2. 更新模型。 3. 移除 `getProductsIntroductoryOfferEligibility` 方法。 4. 更新购买流程。 5. 更新付费墙编辑工具付费墙的展示方式。 6. 修改开发者自定义计时器的实现。 7. 更新付费墙编辑工具购买事件的处理逻辑。 8. 更新付费墙编辑工具自定义动作事件的处理逻辑。 9. 修改 `onProductSelected` 回调。 10. 从 `updateProfile` 方法中移除第三方集成参数。 11. 更新 Adjust、AirBridge、Amplitude、AppMetrica、Appsflyer、Branch、Facebook Ads、Firebase 与 Google Analytics、Mixpanel、OneSignal 及 Pushwoosh 的集成配置。 12. 更新 Observer 模式的实现。 ## 将 Adapty React Native SDK 升级至 3.3.x \{#upgrade-adapty-react-native-sdk-to-33x\} 在 3.3.1 版本之前,`react-native-adapty` SDK 是 Adapty 在您的应用中正常运行所必需的核心 SDK。`@adapty/react-native-ui` SDK 是可选的,仅在使用 Adapty 付费墙编辑工具时才需要安装。 从 3.3.1 版本起,`@adapty/react-native-ui` SDK 已被弃用,其功能已合并至 `react-native-adapty` SDK 中。请按照以下步骤升级至 3.3.1 版本: 1. 将 `react-native-adapty` 包更新至 3.3.1 版本。 2. 从项目依赖中移除 `@adapty/react-native-ui` 包。 3. 同步项目依赖以应用更改。 ## 模型变更 \{#changes-in-models\} ### 新增模型 \{#new-models\} 1. [AdaptySubscriptionOffer](https://react-native.adapty.io/interfaces/adaptysubscriptionoffer): ```typescript showLineNumbers export interface AdaptySubscriptionOffer { readonly identifier: AdaptySubscriptionOfferId; phases: AdaptyDiscountPhase[]; android?: { offerTags?: string[]; }; } ``` 2. [AdaptySubscriptionOfferId](https://react-native.adapty.io/types/adaptysubscriptionofferid): ```typescript showLineNumbers export type AdaptySubscriptionOfferId = | { id?: string; type: 'introductory'; } | { id: string; type: 'promotional' | 'win_back'; }; ``` ### 变更的模型 \{#changed-models\} 1. [AdaptyPaywallProduct](https://react-native.adapty.io/interfaces/adaptypaywallproduct): - 将 `subscriptionDetails` 属性重命名为 `subscription`。 <p> </p> ```diff showLineNumbers - subscriptionDetails?: AdaptySubscriptionDetails; + subscription?: AdaptySubscriptionDetails; ``` 2. [AdaptySubscriptionDetails](https://react-native.adapty.io/interfaces/adaptysubscriptiondetails): - `promotionalOffer` 已移除。现在促销活动仅在可用时通过 `offer` 属性提供。此时 `offer?.identifier?.type` 的值为 `'promotional'`。 - `introductoryOfferEligibility` 已移除(优惠仅在用户符合条件时才会返回)。 - `offerId` 已移除。优惠 ID 现在存储在 `AdaptySubscriptionOffer.identifier` 中。 - `offerTags` 已移至 `AdaptySubscriptionOffer.android`。 <p> </p> 3. [AdaptyDiscountPhase](https://react-native.adapty.io/interfaces/adaptydiscountphase): - `AdaptyDiscountPhase` 模型中移除了 `identifier` 字段。优惠标识符现在存储在 `AdaptySubscriptionOffer.identifier` 中。 <p> </p> ```diff showLineNumbers - ios?: { - readonly identifier?: string; - }; ``` ### 已移除的模型 \{#remove-models\} 1. `AttributionSource`: - 在之前使用 `AttributionSource` 的地方,现在直接使用字符串。 2. `OfferEligibility`: - 该模型已被移除,因为它不再需要。现在,仅当用户符合资格时才返回优惠。 ## 移除 `getProductsIntroductoryOfferEligibility` 方法 \{#remove-getproductsintroductoryoffereligibility-method\} 在 Adapty SDK 3.3.1 之前,产品对象始终包含优惠,即使用户不符合资格也是如此。这需要您在使用优惠之前手动检查资格。 从 3.3.1 版本起,产品对象仅在用户符合资格时才包含优惠。这简化了流程,因为只要存在优惠,即可认为用户符合资格。 ## 更新购买流程 \{#update-making-purchase\} 在早期版本中,已取消和待处理的购买会被视为错误,分别返回代码 `2: 'paymentCancelled'` 和 `25: 'pendingPurchase'`。 从版本 3.3.1 开始,已取消和待处理的购买现在被视为成功结果,应按相应方式处理: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` ## 更新付费墙编辑工具付费墙的展示方式 \{#update-paywall-builder-paywall-presentation\} 有关更新后的示例,请参阅[在 React Native 中展示新版付费墙编辑工具付费墙](react-native-present-paywalls)文档。 ```diff showLineNumbers - import { createPaywallView } from '@adapty/react-native-ui'; + import { createPaywallView } from 'react-native-adapty/dist/ui'; const view = await createPaywallView(paywall); view.registerEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` ## 更新开发者自定义计时器的实现方式 \{#update-developer-defined-timer-implementation\} 将 `timerInfo` 参数重命名为 `customTimers`: ```diff showLineNumbers - let timerInfo = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } + let customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } //and then you can pass it to createPaywallView as follows: - view = await createPaywallView(paywall, { timerInfo }) + view = await createPaywallView(paywall, { customTimers }) ``` ## 修改付费墙编辑工具的购买事件 \{#modify-paywall-builder-purchase-events\} 之前: - 取消购买会触发 `onPurchaseCancelled` 回调。 - 待处理的购买会返回错误码 `25: 'pendingPurchase'`。 现在: - 两者均由 `onPurchaseCompleted` 回调处理。 #### 迁移步骤: \{#steps-to-migrate\} 1. 移除 `onPurchaseCancelled` 回调。 2. 移除对错误码 `25: 'pendingPurchase'` 的处理逻辑。 3. 更新 `onPurchaseCompleted` 回调: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.registerEventHandlers({ // ... other optional callbacks onPurchaseCompleted(purchaseResult, product) { switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; // highlight-start case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; // highlight-end } // highlight-start return purchaseResult.type !== 'user_cancelled'; // highlight-end }, }); ``` ## 修改付费墙编辑工具自定义操作事件 \{#modify-paywall-builder-custom-action-events\} 已移除的回调: - `onAction` - `onCustomEvent` 新增的回调: - 新增 `onCustomAction(actionId)` 回调,用于处理自定义操作。 ## 修改 `onProductSelected` 回调 \{#modify-onproductselected-callback\} 之前,`onProductSelected` 需要传入 `product` 对象。现在改为接受字符串类型的 `productId`。 ## 从 `updateProfile` 方法中移除第三方集成参数 \{#remove-third-party-integration-parameters-from-updateprofile-method\} 第三方集成标识符现在通过 `setIntegrationIdentifier` 方法进行设置。`updateProfile` 方法不再接受这些参数。 ## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\} 为确保集成能够与 Adapty React Native SDK 3.3.1 及更高版本正常工作,请按照以下各节的说明更新您的 SDK 配置。 此外,如果您之前使用 `AttributionSource` 获取归因标识符,请将代码修改为以字符串形式提供所需标识符。 ### Adjust 按照以下说明更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。 ```diff showLineNumbers import { Adjust, AdjustConfig } from "react-native-adjust"; import { adapty } from "react-native-adapty"; var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); + Adjust.getAdid((adid) => { + if (adid) + adapty.setIntegrationIdentifier("adjust_device_id", adid); + }); ``` ### AirBridge \{#airbridge\} 按如下方式更新您的移动应用代码。完整代码示例请参阅 [AirBridge 集成的 SDK 配置](airbridge#connect-your-app-to-airbridge)。 ```diff showLineNumbers import Airbridge from 'airbridge-react-native-sdk'; import { adapty } from 'react-native-adapty'; try { const deviceId = await Airbridge.state.deviceUUID(); - await adapty.updateProfile({ - airbridgeDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` ### Amplitude \{#amplitude\} 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。 ```diff showLineNumbers import { adapty } from 'react-native-adapty'; try { - await adapty.updateProfile({ - amplitudeDeviceId: deviceId, - amplitudeUserId: userId, - }); + await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); + await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` ### AppMetrica 按照以下示例更新您的移动应用代码。完整代码示例请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。 ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import AppMetrica, { DEVICE_ID_KEY, StartupParams, StartupParamsReason } from '@appmetrica/react-native-analytics'; // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceId = params?.deviceId if (deviceId) { try { - await adapty.updateProfile({ - appmetricaProfileId: 'YOUR_ADAPTY_CUSTOMER_USER_ID', - appmetricaDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); + await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_KEY]) ``` ### AppsFlyer 按照以下示例更新你的移动应用代码。完整代码示例请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。 ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import appsFlyer from 'react-native-appsflyer'; appsFlyer.onInstallConversionData(installData => { try { - const networkUserId = appsFlyer.getAppsFlyerUID(); - adapty.updateAttribution(installData, AttributionSource.AppsFlyer, networkUserId); + const uid = appsFlyer.getAppsFlyerUID(); + adapty.setIntegrationIdentifier("appsflyer_id", uid); + adapty.updateAttribution(installData, "appsflyer"); } catch (error) { // handle the error } }); // ... appsFlyer.initSdk(/*...*/); ``` ### Branch \{#branch\} 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。 ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { - adapty.updateAttribution(params, AttributionSource.Branch); + adapty.updateAttribution(params, "branch"); }, }); ``` ### Facebook Ads 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Facebook Ads 集成的 SDK 配置](facebook-ads#connect-your-app-to-facebook-ads)。 ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { AppEventsLogger } from 'react-native-fbsdk-next'; try { const anonymousId = await AppEventsLogger.getAnonymousID(); - await adapty.updateProfile({ - facebookAnonymousId: anonymousId, - }); + await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` ### Firebase 与 Google Analytics \{#firebase-and-google-analytics\} 按以下方式更新您的移动应用代码。完整代码示例请参阅 [Firebase 与 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。 ```diff showLineNumbers import analytics from '@react-native-firebase/analytics'; import { adapty } from 'react-native-adapty'; try { const appInstanceId = await analytics().getAppInstanceId(); - await adapty.updateProfile({ - firebaseAppInstanceId: appInstanceId, - }); + await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } catch (error) { // handle `AdaptyError` } ``` ### Mixpanel \{#mixpanel\} 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。 ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { Mixpanel } from 'mixpanel-react-native'; // ... try { - await adapty.updateProfile({ - mixpanelUserId: mixpanelUserId, - }); + await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // handle `AdaptyError` } ``` ### OneSignal 按照以下示例更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。 <Tabs groupId="current-os" queryString> <TabItem value="v5+" label="OneSignal SDK v5+(当前版本)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { - adapty.updateProfile({ - oneSignalSubscriptionId: subscriptionId, - }); + adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. up to 4.x (legacy)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; - adapty.updateProfile({ - oneSignalPlayerId: playerId, - }); + adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> ### Pushwoosh \{#pushwoosh\} 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。 ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import Pushwoosh from 'pushwoosh-react-native-plugin'; // ... try { - await adapty.updateProfile({ - pushwooshHWID: hwid, - }); + await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` ## 更新 Observer 模式实现 \{#update-observer-mode-implementation\} 更新付费墙与交易的关联方式。以前,你需要使用 `setVariationId` 方法来指定 `variationId`。现在,你可以在使用新的 `reportTransaction` 方法记录交易时直接传入 `variationId`。请参阅[在 Observer 模式下将付费墙与购买交易关联](report-transactions-observer-mode-react-native)中的完整代码示例。 :::warning 不要忘记使用 `reportTransaction` 方法记录交易。跳过此步骤意味着 Adapty 将无法识别该交易,不会授予访问等级,不会将其纳入分析统计,也不会将其发送至集成渠道。此步骤至关重要! ::: :::note 请注意,`reportTransaction` 方法的参数顺序与 `setVariationId` 方法的参数顺序不同。 ::: ```diff showLineNumbers const variationId = paywall.variationId; try { - await adapty.setVariationId(variationId, transactionId); + await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native-sdk-v3 --- --- title: "将 Adapty React Native SDK 迁移至 v3.0" description: "迁移至 Adapty React Native SDK v3.0,享受更优性能与全新变现功能。" --- Adapty SDK v3.0 带来了重大更新,提升了性能,并新增了多项变现功能。本指南将帮助你将现有的 React Native 集成从之前的版本迁移至 v3.0。 ## 主要变更 \{#major-changes\} ### 付费墙编辑工具更新 \{#paywall-builder-updates\} Adapty SDK v3.0 引入了全新的付费墙编辑工具,支持更灵活的布局和更丰富的自定义能力。如果你目前使用的是旧版付费墙编辑工具,需要按照以下步骤进行迁移。 ### 方法与类型重命名 \{#method-and-type-renames\} v3.0 对多个方法和类型进行了重命名,以提升 API 的一致性和可读性。 ## 迁移步骤 \{#migration-steps\} ### 第一步:更新依赖 \{#step-1-update-dependencies\} 将 `react-native-adapty` 更新至最新的 v3.x 版本: ```bash npm install react-native-adapty@3 # 或 yarn add react-native-adapty@3 ``` ### 第二步:更新 SDK 初始化 \{#step-2-update-sdk-initialization\} v3.0 的初始化方式与之前版本保持兼容,无需修改初始化代码。 ### 第三步:迁移付费墙编辑工具 \{#step-3-migrate-paywall-builder\} 如果你使用的是旧版付费墙编辑工具(Legacy Paywall Builder),需要将组件迁移至新版付费墙编辑工具。 <Tabs> <TabItem value="new" label="新版付费墙编辑工具"> ```typescript ``` </TabItem> <TabItem value="legacy" label="旧版付费墙编辑工具"> ```typescript ``` </TabItem> </Tabs> ### 第四步:更新已重命名的方法 \{#step-4-update-renamed-methods\} 请参阅下方的重命名对照表,更新代码中所有受影响的方法和类型调用。 ## 重命名对照表 \{#renamed-methods-and-types\} | 旧名称 | 新名称 | |--------|--------| | `getPaywalls` | `getPaywall` | | `AdaptyPaywallController` | `AdaptyPaywallViewController` | | `makePurchase` | `purchase` | ## 需要帮助? \{#need-help\} 如有任何问题,欢迎通过 [Adapty 支持](https://adapty.io/contact) 联系我们,或在 [GitHub](https://github.com/adaptyteam/AdaptySDK-React-Native) 提交 Issue。 Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder)支持——这是一款全新升级的无代码、易上手的付费墙创建工具。凭借极高的灵活性和丰富的设计能力,你的付费墙将变得更加高效且盈利。 ## 升级至 3.0.1 版本 \{#upgrade-to-version-301\} 1. 按常规方式升级至 3.0.1 版本。 2. 替换备用付费墙文件: 1. 从 Adapty 看板[下载最新版本](fallback-paywalls)。 2. 将文件存储在用户设备上,并按照[此处](react-native-use-fallback-paywalls)的说明将其传递给 `.setFallbackPaywalls` 方法。 --- # End of Documentation _Generated on: 2026-07-24T13:01:53.419Z_ _Successfully processed: 45/45 files_ # TUTORIAL - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.421Z Total files: 277 --- # File: is-adapty-right-for-me --- --- title: "Adapty 适合我吗?" description: "了解 Adapty 如何适配您的使用场景。无论您是在发布新应用、优化收入,还是从其他工具迁移——这里是您的起点。" --- Adapty 是一个面向移动应用的应用内购买平台。它涵盖订阅、一次性购买和消耗品——从购买处理和收据验证到分析、A/B 测试和集成,一应俱全。 以下是 Adapty 在不同场景下的应用方式。 ## 我正在推出一款含内购功能的新应用 \{#im-launching-a-new-app-with-in-app-purchases\} 无论你打算销售订阅、一次性购买还是消耗型商品,Adapty 都能提供全方位支持: - **7 个平台的 SDK**:iOS、Android、React Native、Flutter、Unity、Kotlin Multiplatform 和 Capacitor。 - **购买处理**:订阅续费与重试逻辑、一次性购买、消耗型商品及收据验证——全部自动处理。 - **无代码付费墙编辑工具**:无需编写 UI 代码,即可设计并上线付费墙。 - **从第一天起即可查看分析数据**:首批用户到来时,即可追踪收入、试用、转化等数据。 准备好开始了吗?请参阅[快速入门指南](quickstart)。 ## 我想要 A/B 测试、分析和集成功能 \{#i-want-ab-tests-analytics-and-integrations\} Adapty 帮助你优化现有的功能: - **A/B 测试**:测试不同的价格、付费墙设计、试用时长和促销活动,找出转化效果最佳的方案。使用 [AI Growth Advisor](autopilot) 获取针对你的应用量身定制的 A/B 测试建议,这些建议基于来自 20,000 款订阅应用的数据。 - **数据图表**:追踪 MRR、LTV、流失率、留存率及其他数十项指标。 - **目标受众细分**:针对特定用户群体展示定制化的付费墙和优惠。 - **远程付费墙配置**:无需发布新版本即可迭代优化付费墙。 - **第三方集成**:将购买事件发送至 Amplitude、AppsFlyer、Adjust、Mixpanel 及你团队已在使用的其他工具。 探索 [A/B 测试](ab-tests)、[分析](analytics)、[分析服务集成](analytics-integration) 或 [归因服务集成](attribution-integration)。 ## 我想用 LLM 实现应用内购买 \{#i-want-to-implement-in-app-purchases-with-an-llm\} Adapty 的文档已针对 AI 编程助手(如 Cursor、Claude、ChatGPT 等)进行了优化。每个页面都提供纯 Markdown 格式,并为各平台提供分步骤的 LLM 辅助实现指南: - **可直接复制粘贴的指南**:将指南发送给你的 LLM,让它引导你完成每个实现阶段。 - **Markdown 访问**:在任意文档 URL 后添加 `.md`,或点击 **Copy for LLM** 获取纯文本版本。 - **Context7 MCP 支持**:将 Adapty 文档直接连接到你的 LLM 驱动的 IDE。 选择你的平台,立即开始:[借助 AI 辅助集成 Adapty](adapty-cursor)。 ## 我想运行并优化 Apple Ads 广告系列 \{#i-want-to-run-and-optimize-apple-ads-campaigns\} 如果你在投放 Apple Search Ads,Adapty Ads Manager 可以直接将广告系列效果与收入数据挂钩,无需 MMP: - **实时效果数据**:追踪广告系列、广告组和关键词的表现。 - **端到端收入追踪**:完整还原从搜索到安装、试用、订阅再到 LTV 的全链路。 - **AI 预测与建议**:预测投资回报并获取扩量建议。 - **AI 智能助手**:用自然语言提问,获取全漏斗分析结果与优化建议。 - **基于规则的自动化**:稳定维持您的 CPA 和 ROAS 目标。 立即开始使用 [Adapty Ads Manager](adapty-ads-manager)。 ## 我想追踪用户来源 \{#i-want-to-track-where-my-users-come-from\} Adapty Attribution 是一款内置归因解决方案,可将广告支出与应用安装量及订阅收入关联起来: - **统一营销看板**:在一处查看所有渠道的 ROAS、安装量和收入数据。 - **内置归因**:无需依赖外部 MMP,直接将广告活动与安装量和收入关联起来。 - **追踪链接**:在 Adapty 中生成链接并添加到广告活动中,实现精准归因。 - **延迟深度链接**:即使用户点击时尚未安装应用,安装后也能引导至正确的内容页面。 - **同期群分析**:分析获客效果和用户随时间的行为变化。 了解更多关于 [Adapty 归因](adapty-user-acquisition) 的信息。 ## 我想通过邮件转化试用用户并挽回流失的订阅者 \{#i-want-to-convert-trial-users-and-recover-churned-subscribers-via-email\} Adapty Mail 将 Adapty 用户数据转化为 AI 生成的邮件营销活动,精准触达试用用户、流失的订阅者以及其他生命周期节点: - **AI 自动生成营销活动**:Adapty 根据你的品牌资料自动生成每个营销活动的文案和设计。 - **生命周期触发**:根据关键生命周期事件自动发送营销活动。 - **Web 付费墙结账**:为每位收件人生成专属结账链接,购买行为归因到对应的推广邮件。 - **使用自有域名发送**:所有邮件均通过你验证的域名发出,无需单独搭建邮件平台。 了解更多关于 [Adapty Mail](adapty-mail) 的信息。 ## 我想快速迭代而无需发布新版本 \{#i-want-to-iterate-fast-without-app-releases\} Adapty 集成完成后,绝大多数日常工作都可以在看板中完成,无需发布新版本: - **流程**:在可视化编辑器中设计付费墙和用户引导,即时发布更改。 - **从看板发起 A/B 测试**:无需改动代码,即可启动实验、调整定价和更换优惠。 - **看板数据分析**:实时监控收入、流失率、试用情况和转化率。 - **Slack 和邮件报告**:自动推送团队最关注的数据指标。 探索 [Flows](adapty-flow-builder) 或查看 [Analytics](charts)。 ## 我在 Web 端销售,需要一个移动应用 如果您的用户已通过网站付费,而您正在添加移动应用,Adapty 可跨平台同步购买: - **Stripe 和 Paddle 集成**:自动将 Web 购买同步至 Adapty。 - **Web 到移动同步**:在 Web 上付费的用户可在应用中获得访问权限,反之亦然。 - **统一跨平台分析**:在一个仪表板中查看 Web 和移动收入。 设置 [Stripe 集成](stripe)、[Paddle 集成](paddle),或了解如何[同步 Web 和移动订阅者](sync-subscribers-from-web)。 ## 我正在从其他工具迁移 \{#im-migrating-from-another-tool\} Adapty 让您轻松从其他订阅平台迁移: - **迁移指南**:从其他订阅平台迁移的分步操作说明。 - **观察者模式**:通过[观察者模式](observer-vs-full-mode)保留现有的计费代码并逐步接入 Adapty——先从分析和 A/B 测试开始,准备好后再进一步扩展。 - **历史数据导入**:将您现有的交易记录导入 Adapty,确保分析数据保持完整。 了解[迁移至 Adapty](migrate-to-adapty-from-another-solutions) 和[导入历史数据](importing-historical-data-to-adapty)的相关内容。 --- 还在探索中?[快速入门指南](quickstart)是一个很好的起点。 --- # File: integrate-payments --- --- title: "与应用商店或支付平台集成" description: "将 Adapty 与 App Store、Google Play、自定义商店、Stripe 和 Paddle 集成。" --- 开始使用 Adapty,首先需要与用户购买产品的应用商店集成。Adapty 可连接各类应用商店和网络支付服务商,将您所有的应用内购买和分析数据汇聚在一处。 ## 与应用商店及网页支付集成 \{#integrate-with-stores-and-web-payments\} 在下方选择您的应用商店以查看详细的集成步骤: - [App Store](initial_ios) - [Google Play](initial-android) - 网页支付: - [Stripe](stripe) - [Paddle](paddle) - [其他应用商店](custom-store) ## 后续步骤 \{#next-steps\} 连接好您的应用商店或支付平台后,您可以继续[添加产品](quickstart-products)。 --- # File: quickstart-products --- --- title: "添加产品" description: "将应用内产品或订阅添加到 Adapty,并将其与 App Store、Google Play、Stripe、Paddle 或自定义商店的商品关联。" --- :::tip 想通过编程方式配置 Adapty?您可以使用 [开发者 CLI](developer-cli-quickstart) 完成此步骤。 ::: 在使用 Adapty 的核心功能之前,您需要添加每个您销售的产品,并将其与您支持的每个商店或支付平台关联。此设置使您可以向用户设备交付产品,并在后续分析中进行跟踪。 在 Adapty 中,您的应用所销售的任何内容都是一个**产品**。如果同一商品同时存在于 App Store、Google Play 或 Stripe 中,您可以将它们归组为 Adapty 中的单个产品。只需设置一次,即可在一处管理所有平台的产品。 让我们来添加您的第一个产品。 <Tabs groupId="products" queryString> <TabItem value="no-products" label="商店中尚无产品" default> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> <TabItem value="products-in-stores" label="商店中已有产品"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## 添加您的第一个产品 \{#add-your-first-product\} :::tip 本快速入门介绍创建产品所需的基本操作。如需了解更多详情,请参阅[创建产品](create-product)指南。 ::: 假设您想添加一个月度订阅作为产品。 1. 从 Adapty 主菜单进入 [Products](https://app.adapty.io/products)。 2. 点击右上角的 **Create product**。 <img src={require('./img/products-tab.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important **接下来的步骤取决于您是否已在 App Store 和/或 Google Play 中拥有产品:** ::: <Tabs groupId="products" queryString> <TabItem value="no-products" label="商店中尚无产品" default> :::important 在开始之前,请确保您已配置与 [App Store](initial_ios) 和/或 [Google Play](initial-android) 的集成。对于 App Store,请确保您已[添加 App Store Connect API 密钥](app-store-connection-configuration#step-6-add-app-store-connect-api-key),以便 Adapty 可以推送产品。 ::: 3. 选择 **Create a new product and push to stores**。 4. 填写产品详情: - **Product name**:仅在 Adapty 控制台中对您可见的名称。 - **Access Level**:决定购买后解锁哪些功能的唯一标识符。如果您应用中所有付费用户都能访问相同功能,您可以使用默认访问等级:`premium`。对于更复杂的设置,可以创建额外的[访问等级](access-level)。 - **Subscription duration**:从列表中选择订阅时长。 - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**:订阅时长。 - **Lifetime**:对于永久解锁应用高级功能的产品,使用永久授权期限。 - **Non-Subscriptions**:对于非订阅且没有时长的产品,使用非订阅类型。这些可以用于解锁额外功能、消耗型商品等。 - **Consumables**:消耗型商品可以多次购买,在应用使用过程中会被消耗。例如游戏内货币和额外道具。请注意,消耗型商品不影响访问等级。 - **Price (USD)**:以美元计的产品价格。该价格将作为基准价格,用于自动计算并设置各个国家的价格。您之后可以[为不同国家和地区自定义价格](edit-product#set-country-specific-prices)。 <img src={require('./img/create-product-push.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Save & Continue**,然后切换到 **App Store** 或 **Google Play** 标签页,填写该商店的产品详情。 <Tabs> <TabItem value="App Store" label="App Store" default> - **Product ID**:为产品创建一个永久性的唯一 ID。 - **Product group**:选择您在 App Store Connect 中创建的现有产品组,或点击 **Create new Product Group** 并设置其名称和 ID。Adapty 创建后,您可以从下拉菜单中选择它。 - **Screenshot**:上传一张应用内购买的截图,清晰展示所提供的商品或服务。此截图仅用于 App Store 审核,不会显示在 App Store 上。截图尺寸和格式要求请参阅[此处](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/)。 :::warning 如果这是您该应用的第一个产品,您必须在 App Store Connect 中手动提交审核。此后无需再次手动提交。审核完成后,Adapty 中的产品状态将自动更新。 ::: </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Base Product ID**:为产品创建一个永久性的唯一 ID。 - **Subscription**:选择您在 Google Play Console 中创建的现有订阅组,或点击 **Create new Product Group** 并设置其名称和 ID。Adapty 创建后,您可以从下拉菜单中选择它。 </TabItem> </Tabs> 6. 对于 iOS,通过从下拉菜单中选择**免费试用时长**来配置新用户优惠(免费试用)。在初始设置中,您可以添加免费试用的新用户优惠。主产品获得商店批准后,您可以通过关联商店控制台中的现有 ID 来[添加更多优惠](offers)(例如促销活动、赢回优惠)。 :::important 新用户优惠不会自动与 Google Play 同步。与 App Store 不同,Google Play 没有单独的"新用户优惠"类型——免费试用和折扣优惠都通过基础方案中的**优惠**来配置。[在 Google Play Console 中创建优惠并将其与 Adapty 产品关联](google-play-offers)。 ::: </TabItem> <TabItem value="products-in-stores" label="商店中已有产品"> 3. 选择 **Connect an existing store product**。 4. 填写产品详情: - **Product name**:仅在 Adapty 控制台中对您可见的名称。 - **Access level ID**:决定购买后解锁哪些功能的唯一标识符。如果您应用中所有付费用户都能访问相同功能,您可以使用默认访问等级:`premium`。对于更复杂的设置,可以创建额外的[访问等级](access-level)。 - **Subscription duration**:从列表中选择订阅时长。 - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**:订阅时长。 - **Lifetime**:对于永久解锁应用高级功能的产品,使用永久授权期限。 - **Non-Subscriptions**:对于非订阅且没有时长的产品,使用非订阅类型。这些可以用于解锁额外功能、消耗型商品等。 - **Consumables**:消耗型商品可以多次购买,在应用使用过程中会被消耗。例如游戏内货币和额外道具。请注意,消耗型商品不影响访问等级。 - **Price (USD)**:以美元计的产品价格。如果您的产品已在商店中,此值不会影响其在商店中的实际价格,您可以从列表中选择任意值。之后,您可以直接在 Adapty 控制台中[为不同地区自定义价格](edit-product#set-country-specific-prices)。 <img src={require('./img/product-info.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <br /> 5. 添加商店详情。选择您的商店: <Tabs> <TabItem value="App Store" label="App Store" default> - **App Store Product ID**:用于在设备上访问您产品的唯一标识符。如果找不到它,请确保 ID 正确且属于正确的应用。 </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Google Play Product ID**:来自 Play Store 的产品标识符。从现有产品 ID 列表中选择。如果找不到,请确保 ID 正确且属于正确的应用。 - **Base plan ID**:在 Play Store 中定义产品基础计划的 ID。 - **Legacy fallback product**:备用产品专用于使用旧版 Adapty SDK(2.5 及以下版本)的应用。请按以下格式指定值:`<subscription_id>:<base_plan_id>`。 :::important 新用户优惠不会自动与 Google Play 同步。与 App Store 不同,Google Play 没有单独的"新用户优惠"类型——免费试用和折扣优惠都通过基础方案中的**优惠**来配置。[在 Google Play Console 中创建优惠并将其与 Adapty 产品关联](google-play-offers)。 ::: <details> <summary>点击此处了解在哪里找到 Google Play 产品 ID 和基础计划 ID。</summary> 1. 在您的 [Google Play Console](https://play.google.com/console/developers/android/app) 账户中,前往 **Monetize with Play > Products > Subscriptions**。 2. 打开要购买的**订阅**。 3. 您将在**订阅详情**部分看到产品 ID,在**基础计划和优惠**部分的 **ID 和时长**列中看到基础计划 ID。 <img src={require('./img/play-store-id.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Stripe" label="Stripe" default> - **Stripe Product ID**:来自 Stripe 的唯一产品标识符。 - **Stripe Price ID**:来自 Stripe 的与产品关联价格的唯一标识符。 <details> <summary>点击此处了解在哪里找到 Stripe 产品 ID 和价格 ID。</summary> 1. 前往 Stripe 中的[产品目录](https://dashboard.stripe.com/products?active=true)。 2. 打开您需要的产品。 3. 您将看到: - Stripe 产品 ID(格式如 `prod_...`)位于右上角。 - Stripe 价格 ID(格式如 `price_...`)位于**定价**部分的 **API ID** 列中。 <img src={require('./img/product-stripe.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Paddle" label="Paddle" default> - **Paddle Product ID**:来自 Paddle 的唯一产品标识符。 - **Paddle Price ID**:来自 Paddle 的与产品关联价格的唯一标识符。 <details> <summary>点击此处了解在哪里找到 Paddle 产品 ID 和价格 ID。</summary> 1. 前往 Paddle 中的[产品目录](https://vendors.paddle.com/products-v2)。 2. 打开您需要的产品。 3. 您将看到: - Paddle 产品 ID(格式如 `pro_...`)位于**附加详情**部分。 - Paddle 价格 ID(格式如 `pri_...`)位于**价格**部分的 **ID** 列中。 <img src={require('./img/paddle-product-price.webp').default} style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Custom" label="自定义商店" default> 您可以选择现有的自定义商店,或添加新的自定义商店并将产品与其关联。 请注意,Adapty 仅跟踪来自 App Store、Google Play 和 Stripe 的交易。对于自定义商店,您需要使用 Adapty 服务端 API 的[设置交易方法](api-adapty/operations/setTransaction)来提交交易。 </TabItem> </Tabs> 6. 如有需要,您可以为产品[创建优惠](create-offer)。要添加优惠,请点击 **Yes, add offers**。否则,点击 **No, thanks**。 您的产品将出现在产品列表中。 <img src={require('./img/created-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} 将产品添加到 Adapty 后,您可以继续[设置付费墙](quickstart-paywalls),这是开始销售产品的唯一方式。 --- # File: quickstart-paywalls --- --- title: "启用购买" description: "在 Adapty 中添加流程或付费墙以展示您的产品,然后将其绑定到版位。" --- :::info 在继续阅读本指南之前,请确保你已完成[商店集成](integrate-payments),并按照上一篇[添加产品指南](quickstart-products)创建了至少一个产品。 ::: 现在你已经有了产品,接下来需要一种方式将它们展示给用户。Adapty 为你提供了三种方案: - **流程编辑工具(推荐)**:用于完整购买流程的无代码可视化编辑工具。Adapty SDK 会原生渲染结果,无需编写任何 UI 代码。 - **手动付费墙**:你创建付费墙,为其关联产品,并在应用代码中自行渲染 UI。 - **Adapty 付费墙编辑工具(旧版)**:无代码付费墙编辑工具。 两种方式的最终步骤相同:将你构建的内容关联到一个[版位](placements)。版位是应用在运行时调用的入口,用于为合适的用户获取正确的内容。 <Tabs groupId="purchase-setup" queryString> <TabItem value="flow-builder" label="Use the Flow Builder" default> :::important Flow Builder 目前支持 iOS、Android、React Native、Flutter 和 Capacitor SDK v4 及更高版本。其他平台的支持即将推出。 ::: 流程由一个或多个直接嵌入产品的页面组成。你可以在[付费墙编辑工具](adapty-flow-builder)中设计它——无需编写代码。 Adapty SDK 在每个平台上以原生方式渲染流程。你的应用调用 `getFlow`,SDK 负责呈现页面、处理购买并上报事件。无需额外的 UI 代码,也无需单独维护付费墙。 ## 1. 构建流程 \{#1-build-the-flow\} 1. 前往 Adapty 主菜单中的 [**Flows**](https://app.adapty.io/flows)。 2. 点击 **Create flow** 并设计你的流程。 了解更多关于 [Adapty Flow Builder](adapty-flow-builder) 的信息。 以下模板指南将逐步介绍最常见的使用模式: <CustomDocCardList ids={['basic-paywall-screen', 'show-plans-bottom-sheet', 'paywall-with-tabs', 'paywall-features-per-product', 'onboarding-flow-tutorial']} /> 流程保存并发布后,继续将其关联到版位。 :::warning 别忘了发布流程!如果不发布,就无法将其添加到版位中。 ::: ### 2. 将流程添加到版位 \{#2-add-the-flow-to-a-placement\} 创建一个<InlineTooltip tooltip="版位">版位是应用中展示流程、付费墙、用户引导或 A/B 测试的特定位置。通过版位,你可以针对特定的[目标受众](audience)投放内容。了解更多关于[版位](placements)的信息。</InlineTooltip>,让应用在运行时能够请求该流程。 我们先从最基础的开始——用户引导版位。之后,你可以在用户旅程中添加更多[有意义的版位](choose-meaningful-placements)。 1. 前往 Adapty 主菜单中的 [**Placements**](https://app.adapty.io/placements),切换到 **Flows** 标签页。 2. 点击 **Create placement**。 3. 输入 **Placement name**(例如 `main` 或 `onboarding`)。这是 Adapty 看板中的内部标识符。 4. 输入 **Placement ID**。你将在 Adapty SDK 中使用此 ID 来加载对应版位的流程。 5. 点击 **Run flow**,选择你刚构建的流程。 6. 点击 **Save & publish**。 在您的应用代码中,只需硬编码版位 ID。其余所有内容——运行哪个流程、销售哪些产品、界面如何展示——均在 Adapty 看板中配置,随时可以更改,无需更新应用。 :::tip Adapty 支持向不同用户群体展示不同流程,并分析其表现。了解更多关于[目标受众](audience)和 [A/B 测试](ab-tests)的内容。 ::: </TabItem> <TabItem value="manual-paywall" label="Implement paywall manually"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/e4o7Z2tUGL8?si=ipwbW3VVN0fIg0R0" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 付费墙是一个远程配置的容器,包含一个或多个产品。Adapty 提供产品列表和可选的[远程配置](customize-paywall-with-remote-config) JSON 数据——你的应用代码读取这些数据并渲染 UI。 :::tip 想通过编程方式配置 Adapty?你可以使用 [Developer CLI](developer-cli-quickstart) 完成这一步骤。 ::: ### 1. 创建付费墙 \{#1-create-a-paywall\} 1. 前往 Adapty 主菜单中的 [**Paywalls**](https://app.adapty.io/paywalls)。 2. 点击 **Create paywall**。 3. 输入 **Paywall name**,这是付费墙在 Adapty 看板中的内部标识符。 4. 点击 **Add product**,选择要在付费墙上展示的产品。 5. (可选)打开 **Remote config** 标签页,添加应用所需的 JSON 数据(标题、文案、功能开关等)。详情请参阅[使用远程配置设计付费墙](customize-paywall-with-remote-config)。 6. 点击 **Create as a draft**,准备好后再发布。 <img src="/assets/shared/img/quickstart-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 您将在应用代码中渲染此付费墙。<InlineTooltip tooltip="手动实现付费墙">请参阅您所用平台的指南:[iOS](ios-implement-paywalls-manually)、[Android](android-implement-paywalls-manually)、[React Native](react-native-implement-paywalls-manually)、[Flutter](flutter-implement-paywalls-manually)、[Unity](unity-implement-paywalls-manually)。</InlineTooltip> ### 2. 将付费墙添加到版位 创建一个 <InlineTooltip tooltip="版位">版位是应用中展示流程、付费墙、用户引导或 A/B 测试的特定位置。版位让你能够针对特定[目标受众](audience)投放内容。了解更多关于[版位](placements)的信息。</InlineTooltip>,以便应用在运行时请求付费墙。 我们从最基础的版位开始——用户引导版位。之后,你可以在用户旅程中添加更多[有意义的版位](choose-meaningful-placements)。 1. 在 Adapty 主菜单中进入 [**Placements**](https://app.adapty.io/placements),切换到 **Paywalls** 标签页。 2. 点击 **Create placement**。 3. 填写 **Placement name**(例如 `main` 或 `onboarding`)。这是在 Adapty 看板中使用的内部标识符。 4. 填写 **Placement ID**。在 Adapty SDK 中加载该版位的付费墙时会用到此 ID。 5. 点击 **Run paywall**,选择刚刚创建的付费墙。 6. 点击 **Save & publish**。 <img src="/assets/shared/img/add-placement.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在你的应用代码中,你只需硬编码版位 ID。其他一切——运行哪个付费墙、销售哪些产品、远程配置——都在 Adapty 看板中配置,随时可以修改,无需更新应用。 :::tip Adapty 支持向不同用户群体展示不同的付费墙,并分析其效果。了解更多关于[目标受众](audience)和 [A/B 测试](ab-tests)的内容。 ::: </TabItem> <TabItem value="paywall-builder" label="Adapty Paywall Builder (Legacy)"> 使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙是一个无需编写代码的页面,产品直接嵌入其中。Adapty SDK 会原生渲染它,无需编写任何 UI 代码。 :::warning 付费墙编辑工具目前仍可正常使用,但 Adapty 已停止为其添加新功能或发布更新。新项目请改用 [Flow Builder](adapty-flow-builder)。 ::: ### 1. 创建付费墙 \{#build-the-paywall\} 1. 进入 Adapty 主菜单中的 [**Paywalls**](https://app.adapty.io/paywalls)。 2. 点击 **Create paywall**。 3. 填写 **Paywall name**,这是付费墙在 Adapty 看板中的内部标识符。 4. 点击 **Add product**,选择要在付费墙上展示的产品。 5. 打开 **Builder & Generator** 标签页,从模板创建付费墙,或使用 AI 生成。 6. 开启 **Show on device** 开关,让 SDK 能够渲染该付费墙。 ### 2. 将付费墙添加到版位 \{#add-the-paywall-to-a-placement\} 创建一个 <InlineTooltip tooltip="版位">版位是应用中展示流程、付费墙、用户引导或 A/B 测试的特定位置。版位让你可以针对特定[目标受众](audience)投放内容。了解更多关于[版位](placements)的信息。</InlineTooltip>,让你的应用能够在运行时请求付费墙。 1. 在 Adapty 主菜单中进入 [**Placements**](https://app.adapty.io/placements),切换到 **Paywalls** 标签页。 2. 点击 **Create placement**。 3. 输入 **Placement name**(如 `main` 或 `onboarding`)。这是在 Adapty 看板中使用的内部标识符。 4. 输入 **Placement ID**。你将在 Adapty SDK 中使用此 ID 来加载该版位的付费墙。 5. 点击 **Run paywall**,选择你创建的付费墙。 6. 点击 **Save & publish**。 在你的应用代码中,只需硬编码版位 ID。其他所有内容——运行哪个付费墙、销售哪些产品、界面如何呈现——都在 Adapty 看板中配置,随时可以修改,无需更新应用。 </TabItem> </Tabs> ## 后续步骤 \{#next-steps\} 现在你已经准备好了可供 SDK 分发的内容。接下来,请将 [Adapty SDK 集成](quickstart-sdk)到你的应用中,并开始获取版位。 --- # File: quickstart-sdk --- --- title: "在应用代码中集成 Adapty SDK" description: "将 Adapty 与 App Store、Google Play、自定义商店、Stripe 和 Paddle 集成。" --- 将 Adapty SDK 集成到您的应用中,以便: - 开箱即用地处理购买、凭据验证和订阅管理 - 无需更新应用即可创建和测试付费墙 - 零配置获取详细的购买分析数据——包含同期群、LTV、流失率和漏斗分析 - 在应用会话和设备之间始终保持用户订阅状态最新 - 仅需一行代码即可将您的应用与营销归因和分析服务集成 ## 工作原理 \{#how-does-it-work\} 对于 Adapty SDK 的基本实现,您只需关注以下三件事: 1. 安装并初始化 SDK。 2. 将应用内购买的处理委托给 Adapty。 3. 在用户画像中监控订阅状态。Adapty 负责判断订阅状态、类型和到期时间——SDK 只需消费这些信息。 具体顺序和细节因应用而异,但基本上就是这些。 ## 开始使用 \{#get-started\} 选择您的平台,立即开始: **iOS** - **[SDK 快速入门](ios-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples)** **Android** - **[SDK 快速入门](android-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app)** **React Native** - **[SDK 快速入门](react-native-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/)** **Flutter** - **[SDK 快速入门](flutter-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example)** **Unity** - **[SDK 快速入门](unity-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets)** **Capacitor** - **[SDK 快速入门](capacitor-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples)** **Kotlin Multiplatform**: - **[SDK 快速入门](kmp-sdk-overview)** - **[示例应用](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example)** ## 下一步 \{#next-steps\} 完成 Adapty SDK 在应用代码中的配置后,您可以继续[测试实现](quickstart-test)。 --- # File: quickstart-test --- --- title: "测试你的 Adapty 集成" description: "通过测试 SDK 激活、付费墙获取以及 App Store、Google Play、Stripe 和 Paddle 上的应用内购买,快速验证你的 Adapty 集成是否正常工作。" --- 一切准备就绪!现在请确认你的集成按预期工作,并且你可以在 Adapty 控制台中看到你的购买记录。 进行测试购买是验证集成端到端是否正常工作的最佳方式。从应用内购买开始,然后验证你的结果。 ## 1. 测试应用内购买 \{#1-test-in-app-purchases\} 根据你的应用商店或支付平台,遵循相应的指南。 ### App store \{#app-store\} 我们建议使用测试账户(沙盒 Apple ID)并在真实设备上进行测试。要了解所有测试步骤的详细信息,请前往 [App Store 沙盒测试](test-purchases-in-sandbox)的详细文章。 :::warning 在真实设备上测试以获得最可靠的结果。你可以选择使用模拟器进行测试,但我们不建议这样做,因为可靠性较低。 ::: ### Google Play Store \{#google-play-store\} 创建一个测试用户并在真实设备上测试你的应用。要了解所有测试步骤的详细信息,请前往 [Google Play Store 测试](testing-on-android)的详细文章。 :::note Google [建议](https://support.google.com/googleplay/android-developer/answer/14316361)使用真实设备进行测试。如果你决定使用模拟器,请确保它已安装 Google Play,以确保你的应用正常运行。 ::: ### Stripe \{#stripe\} 在 Stripe 上测试购买需要使用 Stripe 测试模式的 API 密钥将 Stripe 连接到 Adapty。你从 Stripe 测试模式进行的交易将在 Adapty 中被视为沙盒交易。 要了解所有连接步骤的详细信息,请前往 [Stripe 集成文章](stripe#6-test-your-integration)。 ### Paddle \{#paddle\} 在 Paddle 上测试购买需要使用 Paddle 测试环境的 API 密钥将 Paddle 连接到 Adapty。你从 Paddle 测试环境进行的交易将在 Adapty 中被视为测试交易。 要了解所有连接步骤的详细信息,请前往 [Paddle 集成文章](paddle#4-test-your-integration)。 ## 2. 验证测试购买 \{#2-validate-test-purchases\} 完成测试购买后,在 Adapty 控制台的 [**Event Feed**](https://app.adapty.io/event-feed) 中查找对应的交易记录。如果购买未出现在 **Event Feed** 中,说明 Adapty 未能追踪到该购买。 在[验证测试购买](validate-test-purchases)的详细指南中了解更多信息。 <img src="/assets/shared/img/test-event-feed.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 后续步骤 \{#next-steps\} 恭喜你成功完成 Adapty 的用户引导!现在你已准备好提升应用内购买收益。 为正式发布做好准备: <Button id="release-checklist"> 发布检查清单 </Button> 或者,你也可以继续进行以下操作: - **[A/B 测试](ab-tests)**:尝试不同的价格、订阅时长、试用期和视觉元素,以找出最有效的组合。 - **[分析](how-adapty-analytics-works)**:深入了解详细的变现数据图表,以理解用户行为并优化收益表现。 - **集成**:Adapty 将[订阅事件](events)发送到第三方分析和归因工具,例如 [Amplitude](amplitude)、[AppsFlyer](appsflyer)、[Adjust](adjust)、[Branch](branch)、[Mixpanel](mixpanel)、[Facebook Ads](facebook-ads)、[AppMetrica](appmetrica) 以及自定义 [Webhook](webhook)。 :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: --- # File: release-checklist --- --- title: "发布检查清单" description: "遵循 Adapty 的发布检查清单,确保应用更新过程顺畅无误。" --- 我们非常高兴您决定使用 Adapty!希望集成过程一切顺利。本指南将引导您完成确保应用准备好在商店发布所需的各个步骤,让您确信变现流程运行正常。 ## 起飞前必备事项 \{#pre-flight-essentials\} 开始验证前您需要准备: - 一台配置了沙盒账号的真实设备 - 访问 Adapty 看板的权限 - 访问 App Store Connect / Google Play Console 的权限 :::note 虽然沙盒购买可以在模拟器上运行,但要完整测试所有流程(包括支付对话框和生物识别提示),仍需要真实设备。 ::: <Button id="test-purchases-in-sandbox"> App Store 测试指南 </Button> <Button id="testing-on-android"> Google Play 测试指南 </Button> ## 通用验证 \{#universal-validations\} - [ ] **商店连接**:确保已将 Adapty 连接至 App Store 和/或 Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **订阅事件推送**:确认服务器通知已配置: - [ ] [App Store 服务器通知](enable-app-store-server-notifications) - [ ] [实时开发者通知(RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **用户画像识别**:验证用户识别逻辑,确保购买记录关联到正确的用户画像: - [ ] [检查应用代码中的识别逻辑是否符合你的使用场景](ios-quickstart-identify) - [ ] [了解用于在用户画像之间共享付费访问权限的父级/继承逻辑](sharing-paid-access-between-user-accounts) - [ ] **优惠活动**:如果应用中包含 App Store 促销活动,请确保已将内购密钥[添加到主字段和 **App Store promotional offers** 部分](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers)。 - [ ] **数据收集**:确保符合隐私合规要求: - [ ] 如需遵守 GDPR、CCPA 等隐私法规,或应用面向儿童用户,请控制是否[启用 IDFA 和 IP 的收集与共享](sdk-installation-ios#data-policies)。 - [ ] 如果应用使用了 AppTrackingTransparency,请确保已[将授权状态发送给 Adapty](ios-deal-with-att)。 - [ ] **隐私标签**:[了解更多](apple-app-privacy) Adapty 收集的数据,以及审核时需要设置哪些标志。 ## 购买验证 \{#purchase-validations\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 在正式上线之前,请确保应用内购买功能正常运行,且付费墙已准备好通过应用商店审核。 验证应用内购买的方式取决于你的具体实现方案: - 你展示的是通过 Adapty 付费墙编辑工具创建的付费墙 - 你实现了自定义付费墙,并在其中使用 `makePurchase` 方法处理购买 - 你以观察者模式使用 Adapty(无论是配合付费墙编辑工具还是自定义付费墙) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **目标**:Adapty 渲染付费墙,用户可以购买产品、解锁访问权限,并且恢复购买流程正常运行。 - [ ] 你的应用从即将上线的同一[版位展示付费墙](ios-present-paywalls)。 - [ ] 付费墙能正常显示在屏幕上。如果加载时间过长(例如你或用户网络不稳定),请考虑[调整获取策略](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)。 - [ ] 付费墙显示的是预期的实验变体(如适用,包含目标受众/语言区域)。如有需要,可[调整目标受众优先级](change-audience-priority)。 - [ ] 付费墙上能正常显示产品和价格。注意,Apple 的 API 在测试期间(尤其是配置了不同地区时)偶尔会返回不准确的价格,因此请优先测试购买流程的功能,而非价格准确性——Adapty 不会影响商店价格。 - [ ] 沙盒购买成功完成,并收到购买成功的回调。 - [ ] 访问权限已解锁且持续有效。确认[基于当前 Adapty 用户画像授予付费访问权限](ios-check-subscription-status#connect-profile-with-paywall-logic)。 - [ ] 购买完成后,Adapty 用户画像拥有有效的访问等级。 - [ ] 当用户画像包含对应访问等级时,付费功能解锁(而不仅仅依赖购买回调)。 - [ ] 恢复购买功能正常。重新安装应用或在新设备上安装时,自动恢复购买按照[在用户账号之间共享付费访问权限](sharing-paid-access-between-user-accounts)的设置运行。如果没有后端身份验证,购买将无论该设置如何都自动恢复。其他情况下,请确保用户在重新安装应用后能够恢复购买。 - [ ] 应用商店审核要求: - [ ] 付费墙上有**恢复购买**按钮。你可以在付费墙编辑工具中添加该按钮,点击后将自动处理购买恢复。 - [ ] 付费墙页面上可以访问使用条款和隐私政策,点击相关链接可在浏览器中打开。 </TabItem> <TabItem value="makepurchase" label="Custom paywall (makePurchase)" default> **目标**:您负责渲染 UI;Adapty 负责处理购买、用户画像更新和恢复购买。 - [ ] 产品 ID 未硬编码在应用代码中。你只需硬编码[版位](placements) ID。 - [ ] 你的应用从实际发布时使用的同一版位[获取产品](fetch-paywalls-and-products)。 - [ ] 产品列表加载成功。如果加载时间过长(例如你或用户网络不稳定),请考虑[调整获取策略](fetch-paywalls-and-products#fetch-paywall-information)。 - [ ] 获取到的产品与预期的实验变体(目标受众/语言区域,如适用)匹配。如有需要,可[调整目标受众优先级](change-audience-priority)。 - [ ] 产品和价格正确显示在付费墙上。请注意,Apple 的 API 在测试期间(尤其是使用不同地区配置时)偶尔会返回不准确的价格,因此请优先验证购买流程是否正常,而非价格是否准确——Adapty 不会影响商店价格。 - [ ] 使用 [makePurchase](making-purchases) 完成沙盒购买: - [ ] 购买成功的结果已正确处理。 - [ ] 待处理/失败/取消等情况已妥善处理。 - [ ] 如果你[使用了远程配置](present-remote-config-paywalls),其值已正确应用到付费墙。 - [ ] 付费墙展示时,调用了 [`logShowFlow`(iOS SDK v4+)/ `logShowPaywall` 方法](present-remote-config-paywalls#track-paywall-view-events)。 - [ ] 沙盒购买成功完成,并收到购买成功的回调。 - [ ] 访问权限已解锁且持续有效。确认[根据当前 Adapty 用户画像授予了付费访问权限](ios-check-subscription-status#connect-profile-with-paywall-logic)。 - [ ] 购买后,Adapty 用户画像中存在有效的访问等级。 - [ ] 当用户画像中包含对应访问等级时,付费功能才解锁(而不仅仅依赖购买回调)。 - [ ] 恢复购买功能正常。重新安装应用或在新设备上安装时,自动恢复购买功能按照[共享付费访问权限](sharing-paid-access-between-user-accounts)的设置运行。如果没有任何后端身份验证,无论该设置如何,购买都会自动恢复。其他情况下,请确保用户在重装应用后能够恢复其购买记录。 - [ ] 商店审核要求: - [ ] **Restore purchases** 按钮可访问,且[恢复购买功能](restore-purchase)正常工作。 - [ ] 付费墙页面中可访问"使用条款"和"隐私政策",点击链接后能在浏览器中打开。 </TabItem> <TabItem value="observer" label="观察者模式"> **目标**:您自行处理购买、用户画像更新和恢复操作;Adapty 负责接收交易报告。 - [ ] **您的应用使用自有购买流程完成购买**(StoreKit / BillingClient / 后端): - [ ] 沙盒购买在商店 UI 中成功完成。 - [ ] 应用能妥善处理待处理/失败/取消等异常情况。 - [ ] **交易已上报至 Adapty**。 - [ ] 已在应用代码中[启用观察者模式](implement-observer-mode)。 - [ ] 购买记录出现在 Adapty Event Feed 中。 - [ ] 续订、取消和退款情况能随时间推移得到正确反映(如适用)。 - [ ] **付费墙展示已被追踪**。在付费墙展示时调用 [`logShowFlow`(iOS SDK v4+)/ `logShowPaywall` 方法](present-remote-config-paywalls#track-paywall-view-events)。 - [ ] **恢复购买功能在您的实现中正常工作**。重新安装应用或切换设备后,访问权限能正确恢复。 - [ ] **商店审核要求**: - [ ] **恢复购买**操作可访问,并能触发您的恢复流程。 - [ ] 使用条款和隐私政策可从付费墙或购买界面访问,并在浏览器中打开。 </TabItem> </Tabs> 如有任何关于集成 Adapty SDK 的问题,请使用右下角的 AI 聊天机器人,或发送邮件至 [support@adapty.io](mailto:support@adapty.io) 联系我们。 --- # File: observer-vs-full-mode --- --- title: "观察者模式" description: "比较 Adapty 中用于订阅的观察者模式和完整模式。" --- Adapty 是一个功能强大、灵活的应用内购买平台,旨在提升您的收入和订阅用户规模。Adapty 提供可针对特定用户市场细分进行定制的付费墙、针对定价、时长、试用期和视觉元素的 A/B 测试,以及用于应用变现的全面分析工具和第三方集成,助力您的增长策略。 然而,如果您已经拥有自己的购买基础设施,且暂时不打算切换到 Adapty 的系统,可以考虑使用 Adapty 观察者模式。这种受限模式不使用 Adapty 付费墙,不针对用户目标受众进行付费墙定向,不管理订阅(包括处理续订和账单重试),而仅专注于分析功能。尽管存在这些限制,观察者模式仍提供强大的分析能力,包括与归因系统的集成、高级分析、消息推送和 CRM 用户画像。 两种模式的价格相同,都需要更新您的移动应用,因此选择的本质在于:是迁移到 Adapty 的基础设施以获得完整功能,还是保留现有基础设施,同时仅获得第三方集成和分析能力。 | 功能 | 观察者模式 | 完整模式 | |-------------|-------------|---------| | **全面分析** | ✅ | ✅ | | **第三方集成** | ✅ | ✅ | | **响应购买事件以向用户授予/限制付费访问权限** | ❌ | ✅ | | **购买基础设施维护方** | 您自己 | Adapty | | **A/B 测试** | <p>可行,但需要大量额外的编码和配置,比完整模式工作量更大。</p> | ✅ | | **实施时间** | <p>用于分析和集成:不足一小时</p><p>包含 A/B 测试:经充分测试后最多需要一周</p> | 数小时 | ## 观察者模式的工作原理 \{#how-observer-mode-works\} 在观察者模式下,您需要将来自 Apple/Google 的新交易上报给 Adapty SDK,Adapty SDK 再将其转发至 Adapty 后端。您负责管理应用中付费内容的访问权限、完成交易、处理续订、解决账单问题等。 ## 如何设置观察者模式 \{#how-to-set-up-observer-mode\} 1. 完成 Adapty 与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成设置。 2. 在配置 Adapty SDK 时将 `observerMode` 参数设置为 `true` 以启用该模式。请参阅以下平台的设置说明:[iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk)、[Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk)、[Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk) 和 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk)。 3. 针对 iOS 及基于 iOS 的跨平台框架,将现有购买基础设施中的交易[上报至 Adapty](report-transactions-observer-mode)。 4. (可选)如需使用第三方集成,请按照[配置第三方集成](configuration)文档中的说明进行设置。 :::warning 在观察者模式下运行时,Adapty SDK 不会最终确认交易,请确保您自行处理这一环节。 ::: ## 如何在观察者模式中使用付费墙和 A/B 测试 \{#how-to-use-paywalls-and-ab-tests-in-observer-mode\} 在观察者模式下,Adapty SDK 无法确定购买来源,因为购买操作在您自己的基础设施中完成。因此,如果您打算在观察者模式中使用付费墙和/或 A/B 测试,则需要在上报交易时,在移动应用代码中将来自应用商店的交易与对应的付费墙进行关联。 此外,使用付费墙编辑工具设计的付费墙在观察者模式下需要以特殊方式展示: - 在观察者模式下展示付费墙:[iOS](implement-observer-mode) 或 [Android](android-present-paywall-builder-paywalls-in-observer-mode)。 - 在观察者模式下上报交易时,[将付费墙与购买交易进行关联](report-transactions-observer-mode)。 --- # File: migration-from-revenuecat --- --- title: "从 RevenueCat 迁移" description: "按照我们的分步指南,从 RevenueCat 迁移到 Adapty。" --- 整个迁移计划共分 5 个步骤,平均耗时约 2 小时。90% 的迁移工作可在一个工作日内完成。 1. 了解核心差异;创建并准备 Adapty 账户 _(5 分钟)_; 2. 为您的平台安装 Adapty SDK([iOS](sdk-installation-ios)、[Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter)、[Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)、[Unity](sdk-installation-unity)),替换 RevenueCat SDK _(1 小时)_; 3. 为 Adapty 设置 [Apple App Store 服务器通知](enable-app-store-server-notifications),并(可选)设置[原始事件转发](enable-app-store-server-notifications#raw-events-forwarding) _(5 分钟)_; 4. 测试并发布您的应用更新 _(30 分钟)_; 5. (可选)向 RevenueCat 客服申请 CSV 格式的历史数据 _(5 分钟)_; 6. (可选)通过 Adapty 客服导入历史数据 _(30 分钟)_。 :::info 您的订阅用户将自动迁移 所有曾经激活过订阅的用户,只要打开集成了 Adapty SDK 的新版应用,就会立即迁移到 Adapty。订阅状态验证和高级功能访问权限将自动恢复。 ::: 在发布集成了 Adapty SDK 的新版应用之前,请务必查看我们的[发布清单](release-checklist)。 ## 了解核心差异;创建并准备 Adapty 账户 \{#learn-the-core-differences-create-and-prepare-an-adapty-account\} Adapty 与 RevenueCat 的 SDK 设计思路相似,最大的区别在于网络使用方式和响应速度:Adapty SDK 专为按需快速获取信息而设计,当你发起请求时,能以最快速度返回结果。例如,在请求付费墙时,你会先获取[远程配置](customize-paywall-with-remote-config),用于预构建用户引导或付费墙界面,然后再通过专门的请求获取产品信息。 命名方式略有不同: | RevenueCat | Adapty | | :---------- | :-------------- | | Package | 产品 | | Offering | 付费墙 | | Paywall | 付费墙编辑工具 | | Entitlement | 访问等级 | Adapty 有一个[版位](placements)的概念。它是应用内用户可以发起购买的逻辑位置。大多数情况下,你会有一到两个版位: - 用户引导(因为 80% 的购买都发生在这里); - 通用版位(在用户完成引导后,在设置页面或应用内展示)。 <img src="/assets/shared/img/2406d97-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 安装 Adapty SDK 并替换 RevenueCat SDK \{#install-adapty-sdk-and-replace-revenuecat-sdk\} 为你的平台安装 Adapty SDK([iOS](sdk-installation-ios)、[Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter)、[Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)、[Unity](sdk-installation-unity)),并集成到你的应用中。 你需要在应用端替换几个 SDK 方法。下面介绍最常用的函数及其对应的 Adapty SDK 替换方式。 ### SDK 激活 \{#sdk-activation\} 将 `Purchases.configure` 替换为 `Adapty.activate`。 ### 获取付费墙(产品组合)\{#getting-paywalls-offerings\} 将 `Purchases.shared.getOfferings` 替换为 [`Adapty.getPaywall`](fetch-paywalls-and-products#fetch-paywall-information)。 在 Adapty 中,你始终通过[版位 ID](placements) 来请求付费墙。实际上,每次最多只会获取 1-2 个付费墙,这样设计是有意为之,目的是加快 SDK 速度并减少网络请求。 ### 获取用户(客户用户画像)\{#getting-a-user-customer-profile\} 将 `Purchases.shared.getCustomerInfo` 替换为 `Adapty.getProfile`。 ### 获取产品 \{#getting-products\} 在 RevenueCat 中,你使用以下结构:`Purchases.shared.getOfferings`,然后 `self.offering?.availablePackages`。 在 Adapty 中,你首先请求一个付费墙(见上文)以立即访问 Adapty 的[远程配置](customize-paywall-with-remote-config),然后通过 [`Adapty.getPaywallProducts`](fetch-paywalls-and-products#fetch-products) 获取产品。 ### 进行购买 \{#making-a-purchase\} 将 `Purchases.shared.purchase` 替换为 [`Adapty.makePurchase`](making-purchases#make-purchase)。 ### 检查访问等级(权益)\{#checking-access-level-entitlement\} 先获取用户画像(请先阅读上文),然后将 `customerInfo?.entitlements["premium"]?.isActive == true` 替换为 [`profile.accessLevels["premium"]?.isActive == true`](subscription-status#retrieving-the-access-level-from-the-server)。 ### 恢复购买 \{#restore-purchase\} 将 `Purchases.shared.restorePurchases` 替换为 [`Adapty.restorePurchases`](restore-purchase)。 ### 检查用户是否已登录 \{#check-if-the-user-is-logged-in\} 将 `Purchases.shared.isAnonymous` 替换为 `if profile.customerUserId == nil`。 ### 登录用户 \{#log-in-user\} 将 `Purchases.shared.logIn` 替换为 [`Adapty.identify`](identifying-users#set-customer-user-id-after-configuration)。 ### 退出用户登录 \{#log-out-user\} 将 `Purchases.shared.logOut` 替换为 [`Adapty.logout`](identifying-users#logging-out-and-logging-in)。 ## 将 App Store 服务器端通知切换到 Adapty \{#switch-app-store-server-side-notifications-to-adapty\} 具体操作方法请参阅[此处](migrate-to-adapty-from-another-solutions#changing-apple-server-notifications)。 ## 测试并发布新版本应用 \{#test-and-release-a-new-version-of-your-app\} 如果你看到这里,说明你已经完成了: - [x] 配置 Adapty 看板 - [x] 安装 Adapty SDK - [x] 用 Adapty 函数替换了原有 SDK 逻辑 - [x] 将 App Store 服务端通知切换至 Adapty,并可选择开启原始事件转发至 RevenueCat - [ ] 沙盒购买测试 - [ ] 发布新版本应用 完成以上步骤后,在沙盒环境中进行一次测试购买,然后发布应用即可。 :::info 请参阅[发布检查清单](release-checklist)。 使用我们的清单对现有集成进行最终检查,或添加[归因](attribution-integration)或[分析](analytics-integration)集成等其他功能。 ::: ## (可选)以 CSV 格式导出 RevenueCat 历史数据 \{#optional-export-your-revenuecat-historical-data-in-csv-format\} :::warning 不要急于导入历史数据 建议在集成 SDK 并发版后,至少等待一周再进行历史数据导入。在此期间,我们将通过 SDK 获取所有购买价格信息,从而使导入的数据更加准确。 ::: 请按照 [RevenueCat 官方文档](https://www.revenuecat.com/docs/integrations/scheduled-data-exports) 中的说明,以 CSV 格式从 RevenueCat 导出您的历史数据。 ## (可选)向 RevenueCat 支持团队索取 Google Purchase Tokens \{#optional-ask-revenuecat-support-for-google-purchase-tokens\} 如果你需要导入 Google Play 交易记录,请通过 RevenueCat 的[支持页面](https://app.revenuecat.com/settings/support)联系其支持团队,索取包含 Google Purchase Tokens 的 CSV 文件。Google Purchase Token 是 Google Play 为每笔交易提供的唯一标识符,对于在 Adapty 中准确追踪和验证购买记录至关重要。该信息不包含在标准导出文件中。该文件包含以下三列: - `user_id` - `google_purchase_token` - `google_product_id` ## 联系我们以导入历史数据 \{#write-us-to-import-your-historical-data\} 请通过网站聊天工具或发送邮件至 [support@adapty.io](mailto:support@adapty.io) 联系我们,并附上您的 CSV 文件。 1. 将您从 RevenueCat 导出的 CSV 文件直接发送给我们的支持团队。 2. 如果需要导入 Google Play 交易记录,请同时附上从 RevenueCat 支持团队获取的包含 Google Purchase Token 的 CSV 文件。 3. 请告知我们应使用哪个用户 ID 作为 Customer User ID(Adapty 的主要用户标识符):`rc_original_app_user_id` 或 `rc_last_seen_app_user_id_alias`。 我们的支持团队将为您把交易记录导入 Adapty。每笔交易将导入以下数据: | 参数 | 描述 | | ----------------------------- | ------------------------------------------------------------ | | user_id | 客户用户 ID,即用户在 Adapty 和您系统中的主要标识符。 | | apple_original_transaction_id | 对于订阅链,这是原始交易的购买日期,通过 `store_original_transaction_id` 关联。 | | google_product_id | Google Play 商店中的产品 ID。 | | google_purchase_token | Google Play 为每笔交易提供的唯一标识符,用于验证。 | | country | 用户所在国家/地区。 | | created_at | 用户创建的日期和时间。 | | subscription_expiration_date | 订阅到期的日期和时间。 | | email | 终端用户的电子邮件。 | | phone_number | 终端用户的手机号码。 | | idfa | 广告主标识符(IDFA),由 Apple 分配给用户设备。 | | idfv | 供应商标识符(IDFV),由同一开发者分配给其所有应用,并在该设备上的这些应用间共享。 | | advertising_id | 由 Android 操作系统提供的唯一标识符,广告主可用于广告追踪。 | | attribution_channel | 营销渠道名称。 | | attribution_campaign | 营销活动名称。 | | attribution_ad_group | 归因广告组。 | | attribution_ad_set | 归因广告集。 | | attribution_creative | 归因创意关键词。 | 此外,以下集成的集成标识符也将被导入:Amplitude、Mixpanel、AppsFlyer、Adjust 和 FacebookAds。 ## 常见问题 \{#faq\} ### 我已成功安装 Adapty SDK 并发布了包含它的新版本。那些没有更新到包含 Adapty SDK 版本的老订阅用户会怎样?\{#i-successfully-installed-adapty-sdk-and-released-a-new-app-version-with-it-what-will-happen-to-my-legacy-subscribers-who-did-not-update-to-a-version-with-adapty-sdk\} 大多数用户会在夜间充电时让手机自动更新所有应用,因此这通常不是问题。确实可能还有少量付费订阅用户没有升级,但他们仍然可以访问高级内容。你不需要为此担心,也无需强制他们更新。 ### 我需要尽快从 RevenueCat 导出历史数据吗?还是说不导出会丢失数据?\{#do-i-need-to-export-my-historical-data-from-revenuecat-as-quickly-as-possible-or-will-i-lose-it\} 不需要那么着急,先发布集成了 Adapty SDK 的版本,之后再向我们提供历史数据即可。我们会还原用户的付款记录,并填充[用户画像](profiles-crm)和[数据图表](charts)。 ### 我使用 MMP(AppsFlyer、Adjust 等)和分析工具(Mixpanel、Amplitude 等)。如何确保一切正常运行?\{#i-use-mmp-appsflyer-adjust-etc-and-analytics-mixpanel-amplitude-etc-how-do-i-make-sure-that-everything-will-work\} 您首先需要通过我们的 SDK 将您希望我们发送数据的第三方服务 ID 传递给我们。请阅读[归因集成](attribution-integration)和[分析集成](analytics-integration)的相关指南。对于历史数据和老用户,**请确保您从 RevenueCat 导出的数据中将这些 ID 传递给我们。** --- # File: migration-from-superwall --- --- title: "从 Superwall 迁移" description: "通过逐步指南将 Superwall 迁移到 Adapty,涵盖每个 SDK 调用和概念的对应关系。" --- 从 Superwall 迁移到 Adapty 通常只需约两小时。你只需替换 SDK、将应用商店服务器通知指向 Adapty,然后发布新版本即可。付费订阅用户的权益会自动保留——Adapty 在用户首次启动时即可从 App Store 和 Google Play 收据中恢复。 :::info 你的订阅用户将自动完成迁移 所有曾经激活过订阅的用户,只要打开集成了 Adapty SDK 的新版应用,就会自动迁移到 Adapty。订阅状态验证和高级访问权限会自动恢复。 ::: ## 本指南的结构 \{#how-this-guide-is-organized\} 迁移共分六个步骤: 1. [将 Superwall 概念映射到 Adapty](#map-your-superwall-concepts-to-adapty) _(5 分钟)_ 2. [安装 Adapty SDK](#install-the-adapty-sdk) _(15 分钟)_ 3. [替换 SDK 调用](#replace-sdk-calls) _(1 小时)_ 4. [切换 App Store 和 Google Play 服务器通知](#switch-app-store-and-google-play-server-notifications) _(5 分钟)_ 5. [测试与发布](#test-and-release) _(30 分钟)_ 6. [(可选)导入历史数据](#optional-import-historical-data) ## 将 Superwall 概念映射到 Adapty \{#map-your-superwall-concepts-to-adapty\} 大多数 Superwall 概念在 Adapty 中都有对应的概念: | Superwall | Adapty | 变更说明 | | :------------------- | :------------------------------------------------ | :--------------------------------------------------------------------------- | | Campaign | [版位](placements) + [目标受众](audience) | Campaign 逻辑拆分为版位(位置)和目标受众(规则)。 | | Placement | [版位](placements) | 概念相同,名称相同。 | | Audience filter | [目标受众](audience) | 规则集位于版位内部。 | | Entitlement | [访问等级](access-level) | 命名标识符(例如 `premium`)。 | | WebView paywall | [付费墙编辑工具付费墙](adapty-paywall-builder) | 由 Adapty SDK 原生渲染,而非使用 `WKWebView`。 | | `PurchaseController` | 内置 | 无需实现协议 —— Adapty 自动处理购买流程。 | | Feature gating | [访问等级](access-level)检查 | 检查 `profile.accessLevels["premium"]?.isActive`。 | 在接触代码之前,有两个思维转变值得注意: - **获取与展示是两个独立步骤**:Superwall 的 `register` 方法在一次调用中完成付费墙获取、营销活动评估和 UI 展示。Adapty 将这些步骤拆分开来——你需要先获取付费墙,拿到其配置,再进行展示。虽然多了几行代码,但这让你可以预加载配置、显示自定义加载状态,或根据自己的逻辑取消展示。 - **订阅状态按访问等级区分**:Superwall 暴露单一的 `subscriptionStatus` 发布属性。Adapty 返回一个包含命名访问等级的 [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile),因此同一用户可以同时持有 `sports` 和 `science` 两个独立的访问等级。如需同步读取,建议从 `AdaptyDelegate` 缓存用户画像,而不是每次视图加载时都调用 `getProfile()`。 ## 安装 Adapty SDK \{#install-the-adapty-sdk\} 为你的平台安装 Adapty SDK —— [iOS](sdk-installation-ios)、[Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter)、[Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)、[Unity](sdk-installation-unity) 或 [Capacitor](sdk-installation-capacitor) —— 同时从项目中移除 SuperwallKit。 ## 替换 SDK 调用 \{#replace-sdk-calls\} 逐一检查集成的各个部分,将 Superwall 调用替换为对应的 Adapty 调用。每个小节末尾都附有链接,涵盖全部七个平台的 SDK——请根据你的应用选择对应链接。 ### 初始化 SDK \{#initialize-the-sdk\} 将 `Superwall.configure` 替换为 `Adapty.activate`。 请查阅适用于你所在平台的安装指南 —— [iOS](sdk-installation-ios)、[Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter)、[Kotlin Multiplatform](sdk-installation-kotlin-multiplatform)、[Unity](sdk-installation-unity) 或 [Capacitor](sdk-installation-capacitor)。 ### 识别和登出用户 \{#identify-and-log-out-users\} 将 `Superwall.shared.identify` 替换为 `Adapty.identify`,将 `Superwall.shared.reset` 替换为 `Adapty.logout`。两个 SDK 都会在首次启动时生成匿名用户画像,因此只有在用户登录或登出时才需要调用这些方法。识别用户后需重新获取付费墙——新用户可能会匹配到不同的目标受众。 请参阅适用于您平台的识别指南 — [iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Kotlin Multiplatform](kmp-identifying-users)、[Unity](unity-identifying-users) 或 [Capacitor](capacitor-identifying-users)。 ### 获取并展示付费墙 \{#fetch-and-present-a-paywall\} 将 `Superwall.shared.register` 替换为两步流程:先用 `Adapty.getPaywall` 获取付费墙,再用 `AdaptyUI.getPaywallConfiguration` 加载其视图配置,最后进行展示。 需要注意两点区别: - **功能门控取代了 `feature:` 闭包**:付费墙关闭后,检查返回的用户画像(或通过 `Adapty.getProfile` 获取)上的有效访问等级,再据此进行分支处理。 - **付费墙由 SDK 渲染**:Superwall 在 `WKWebView` 中渲染付费墙。Adapty 则通过付费墙编辑工具以原生方式渲染付费墙——字体、产品信息和按钮均由 SDK 直接绘制。 请参阅适用于您平台的付费墙快速入门 — [iOS](ios-quickstart-paywalls)、[Android](android-quickstart-paywalls)、[React Native](react-native-quickstart-paywalls)、[Flutter](flutter-quickstart-paywalls)、[Kotlin Multiplatform](kmp-quickstart-paywalls)、[Unity](unity-quickstart-paywalls) 或 [Capacitor](capacitor-quickstart-paywalls)。 ### 检查订阅状态 \{#check-subscription-status\} 将 `Superwall.shared.subscriptionStatus` 替换为对用户画像中指定访问等级的检查:`profile.accessLevels["premium"]?.isActive`。通过 `AdaptyDelegate.didLoadLatestProfile(_:)` 监听变更,而非使用 `@Published` 属性模式,并在本地缓存用户画像以便同步读取。 请参阅适用于您平台的订阅状态指南 — [iOS](ios-check-subscription-status)、[Android](android-check-subscription-status)、[React Native](react-native-check-subscription-status)、[Flutter](flutter-check-subscription-status)、[Kotlin Multiplatform](kmp-check-subscription-status)、[Unity](unity-check-subscription-status) 或 [Capacitor](capacitor-check-subscription-status)。 ### 处理购买与恢复 \{#handle-purchases-and-restores\} 使用付费墙编辑工具时,两个 SDK 都会在付费墙界面内自动处理购买流程——**此步骤可跳过**。 对于自定义付费墙,Superwall 需要实现 `PurchaseController`,而 Adapty 不需要:将 `PurchaseController.purchase` 替换为 `Adapty.makePurchase`,将 `PurchaseController.restorePurchases` 替换为 `Adapty.restorePurchases`。SDK 会自行处理验证逻辑。 请参阅适用于您平台的自定义付费墙快速入门指南 — [iOS](ios-quickstart-manual)、[Android](android-quickstart-manual)、[React Native](react-native-quickstart-manual)、[Flutter](flutter-quickstart-manual)、[Kotlin Multiplatform](kmp-quickstart-manual)、[Unity](unity-quickstart-manual) 或 [Capacitor](capacitor-quickstart-manual)。 ### 设置用户属性 \{#set-user-attributes\} 将 `Superwall.shared.setUserAttributes` 替换为 `Adapty.updateProfile`。 请参阅适用于您平台的用户属性指南 — [iOS](setting-user-attributes)、[Android](android-setting-user-attributes)、[React Native](react-native-setting-user-attributes)、[Flutter](flutter-setting-user-attributes)、[Kotlin Multiplatform](kmp-setting-user-attributes)、[Unity](unity-setting-user-attributes) 或 [Capacitor](capacitor-setting-user-attributes)。 ## 切换 App Store 和 Google Play 服务器通知 \{#switch-app-store-and-google-play-server-notifications\} 将应用商店的服务器通知指向 Adapty。Adapty 不依赖这些通知也能正常运行,但分析数据、第三方集成以及 A/B 测试数据图表都需要它们: - **App Store**:请参阅[启用 App Store 服务器通知](enable-app-store-server-notifications)。 - **Google Play**:请参阅[启用实时开发者通知](enable-real-time-developer-notifications-rtdn)。 如果您想在推出过程中并行运行 Superwall 和 Adapty,请使用[原始事件转发](enable-app-store-server-notifications#raw-events-forwarding) —— Adapty 会将商店事件代理回 Superwall,同时您可以验证新的集成。 ## 测试与发布 \{#test-and-release\} 发布前,请逐一确认以下各项: - [x] 已配置 Adapty 看板(产品、付费墙、版位、访问等级) - [x] 已安装 Adapty SDK - [x] 已将 Superwall SDK 调用替换为 Adapty 等效调用 - [x] 已将 App Store 和 Google Play 服务器通知指向 Adapty - [ ] 已完成沙盒购买 - [ ] 已提交新版本应用 请参阅[发布检查清单](release-checklist)进行最终验证。 ## (可选)导入历史数据 \{#optional-import-historical-data\} Superwall 并不拥有您的订阅状态——App Store 和 Google Play 才是。Adapty 在首次启动时会验证收据,因此付费用户无需任何导入即可保留其访问权限。 如果您希望将历史交易数据回填到 Adapty 分析中,请参考[向 Adapty 导入历史数据](importing-historical-data-to-adapty)。建议在 SDK 发布后至少等待一周,以便 SDK 有足够时间收集最新的购买价格。 ## 常见问题 \{#faq\} ### 不更新应用的订阅者会怎样?\{#what-happens-to-subscribers-who-dont-update-the-app\} 大多数用户会在夜间自动更新应用,因此使用旧版本的用户比例会迅速下降。留在旧版本的订阅者可以直接通过 App Store 或 Google Play 继续使用其权益,无需强制更新。 ### 我的 Superwall 活动目标受众会自动迁移吗?\{#do-my-superwall-campaign-audiences-carry-over\} 不会。Superwall 的受众过滤器和 Adapty 的目标受众分别在不同的看板中配置,且使用不同的标识符。请在 Adapty 的[版位](placements)中重新创建你的定向规则,作为[目标受众](audience)进行设置。大多数应用只有一两个版位(用户引导和通用应用内触发),因此重建工作通常很快就能完成。 ### Adapty 是否有与 `getPresentationResult` 等效的方法?\{#does-adapty-have-an-equivalent-to-getpresentationresult\} 没有单独的调用方法。如需判断某个版位是否会显示付费墙,请调用 `Adapty.getPaywall(placementId:)` 并根据结果进行分支处理。若调用成功,说明该用户的目标受众已分配付费墙;若调用失败(原因是未配置付费墙),则跳过展示并执行备用逻辑。 --- # File: importing-historical-data-to-adapty --- --- title: "将历史数据导入 Adapty" description: "将历史数据导入 Adapty 以获取详细分析。" --- 安装 Adapty SDK 并发布应用后,你可以在 [Profiles](profiles-crm) 部分查看用户和订阅者。但如果你有旧系统需要迁移到 Adapty,或者只是想在 Adapty 中查看现有数据,该怎么办? :::note 数据导入并非必须 一旦用户打开集成了 Adapty SDK 的应用,Adapty 会自动为历史用户授予访问等级并恢复其购买事件。在这种情况下,无需导入历史数据。不过,如果你有大量历史交易记录,导入数据可以确保分析数据的准确性,但对于迁移而言通常并非必须。 ::: 将数据导入 Adapty 的步骤如下: 1. 将交易记录导出为 CSV 文件(iOS、Android 和 Stripe 需分别提供独立文件)。详细格式要求请参阅下方的[导入文件格式说明](importing-historical-data-to-adapty#import-file-format)。 2. 如果任意文件超过 1 GB,请准备一个约 100 行的数据样本。 3. 将所有文件上传至 Google Drive(可以压缩,但需保持独立文件)。 4. 对于 iOS 交易记录,即使使用的是 StoreKit 1,也请确保 [**App settings**](https://app.adapty.io/settings/ios-sdk) 中的 **In-app purchase API** 部分已填写 **Issuer ID**、**Key ID** 及 **Private key**(.P8 文件)。详细操作说明请参阅[提供 Issuer ID 和 Key ID](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) 及[上传 In-App Purchase Key 文件](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file)。 5. 通过[电子邮件](mailto:support@adapty.io)或 Adapty 看板内的在线客服将链接分享给我们的团队。 放心,导入历史数据不会产生重复记录,即使数据与 Adapty 中已有条目存在重叠。 ## Android 已知限制 \{#known-limitations-for-android\} 1. 只会恢复有效订阅,已过期的交易记录不会被恢复。 2. 只会恢复订阅中最近一次续订记录,完整的购买链不会被恢复。 3. 如果产品价格在购买后发生了变化,将使用当前价格,可能导致定价不准确。 :::note 如果你有大量 Android 交易记录,在开始导入前可能需要[申请提高 Google Play Developer API 配额](google-play-quota-increase),以避免超出默认 API 限制。 ::: ## 导入文件格式 \{#import-file-format\} :::tip 如果你正在从 RevenueCat 迁移,可以直接发送 RevenueCat 导出文件,无需转换。导出说明请参阅 [RevenueCat 文档](https://www.revenuecat.com/docs/integrations/scheduled-data-exports)。 ::: 请按照以下规则准备数据文件: - [ ] 文件格式为 .CSV。 - [ ] Android、iOS 和 Stripe 导入需使用独立文件。 - [ ] 每个导入文件包含所有[必填列](importing-historical-data-to-adapty#required-fields)。 - [ ] 导入文件中的列需有标题行。 - [ ] 列标题须与下表 **Column name** 列中的内容完全一致,请仔细检查是否有拼写错误。 - [ ] 不需要的列可以不出现在文件中,不要为没有数据的字段添加空列。 - [ ] 导入文件不应包含表中未提及的额外列,如有请删除。 - [ ] 值之间用逗号分隔。 - [ ] 值不需要用引号括起来。 - [ ] 如果一个用户有多个 **apple_original_transaction_id**,请为每个 **apple_original_transaction_id** 单独添加一行,否则可能无法恢复消耗型商品的购买记录。 iOS 和 Android 的示例文件请参考:[iOS](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_ios_sample.csv) 和 [Android](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_android_sample.csv)。 ### 可用的导入文件列 \{#available-import-file-columns\} | 列名 | 是否必填 | 说明 | |-----------|--------|-----------| | **user_id** | 必填 | 你的用户 ID | | **apple_original_transaction_id** | iOS 必填 | <p>原始交易 ID(OTID,[了解更多](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid)),用于 StoreKit 2 导入机制。由于一个用户可能有多个 OTID,只需提供至少一个即可成功导入。</p><p></p><p>**注意:** 此导入需要在 Adapty 看板中配置 In-app purchase API 凭据。操作说明请参阅[此处](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file)。</p> | | **google_product_id** | Google 必填 | Google Play Store 中的产品 ID。 | | **google_purchase_token** | Google 必填 | 唯一标识符,代表用户及其购买的应用内产品 ID | | **google_is_subscription** | Google 必填 | 可选值为 `1` \| `0` | | **stripe_token** | Stripe 必填 | 代表唯一购买记录的 Stripe 对象 token,可以是 Stripe 订阅的 token(`sub_...`)或 Payment Intent 的 token(`pi_...`)。 | | **subscription_expiration_date** | 可选 | 订阅到期日期,即下次扣费日期,包含时区的日期时间格式(2020-12-31T23:59:59-06:00) | | **created_at** | 可选 | 用户画像创建的日期时间(2019-12-31 23:59:59-06:00) | | **birthday** | 可选 | 用户生日,格式为 2000-12-31 | | **email** | 可选 | 用户的电子邮件地址 | | **gender** | 可选 | 用户性别 | | **phone_number** | 可选 | 用户的电话号码 | | **country** | 可选 | 格式为 [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) | | **first_name** | 可选 | 用户名字 | | **last_name** | 可选 | 用户姓氏 | | **last_seen** | 可选 | 包含时区的日期时间(2020-12-31T23:59:59-06:00) | | **idfa** | 可选 | 广告标识符(IDFA)是 Apple 为用户设备随机分配的设备标识符,仅适用于 iOS 应用 | | **idfv** | 可选 | 供应商标识符(IDFV)是分配给同一开发者旗下所有应用的唯一代码,仅适用于 iOS 应用 | | **advertising_id** | 可选 | 广告 ID 是由 Android 操作系统分配的唯一代码,广告商可用其唯一标识用户设备 | | **amplitude_user_id** | 可选 | Amplitude 中的用户 ID | | **amplitude_device_id** | 可选 | Amplitude 中的设备 ID | | **mixpanel_user_id** | 可选 | Mixpanel 中的用户 ID | | **appmetrica_profile_id** | 可选 | AppMetrica 中的用户画像 ID | | **appmetrica_device_id** | 可选 | AppMetrica 中的设备 ID | | **appsflyer_id** | 可选 | AppsFlyer 的唯一标识符 | | **adjust_device_id** | 可选 | Adjust 中的设备 ID | | **facebook_anonymous_id** | 可选 | Facebook 为匿名与你的应用或网站互动(即未登录 Facebook)的用户生成的唯一标识符 | | **branch_id** | 可选 | Branch 的唯一标识符 | | **attribution_source** | 可选 | 归因来源集成,例如 appsflyer | | **attribution_status** | 可选 | organic | | **attribution_channel** | 可选 | 带来该交易的归因渠道 | | **attribution_campaign** | 可选 | 带来该交易的归因活动 | | **attribution_ad_group** | 可选 | 带来该交易的归因广告组 | | **attribution_ad_set** | 可选 | 带来该交易的归因广告集 | | **attribution_creative** | 可选 | 广告或营销活动中用于追踪效果的具体视觉或文字素材,用于衡量其在推动点击、转化或安装等目标行为方面的效果 | | **custom_attributes** | 可选 | 以 JSON 字典的键值格式定义最多 30 个自定义属性:<ul><li>**key**:(字符串)自定义属性名称</li><li>**value**:(字符串、整数、浮点数或布尔值)自定义属性值。</li></ul><p>格式:`"{'string_value': 'some_value', 'float_value': 123.0, 'int_value': 456}"`。</p><p>注意格式中双引号和单引号的使用,布尔值和整数将被转换为浮点数。</p> | ### 必填字段 \{#required-fields\} 每个平台有两组必填字段:**user_id** 以及用于识别对应平台购买记录的数据。各平台的必填字段请参见下表。 | 平台 | 必填字段 | |--------|---------------| | iOS | <p>user_id</p><p>apple_original_transaction_id</p> | | Android | <p>user_id</p><p>google_product_id</p><p>google_purchase_token</p><p>google_is_subscription</p> | | Stripe | <p>user_id</p><p>stripe_token</p> | 缺少这些字段,Adapty 将无法获取交易记录。 为了获得准确的同期群分析,请填写 `created_at`。若未提供,我们将以首次购买日期作为安装日期。 ### 将数据导入 Adapty \{#import-data-to-adapty\} 请通过 [support@adapty.io](mailto:support@adapty.io) 或 [Adapty 看板](https://app.adapty.io/overview) 内的在线客服联系我们并分享导入文件。 --- # File: migrate-integrations-to-adapty --- --- title: "将集成迁移到 Adapty" description: "将分析和归因集成从旧版解决方案切换到 Adapty,同时避免重复事件或中断广告系列。" --- 迁移到 Adapty 不仅仅是切换 SDK。您的第三方分析和归因集成——如 Amplitude 和 Adjust 等工具——也需要进行协调切换。操作得当,过渡过程中几乎不会产生重复或丢失的事件,也不会影响您的推广活动。 ## 映射您的事件 \{#map-your-events\} 在大多数 Adapty 集成中,事件名称是可自定义的。您可以将其配置为与看板和广告活动中已使用的名称匹配。切换后,您的分析和广告活动报告将继续使用相同的事件名称。要查看 Adapty 中所有可用事件的完整列表,请参阅[事件](events)。 对于 Adjust,集成使用事件 ID 而非自定义事件名称。请将您现有的事件 ID 从 Adjust 看板迁移到 Adapty 集成配置中。详情请参阅 [Adjust 集成指南](adjust)。 ## Adapty 如何创建集成事件 \{#how-adapty-creates-integration-events\} 要将事件发送到集成,Adapty 必须拥有用户画像。用户画像通过以下两种方式之一创建: - **历史数据导入**:在 SDK 上线之前,当您[导入历史交易数据](importing-historical-data-to-adapty)时创建用户画像。 - **SDK 交互**:当用户首次使用带有 Adapty SDK 的应用时,系统自动创建用户画像。 Adapty 可实时获取在旧系统中发生的购买记录。但只有在买家的用户画像存在时,才能发送集成事件。用户画像在用户使用集成了 Adapty SDK 的应用版本首次打开应用时创建。未升级到新版本的用户将不会产生集成事件。 ## 迁移日之前的准备工作 \{#prepare-before-migration-day\} ### 排除历史事件 \{#exclude-historical-events\} 在您的[集成设置](configuration)中启用 **Exclude Historical Events**。这可以防止早于用户首次 Adapty SDK 会话的事件被发送到集成中。 此设置在[历史数据导入](importing-historical-data-to-adapty)期间尤为重要,届时 Adapty 会一次性处理大量过去的交易记录。如果不启用此设置,这些交易将在您的分析工具中生成大量事件。 ### 提前配置集成 \{#set-up-the-integration-in-advance\} Adapty 允许您在保持集成禁用状态的同时进行配置和测试。您可以设置凭据、事件映射和过滤器,而无需在准备好之前激活集成。启用集成时,所有配置都会被保留,因此在迁移日之前保持关闭状态不会造成任何数据丢失。 要查找您的集成,请参阅[归因集成](attribution-integration)、[分析集成](analytics-integration)、[消息服务集成](messaging)或 [Webhook 和 ETL 集成](webhook-and-etl)。 ## 迁移当天切换 \{#switch-on-migration-day\} 在迁移当天,同时禁用旧方案中的集成并在 Adapty 中启用该集成。同时运行两者将产生重复事件。 在迁移当天暂停大型获客活动。这可以降低由重叠窗口期内的事件导致活动优化出错的风险。 ## 预期结果 \{#what-to-expect\} 迁移过程中出现少量缺失或重复的集成事件是不可避免的。只要切换操作正确,受影响的事件数量可以忽略不计。 产生数据缺口的主要原因在于上述时序问题:Adapty 只有在用户画像存在之后,才能为某笔购买发送集成事件。在旧系统中产生的购买记录,只有当买家使用集成了 Adapty SDK 的应用打开 App 后,才会生成对应的 Adapty 集成事件。 ## 集成与服务器到服务器通知 \{#integrations-vs-server-to-server-notifications\} Adapty 建议使用集成,而非将原始服务器到服务器商店通知直接转发给您的分析或归因工具。 使用集成的优势: - **统一格式**:来自所有商店(App Store、Google Play、Stripe)的事件均采用相同的事件格式。 - **数据增强**:事件包含 Adapty 收集的数据,例如订阅状态和用户属性,而原始通知则不包含这些信息。 --- # File: whats-new --- --- title: "最新动态" description: "随时了解 Adapty 的最新功能和改进" --- 探索最新功能、改进、SDK 更新以及文档增强内容,助您优化应用的变现策略。本页面每月重点介绍最重要的版本发布。 :::note 对新功能有反馈意见? 欢迎告诉我们!请通过 [产品反馈板](https://adapty.featurebase.app/en?b=69831ba5e82e7a3391632ec2) 联系我们。 ::: ## 2026年7月 \{#july-2026\} - **虚拟货币**:在应用内定义代币、金币或宝石等虚拟货币,为每位用户发放并追踪余额,并通过服务端 API 从服务器读取这些余额。[了解更多](virtual-currencies) - **Apple Ads Manager 中的 AI 助手**:通过对话式 AI 助手查询 Apple Ads 的投放表现,直接获取基于广告活动数据的分析结果,无需手动构建报告。[了解更多](ads-manager-ai-agent) - **Apple Ads Manager 中的新自动化功能**:通过两种新规则类型,在广告系列和广告组级别自动执行更改,与现有的关键词和搜索词自动化功能并列使用。[广告系列规则](ads-manager-automations-campaign-rules) | [广告组规则](ads-manager-automations-ad-group-rules) - **Adapty Mail 中的用户画像**:以单个订阅者为维度的视图,在同一页面展示每位用户的操作历程、当前订阅状态及退订状态。[了解更多](mail-profiles) - **React Native、Flutter、Capacitor 和 Kotlin Multiplatform 的 SDK v4**:支持 Flows 的 v4 SDK 正式发布。React Native、Flutter 和 Capacitor 已全面上线,Kotlin Multiplatform v4 也已发布——每个平台均附有独立的迁移指南。[React Native](migration-to-react-native-sdk-v4) | [Flutter](migration-to-flutter-sdk-v4) | [Capacitor](migration-to-capacitor-sdk-v4) | [Kotlin Multiplatform](migration-to-kmp-sdk-v4) - **新增 Webhook 字段**:Webhook 推送内容现在包含每笔交易的原始价格和折扣信息,方便你在下游追踪促销活动和新用户优惠的定价情况。这些字段仅在 Webhook 中提供。[了解更多](webhook-event-types-and-fields) - **流程中的删除线价格**:直接在付费墙编辑工具中展示带删除线的原始价格及折扣徽章,与折后价并排显示。[了解更多](strikethrough-price) - **流程模板库**:从专业设计的模板开始创建新流程,而无需从空白画布起步,然后根据您的应用进行自定义。[了解更多](paywall-builder-templates) - **安装工具按钮**:每篇文档文章的页眉处现在都有一个 **Install tools** 按钮。点击后会弹出一个模态框,其中包含可直接复制的命令,用于在 Claude Code、Copilot CLI、Gemini CLI、Codex 及其他 AI 编程助手中安装 Adapty SDK 集成技能。[了解更多](adapty-sdk-integration-skill) - **全新 Unity SDK 安装方式**:现在可通过 Swift Package Manager 安装 Unity SDK,并新增了常见配置问题的故障排查指南。[了解更多](sdk-installation-unity) - **Flow Builder 中的底部容器**:一个固定在底部的面板,在页面其余内容滚动时始终保持置顶——非常适合用于 CTA 按钮、法律文本和链接。[了解更多](builder-containers#footer) - **全新流程编辑器视频教程**:YouTube 上持续更新的分步视频播放列表,帮助你从零开始构建流程,现已嵌入各流程编辑器指南中。[了解更多](adapty-flow-builder) ## 2026年6月 \{#june-2026\} - **Flows 现已支持 Android**:用于付费墙和用户引导的可视化无代码构建工具现已支持 Android SDK v4 及以上版本,与 iOS 并行运行。界面原生渲染,无需 Web 视图。[了解更多](adapty-flow-builder) - **Apple Ads Manager 中的 CPP A/B 测试**:在 Apple Ads 中对比不同的自定义产品页面。选择 2 到 4 个页面(包括当前默认页面),Apple Ads 将在这些页面之间轮流分配流量,并报告哪个页面的转化效果最佳。[了解更多](ads-manager-cpp-ab-tests) - **Adapty Mail API**:直接从您的服务器将用户画像和交易数据发送到 Adapty Mail,无需通过 Adapty SDK 传输数据。可用于填充订阅者数据库、复用其他应用中的订阅者,或将您的后端作为数据的唯一可信来源。[了解更多](mail-send-data-via-api) - **在首次启动时展示 Apple Ads 定向付费墙**:Apple Ads 归因数据在 SDK 激活后才会到达,因此过早请求付费墙会导致错过 Apple Ads 目标受众。使用 `AdaptyProfile.appliedAttributionSources` 可在归因数据到达后立即展示 Apple Ads 定向付费墙。[iOS](ios-show-aa-targeted-paywall) | [React Native](react-native-show-aa-targeted-paywall) | [Capacitor](capacitor-show-aa-targeted-paywall) - **Flow 编辑工具自动保存功能**:Flow 编辑工具现在每分钟自动保存一次您的进度,离开页面时不再丢失未保存的内容。您仍可以使用 **Cmd/Ctrl + S** 手动保存草稿。[了解更多](builder-save-publish) - **Flow 编辑工具新视频教程**:新增两个演示视频,分别介绍如何在流程页面间构建导航,以及如何设计选中、激活和禁用等元素状态。[流程中的导航](onboarding-navigation-branching) | [元素状态](builder-element-states) - **日语和越南语文档**:Adapty 文档现已支持日语(日本語)和越南语(Tiếng Việt)。使用顶部导航栏中的语言选择器切换语言。 ## 2026 年 5 月 \{#may-2026\} - **流程(Beta)**:在可视化无代码编辑器中创建完整的页面序列——单屏付费墙、多步骤用户引导以及介于两者之间的任意组合,全部在一个流程中完成。页面以原生方式渲染,无需 Web 视图,且无需发布应用更新即可修改文案、设计和逻辑。目前支持 iOS、Android、React Native、Flutter 以及 Capacitor SDK v4 及以上版本。[了解更多](adapty-flow-builder) - **Autopilot 现在能根据测试结果自动调整**:它作为 AI 增长经理,在每轮测试完成后更新增长计划。下一个假设会基于你已运行的实验、哪些实验胜出、以及哪些方向仍值得探索来制定——而不是按照固定顺序推进。[了解更多](autopilot-how-it-works#how-ai-growth-advisor-decides-what-to-recommend) - **Autopilot 市场洞察中的激活 ARPU**:新增数据图表,将您应用的每次新安装平均收入与品类平均水平进行对比。结合转化漏斗一起使用——高转化率配合低激活 ARPU,可能意味着定价偏低。[了解更多](autopilot-analysis#activation-arpu) - **Adapty Mail 中的数据分析**:在同一视图中对比每个营销活动的投递指标和邮件归因收入。可按营销活动、市场细分、A/B 实验变体、消息或触发器进行分组、细分和筛选,并可下钻至任意行查看详情。[了解更多](mail-analytics) - **Adapty Mail 中的品牌档案**:一个统一的档案,用于驱动邮件文案、语调、视觉效果及网页付费墙内容。Adapty 会从您应用的商店列表、落地页、法律页面和社交主页中构建该档案,您可以在线查看或逐项修改。[了解更多](mail-brand) - **Adapty UA 中的趋势预测**:为每个同期群提供预测收入、ROAS、广告利润、ARPU 和 ARPPU,让你在广告系列成熟之前就能进行对比。趋势预测基于你应用自身的历史同期群数据构建,每日更新,支持从 D0 到 D360 或自定义天数的同期群周期。[了解更多](ua-predicted-metrics) - **Adapty UA 自定义 S3 导出新增字段**:自定义 S3 导出现已包含 `bundle_id`、`device_brand`、`device_model`、`os_version`、`app_version` 和 `sdk_version` 字段。可在下游按设备和应用版本对归因数据进行切片和关联分析。[了解更多](ua-custom-s3) - **CLI 中的版位目标受众**:`adapty placements create` 和 `adapty placements update` 命令现已支持 `--audiences` 参数——一个由 `{segment_ids, paywall_id, priority}` 条目组成的 JSON 数组——让你可以直接在终端为不同市场细分指定不同付费墙。新增的 `adapty paywalls placements` 命令可列出使用指定付费墙的所有版位,方便你在替换前预览影响范围。[了解更多](developer-cli-reference#placements) - **西班牙语文档上线**:Adapty 文档现已提供西班牙语(Español)版本。可通过顶部导航栏的语言切换器进行切换。 ## 2026 年 4 月 \{#april-2026\} - **Adapty Mail**:AI 生成的电子邮件营销活动,帮助将试用用户转化为付费订阅者。在 Adapty 项目中直接构建、发送并追踪活动归因,无需额外的电子邮件平台。[了解更多](adapty-mail) - **付费墙诊断(Autopilot)**:在搭建测试前,先了解付费墙需要优化的地方。上传截图后,Autopilot 会根据同类头部应用的基准数据给出优化建议,并提供 AI 生成的布局和文案方案。其中基于基准数据的建议会作为 A/B 测试轮次加入你的增长计划。[了解更多](autopilot-analysis#paywall-analysis) - **每条 Autopilot 建议都更清晰易懂**:每个假设现在都会说明它为何重要(基于数据的解释,说明您的付费墙与既有模式的偏差)、需要更改什么以及如何设置 A/B 测试,以及在全新的"如何解读结果"部分中需要关注哪些数据图表。[了解更多](autopilot-execute-plan#step-1-view-the-hypothesis) - **保持 Autopilot 增长计划最新状态**:刷新分析以获取最新市场数据和新建议,如果新建议不符合预期,可从版本历史记录中查看过往建议。假设按以下标签分组:Top priority(最高优先级)、All(全部)、Pricing(定价)、Visual(视觉)、Geo-pricing(地区定价)和 Archived(已归档)。[了解更多](autopilot-growth-plan) - **Autopilot 中按时长划分的收入分布**:查看您的收入是否过度集中于某一订阅时长。全新的市场洞察数据图表会显示您按时长划分的收入构成,并附带您所在品类和国家的行业平均值。[了解更多](autopilot-analysis#revenue-distribution-by-duration) - **更新后的 LTV 与收入趋势预测**:预测 LTV 和收入现在会优先使用您应用自身的同期群留存数据(历史数据充足时),不足时则回退到跨应用平均值——这样即使是较新的应用,也能在数据分析和 A/B 测试中获得可用的预测结果。[了解更多](predicted-ltv-and-revenue) - **在 Adapty UA 中发送所有事件**:为 Meta 和 TikTok 提供更完整的转化数据,从而优化受众建模。Adapty 现在支持将来自自然流量和未归因用户的安装及交易数据转发到你的像素,而不仅限于匹配到广告系列的用户。[Meta](ua-facebook#send-all-events) | [TikTok](ua-tiktok#send-all-events) - **文档支持俄语和土耳其语**:Adapty 文档现已提供俄语(Русский)和土耳其语(Türkçe)版本。请使用顶部导航栏中的语言切换器来切换语言。 ## 2026 年 3 月 \{#march-2026\} - **开发者 CLI**:无需打开看板,直接在终端管理 Adapty 账号。CLI 支持创建应用、定义访问等级、配置产品、创建付费墙和配置版位——全程可脚本化,适合自动化环境使用。此外还提供 [Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli),可帮助 AI 编程助手使用该 CLI。[了解更多](developer-cli) - **Apple Ads Manager 概览页面**:在同一页面查看所有关键 Apple Ads 数据图表,每项指标均附有趋势数据图表。通过页头下拉菜单按应用筛选,自定义显示的指标,并调整图表类型和收入展示方式。[了解更多](ads-manager-overview) - **Apple Ads Manager 中的市场情报**:查看竞争对手在 50 多个国家/地区投放广告的关键词,并将表现最佳的关键词直接添加到您的广告系列中。[了解更多](ads-manager-market-intelligence) - **Apple Ads Manager 中的全周期关键词自动化**:根据您定义的效果规则,自动调整出价、暂停或激活关键词,并在广告组之间移动关键词。[了解更多](ads-manager-automations-keyword-rules) - **Apple Ads Manager 中的出价历史记录**:查看任意关键词 CPT 出价的完整变更日志——包括每次变更的时间、变更前后的值,以及触发变更的自动化规则。[了解更多](ads-manager-manage-keywords#bid-history) - **Autopilot 中的视觉轮次**:付费墙设计建议现在已成为增长计划中的一等轮次——与变现轮次并列显示在侧边栏中。每个视觉轮次包含设计原型、最佳应用场景说明以及所针对的关键数据图表。[了解更多](autopilot-growth-plan#view-the-growth-plan) - **向 Autopilot 添加自定义假设**:通过自定义轮次扩展您的增长计划。添加标题、描述、轮次类型(货币化或视觉),设置目标指标——对于货币化轮次,还需指定所涉及的产品。[了解更多](autopilot-growth-plan#add-your-own-hypothesis) - **重新排列 Autopilot 轮次**:拖拽重新排列增长计划各阶段的顺序,按照最适合您策略的顺序运行实验。[了解更多](autopilot) - **Autopilot 中的地域定价轮次**:在增长计划中将特定国家的价格调整作为一种新型轮次进行测试。Autopilot 基于市场洞察数据,推荐每个国家应提价、降价还是维持现价。将推荐方案添加为地域定价轮次,即可以 A/B 测试形式运行——最多可同时运行 5 个。[了解更多](autopilot-growth-plan#geo-pricing-hypotheses) - **Apple Ads Manager 中的搜索词自动化**:自动将表现优异的搜索词提升为精确匹配关键词,并在来源处将其排除——无需手动下载报告。规则可以从模板创建,也可以通过自定义条件和计划从头构建。[了解更多](ads-manager-automations-search-terms) - **Apple Ads Manager 中的最大化转化出价策略**:创建广告系列时,您现在可以选择最大化转化作为出价策略。Apple 的算法会在您的预算范围内最大化下载量,并可设置可选的目标 CPA 进行指导。[了解更多](ads-manager-create-campaign) - **Adapty UA 中的 FunnelFox 集成**:Adapty UA 现已支持与 FunnelFox 的全新集成。[FunnelFox](ua-funnelfox) - **中文文档**:Adapty 文档现已提供中文版本。使用顶部导航栏中的语言选择器切换语言。 ## 2026 年 2 月 \{#february-2026\} - **按国家设置产品定价**:直接在 Adapty 看板中为各国设置不同的价格 —— Adapty 会自动将更改同步至 App Store Connect 和 Google Play。每次定价更新都会记录在审计日志中,确保每一次变更都有迹可查。[了解更多](edit-product) - **Autopilot 中的国家级竞品定价**:在主要市场中将您的订阅价格与竞品进行对比分析。[了解更多](autopilot-analysis#market-and-competitor-analysis) - **用户引导版本控制**:通过完整的版本历史记录追踪和管理用户引导的版本,随时查看变更并在需要时回滚。 - **分析中的付费墙转化数据图表**:两个新的转化数据图表——付费墙浏览 → 试用和付费墙浏览 → 付费——直观展示付费墙将浏览者转化为订阅用户的效果。[了解更多](analytics-conversion) - **复制市场细分**:复制现有市场细分及其所有筛选条件,无需从头重建类似的细分。适合同时运行多个营销活动或目标受众有重叠的 A/B 测试时使用。[了解更多](segments#duplicate-segments) - **Adapty 移动应用中的推送通知**:直接在 Adapty iOS 应用中为 14 种事件类型配置推送通知,无需打开看板即可随时掌握订阅动态。[了解更多](push-notifications) - **Kotlin Multiplatform SDK 3.15**:新增用户引导支持、网页付费墙及 API 改进。[了解更多](migration-to-kmp-315) - **Capacitor SDK 3.16**:新增 Capacitor 8 支持。使用 Capacitor 7 的项目请继续使用 SDK v3.15。[了解更多](migration-to-capacitor-316) - **LLM 辅助 SDK 集成指南**:借助 AI 编码助手完成 Adapty 集成的分步指南。每份指南引导你的 LLM 完成从看板配置到购买的完整实现流程。[iOS](adapty-cursor) | [Android](adapty-cursor-android) | [React Native](adapty-cursor-react-native) | [Flutter](adapty-cursor-flutter) | [Unity](adapty-cursor-unity) | [Kotlin Multiplatform](adapty-cursor-kmp) | [Capacitor](adapty-cursor-capacitor)。如需一键自动化流程,欢迎体验全新 **adapty-sdk-integration skill**(测试版):[iOS](adapty-sdk-integration-skill) | [Android](adapty-sdk-integration-skill-android) | [React Native](adapty-sdk-integration-skill-react-native) | [Flutter](adapty-sdk-integration-skill-flutter) | [Unity](adapty-sdk-integration-skill-unity) | [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) | [Capacitor](adapty-sdk-integration-skill-capacitor) ## 2026 年 1 月 \{#january-2026\} - **Capacitor SDK 正式发布**:经过大量测试,Capacitor SDK 现已可用于生产环境。借助完整的 Adapty 集成支持,使用 Capacitor 为 iOS 和 Android 构建订阅类应用。[了解更多](capacitor-sdk-overview) - **新应用的 Autopilot 功能**:即使你的应用尚无大量交易记录,现在也可以使用 Autopilot 分析功能。从第一天起就获取数据驱动的价格优化建议,制定增长计划。[了解更多](autopilot) - **Autopilot 全球定价机会**:通过针对特定国家的定价建议,挖掘核心市场的收入潜力。Autopilot 会分析您下一批前 5 个国家的转化率和购买力,基于 Adapty 定价指数提供数据驱动的建议,帮助您判断是否应提高、降低或维持现有价格。[了解更多](autopilot) - **账单恢复转化数据图表**:新增数据图表,用于追踪从账单问题和宽限期中恢复的收入。通过监控"Billing issue converted"、"Billing issue converted revenue"、"Grace period converted"和"Grace period converted revenue",衡量您的留存恢复效果。 - **在 Apple Ads Manager 中直接管理广告**:无需在平台之间切换,直接在 Adapty 中创建和管理 Apple Ads 广告活动。[了解更多](ads-manager-manage-ads) - **Apple Ads Manager 数据分析**:在 Adapty 中查看详细的广告级别效果指标和归因数据,在统一看板中浏览推广活动表现、广告组数据分析及归因洞察。[了解更多](adapty-ads-manager-analytics) - **Apple Ads 归因数据图表**:将多个归因指标组合成可自定义的数据图表,结合订阅数据全面分析 Apple Ads 的投放效果。[了解更多](adapty-ads-manager-analytics#charts) - **Apple Ads 归因市场细分**:通过简化的两步操作,基于 Apple Ads 归因数据创建用户市场细分。按广告系列、广告组或关键词定向用户,实现更精准的分析与实验。[了解更多](ads-manager-create-segments) - **全新文档平台**:文档站点已迁移至全新平台,带来更快的功能迭代,并通过增强的搜索、导航和内容组织提升用户体验。 ## 2025年12月 \{#december-2025\} - **Apple Ads Manager 文档**:在统一的分析看板中整合 Apple Search Ads 广告活动数据与收入指标。新文档涵盖广告活动创建、广告组管理,以及如何结合订阅表现追踪广告支出的投资回报率。[了解更多](ads-manager) - **应用内网页付费墙**:通过应用内浏览器在应用中展示基于网页的付费墙,无需外部跳转,带来流畅的用户体验。[iOS](ios-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Android](android-web-paywall#open-web-paywalls-in-an-in-app-browser) | [React Native](react-native-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Flutter](flutter-web-paywall#open-web-paywalls-in-an-in-app-browser) - **滚动市场细分**:创建动态目标受众市场细分,根据移动时间窗口自动更新。例如,创建一个"过去 7 天内安装应用的用户"细分,持续刷新以始终显示最新客户。[了解更多](segments#available-attributes) - **Meta 和 TikTok 广告系列设置指南**:在 Meta(Facebook & Instagram)和 TikTok 上创建和追踪广告系列的分步文档,包含转化追踪和数据分析集成。[Meta](meta-create-campaign) | [TikTok](tiktok-create-campaign) - **手动付费墙实现快速入门指南**:通过分步快速入门指南,了解如何将 Adapty SDK 集成到自定义付费墙 UI 中,从而更快地实现应用内购买。[iOS](ios-implement-paywalls-manually) | [Android](android-implement-paywalls-manually) | [React Native](react-native-implement-paywalls-manually) | [Flutter](flutter-implement-paywalls-manually) | [Unity](unity-implement-paywalls-manually) | [Kotlin Multiplatform](kmp-quickstart-manual) | [Capacitor](capacitor-quickstart-manual) - **用户引导链接的应用内浏览器**:用户引导中的外部链接现在默认在应用内浏览器中打开,让用户留在您的应用中。如有需要,您也可以自定义此行为,改用外部浏览器。[iOS](ios-present-onboardings#customize-how-links-open-in-onboardings) | [Android](android-present-onboardings#customize-how-links-open-in-onboardings) | [React Native](react-native-present-onboardings#customize-how-links-open-in-onboardings) - **改进的 Autopilot 建议**:Autopilot 现在基于对订阅数据的深入分析,提供更优质的价格优化建议。[立即体验 Autopilot](autopilot) - **文档深色模式**:文档现已支持深色模式,可自动检测系统偏好设置,也可通过右上角的手动开关切换。 --- # File: adapty-ecosystem --- --- title: "Adapty 生态系统" description: "Adapty 是面向移动应用的应用内购买平台。了解各产品的功能及其相互关联。" --- Adapty 是面向移动应用的应用内购买平台,围绕一个核心使命而生:让应用创造更多收益。它为你提供增长营收所需的一切:获取用户、促成转化、留住订阅者,并赢回流失用户。 注册后即可立即访问完整的 Adapty 生态系统。只需点击 Adapty 徽标即可在各产品之间切换: - **Core** — 无需直接操作 StoreKit 或 Google Play Billing 即可处理购买,使用无代码工具设计付费墙,并实时追踪收入。其他产品均以此为基础构建。 - **Adapty Ads Manager** — 投放和优化 Apple Ads 广告,并以真实的订阅收入数据衡量效果。 - **Adapty Attribution** — 直观了解哪些广告渠道真正带来了收入,无需 MMP。 - **Adapty Mail** — 通过自动化邮件转化试用用户,并赢回流失用户。 还有两款产品与核心四款并列:**FunnelFox**(网页到应用的转化漏斗及托管结账)和 **Adapty Finance**(基于未来订阅收入的预付融资)。 ## 产品如何协同工作 \{#how-the-products-fit-together\} 每个产品在用户生命周期的不同节点发挥作用。将鼠标悬停在任意链接功能上可查看简要定义,或点击跳转至对应文档。 <ProductMap /> :::link 另请参阅:[Adapty 适合我吗?](is-adapty-right-for-me) ::: ## 专为 AI 工作流而生 \{#built-for-ai-workflows\} 在 AI 编程助手中直接使用 Adapty——无需离开编辑器即可完成集成、管理和查阅。将助手指向适合你平台的 [SDK 集成技能](adapty-sdk-integration-skill),一条指令即可完成全部配置;或者参考[分步 LLM 指南](adapty-cursor),逐步审阅每个环节。 您的 AI 工具可以通过任何合适的方式获取文档。点击 **Copy for LLM** 按钮将任意页面复制为 Markdown 格式,或者将工具指向 [`llms.txt`](https://adapty.io/docs/zh/llms.txt)——这是整个文档集的索引。如需实时访问,[Context7](https://context7.com/adaptyteam/adapty-docs) MCP 服务器可在 Cursor、Claude Code 及其他 IDE 中直接提取文档中最相关的代码片段。所有入口详见[使用 AI 管理 Adapty](manage-adapty-with-ai)。 ## 核心功能 \{#core\} Core 是 Adapty 的基础平台。它负责展示付费墙、管理购买行为,并追踪后续发生的一切。 ### SDK 与商店 \{#sdks-and-stores\} 跳过繁琐的计费接入。Adapty 帮你处理购买、收据验证和续订,并实时更新每位订阅者的状态——让你随时掌握谁有访问权限、原因是什么。 在您的应用内,[移动端 SDK](installation-of-adapty-sdks) 可全程处理购买流程,或在您已有计费系统的情况下[接入现有计费逻辑](observer-vs-full-mode)。支持 7 个平台:[iOS](ios-sdk-overview)、[Android](android-sdk-overview)、[React Native](react-native-sdk-overview)、[Flutter](flutter-sdk-overview)、[Unity](unity-sdk-overview)、[Kotlin Multiplatform](kmp-sdk-overview) 和 [Capacitor](capacitor-sdk-overview)。 在服务端,Adapty 直接对接各大应用商店,因此每一次续订、退款和账单问题都能实时同步到你这里——即使应用已关闭也不例外。支持的平台包括:[App Store](initial_ios)、[Google Play](initial-android)、[Stripe](stripe) 和 [Paddle](paddle),此外还提供[自定义集成](custom-store)方案,可对接任意其他支付服务商。 ### 产品、优惠与访问等级 \{#products-offers-and-access-levels\} Adapty 将你销售的内容与用户解锁的权限分开管理。正因为这种分离,你可以重新定价、替换产品或推出优惠活动,而无需发布应用更新。其中有三个核心概念: - **[产品](product)** — 一个产品可以统一您在 App Store、Play Store 和网页端的 SKU——订阅、一次性购买或消耗型商品——让您在一处管理完整的产品目录。 - **[优惠](offers)** — 提升转化率的关键工具:新用户优惠、促销活动和赢回优惠折扣。 - **[访问等级](access-level)** — 让您将访问权限与具体产品解耦。 ### 流程与版位 \{#flows-and-placements\} 无需编写代码,即可构建驱动收入的各类屏幕——付费墙、用户引导、问卷调查。[流程](adapty-flow-builder)通过 SDK 在设备端渲染,让你随时修改文案、设计和定价,无需发版。你可以从[模板](paywall-builder-templates)开始,也可以从空白画布开始。将每个流程绑定到一个[版位](placements),然后针对由[市场细分](segments)构建的不同[目标受众](audience)进行定向投放。 ### 用户画像与市场细分 \{#profiles-and-segments\} 查看任意订阅者的完整历史记录——[用户画像](profiles-crm)保存每位用户的事件时间线、订阅状态、收入及自定义属性。通过[市场细分](segments)按任意属性对用户进行分组,从而个性化用户所见内容、筛选分析数据并限定 A/B 测试范围。[事件动态](event-feed)实时推送每一条订阅事件。 ### A/B 测试与 AI 增长顾问 \{#ab-tests-and-ai-growth-advisor\} 通过找到最佳转化方案来提升收入: - **[A/B 测试](ab-tests)** — 测试不同的价格、试用时长和流程设计。 - **[AI 增长顾问](autopilot)** — 精准告诉你下一步该做哪个 A/B 测试。将你的付费墙与 20,000+ 订阅应用进行基准对比,并按预期收入提升幅度排列实验优先级。了解[工作原理](autopilot-how-it-works)。 ### 分析与趋势预测 \{#analytics-and-predictions\} [Analytics(分析)](analytics)将应用商店、SDK 和归因数据整合成一个实时收入看板,提供[数十种数据图表](metric-comparison-table)——远超 App Store 和 Google Play 自带的统计功能。你还可以进行更深入的分析,包括[同期群](analytics-cohorts)、[漏斗](analytics-funnels)、[留存](analytics-retention)和[转化](analytics-conversion)分析。[趋势预测](predicted-ltv-and-revenue)能提前数月预测各同期群的 LTV,并在 [A/B 测试](predictions-in-ab-tests)达到统计显著性之前就识别出优胜实验变体。定期[报告](reports)会定时发送到你的邮箱。 ### 开发者 CLI \{#developer-cli\} [Adapty 开发者 CLI](developer-cli-quickstart) 可通过命令行配置产品、版位和访问等级,是喜欢终端操作的开发者在看板之外的另一种选择。 ## Adapty Ads Manager \{#adapty-ads-manager\} [Adapty Ads Manager](adapty-ads-manager) 是一个 Apple Ads 平台,以 AI 驱动的优化、实时收入归因和竞品情报取代了原生 Apple Ads 控制台。由于 Core 已经追踪每一次安装、试用、订阅和续费,Ads Manager 可将广告支出直接与 LTV 关联,无需 MMP。 核心功能: - **[广告系列与关键词](ads-manager)** — 直接在 Adapty 看板中创建和管理广告系列、广告组及出价。 - **[AI 智能助手](ads-manager-ai-agent)** — 用自然语言进行全链路查询并获取优化建议。 - **[市场情报](ads-manager-market-intelligence)** — 覆盖 50 多个国家/地区的竞品关键词策略分析。 - **[CPP A/B 测试](ads-manager-cpp-ab-tests)** — 自定义产品页面的对比测试。 - **[自动化](ads-manager-automations)** — 让广告系列自我优化。当数据图表达到设定阈值时,出价、关键词和搜索词将自动调整。 ## Adapty 归因 \{#adapty-attribution\} [Adapty 归因](adapty-user-acquisition) 将应用安装和订阅收入与带来它们的广告活动相关联。它将广告平台消耗、追踪链接点击和 SDK 安装事件整合成各付费渠道的 ROAS、LTV 和同期群视图,无需借助外部 MMP。 主要功能: - **[广告平台集成](ua-integrations)** — Meta Ads、TikTok for Business、FunnelFox 以及 S3/GCS 管道。 - **[追踪链接](ua-tracking-links)** — 在 Adapty 中生成,添加到广告活动中,并在首次启动时与安装记录进行匹配。 - **[延迟深度链接](ua-deferred-data)** — 即使用户在安装前就点击了链接,也能在首次启动时将新用户引导至正确的应用内容。 - **[归因数据](ua-attribution-data)** — 在应用中接收归因数据载荷,用于自定义逻辑。 ## Adapty Mail \{#adapty-mail\} [Adapty Mail](adapty-mail) 可将用户数据转化为 AI 生成的邮件营销活动。它从你的应用商店列表、落地页和社交主页中构建[品牌档案](mail-brand),并在几分钟内生成完整的邮件序列。邮件从你已验证的域名发出,每笔购买都会归因到促成它的那封邮件。无需单独的邮件平台。 主要功能: - **[营销活动](mail-email-campaigns)** — 完整的多封邮件序列,一次性生成。将其关联到流程后即开始发送。 - **[流程](mail-flows)** — 将营销活动绑定到某个市场细分和订阅事件触发器(如*从未购买*或*账单问题*),实现自动发送。 - **[Web 付费墙](mail-checkout)** — 为每位收件人生成个性化结账页面,确保购买行为能归因到对应邮件。 - **[市场细分](mail-segments)**和**[用户画像](mail-profiles)** — 精准触达最有可能转化的用户。根据购买状态、所在国家或收入构建细分,然后基于此触发营销活动或流程。数据来源于 Adapty Core,仅限已识别且包含邮件地址的用户画像。 ## 将 Adapty 接入现有技术栈 \{#connect-adapty-to-your-existing-stack\} [第三方集成](configuration)将订阅[事件](events)转发至团队已在使用的数据分析、归因和消息推送平台: - **分析工具**: [Amplitude](amplitude)、[Mixpanel](mixpanel)、[PostHog](posthog)、[Firebase / Google Analytics](firebase-and-google-analytics)、[AppMetrica](appmetrica)、[SplitMetrics Acquire](splitmetrics)。 - **归因**: [AppsFlyer](appsflyer)、[Adjust](adjust)、[Branch](branch)、[Airbridge](airbridge)、[Apple Ads](apple-search-ads)、[Singular](singular)、[Tenjin](tenjin)、[Asapty](asapty)、[Facebook Ads](facebook-ads)。 - **消息推送**: [Braze](braze)、[OneSignal](onesignal)、[Pushwoosh](pushwoosh)、[Slack](slack)。 - **Webhook 与 ETL**: 自定义 [Webhook](webhook)、[Amazon S3](s3-exports)、[Google Cloud Storage](google-cloud-storage)。 ## 更广泛的生态系统 \{#the-wider-ecosystem\} 另有两款产品与你的 Adapty 数据相连,但服务于核心生命周期之外的需求。 ### FunnelFox [FunnelFox](https://funnelfox.com/docs/integrations/subscription-management/adapty) 是一款网页到应用的漏斗构建工具,可创建落地页和问卷来引导用户进入你的应用,并通过其[计费](https://funnelfox.com/docs/billing/integration-billing-funnelfox)引擎在网页端完成支付。将 FunnelFox 连接到 Adapty,即可实现订阅跟踪与收入归因。 ### Adapty Finance [Adapty Finance](https://adapty.io/blog/introducing-adapty-finance/) 可预支你未来的订阅收入,让你无需等待商店结算。 ## 后续步骤 \{#next-steps\} - **[Adapty 适合我吗?](is-adapty-right-for-me)** — 以使用场景为导向的平台功能介绍。 - **[快速入门指南](quickstart)** — 连接应用商店、添加产品并集成 SDK。 - **[用 AI 管理 Adapty](manage-adapty-with-ai)** — 使用 AI 编程工具操作 Adapty 的所有入口。 --- # File: generate-in-app-purchase-key --- --- title: "在 App Store Connect 中生成应用内购买密钥" description: "生成用于安全交易的应用内购买密钥。" --- **应用内购买密钥**是在 App Store Connect 中创建的一种专用 API 密钥,用于通过确认购买的真实性来验证购买行为。 :::note 要为 App Store Server API 生成 API 密钥,您必须在 App Store Connect 中持有管理员角色或账户持有人角色。您也可以在 [Apple 开发者文档](https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests)中了解如何生成 API 密钥。 ::: 1. 打开 **App Store Connect**。前往 [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs) 部分。 2. 然后点击 **Active** 标题旁边的添加按钮 **(+)**。 <img src="/assets/shared/img/6d737db-generate_in-app_key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在打开的 **Generate In-App Purchase Key** 窗口中,输入密钥名称以便日后参考。该名称不会在 Adapty 中使用。 4. 点击 **Generate** 按钮。**Generate in-App Purchase Key** 窗口关闭后,您将在 **Active** 列表中看到已创建的密钥。 <img src="/assets/shared/img/fac066b-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 生成 API 密钥后,点击 **Download In-App Purchase Key** 按钮,将密钥以文件形式下载。 <img src="/assets/shared/img/d59faff-download_in-app_purchase_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 在 **Download in-App Purchase Key** 窗口中,点击 **Download** 按钮。文件将保存到您的计算机。 请务必妥善保管此文件,以便日后上传到 Adapty 看板。请注意,生成的文件只能下载一次,因此在上传之前请确保安全存储。从 **In-App Purchase section** 生成的 .p8 密钥将在[配置 Adapty 与 App Store 的初始集成](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file)时使用。 **下一步:** - [配置 App Store 集成](app-store-connection-configuration) --- # File: app-store-connection-configuration --- --- title: "配置 App Store 集成" description: "配置您的 App Store 连接,实现无缝的订阅追踪。" --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/VJQbzoTCkqs?si=l7BPX9mIu6GVGZ0Z" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 本节介绍如何为您的 iOS 应用建立 App Store 与 Adapty 之间的连接。这是我们显示订阅数据图表和验证购买所必需的步骤。您可以在初始用户引导期间完成集成,也可以稍后在 Adapty 看板的 **App Settings** 中进行配置。 虽然您可能在用户引导期间已完成移动应用与 Adapty 的集成配置,但您仍可以随时在 **App settings** 中修改这些设置。 :::danger 在沙盒阶段,只要你的移动应用尚未正式上线,可以安全地进行配置更改。应用发布后再做更改可能会破坏应用内的购买流程。 ::: ## 步骤 1. 填写 Bundle ID 和 Apple app ID \{#step-1-provide-bundle-id-and-apple-app-id\} **Bundle ID** 和 **Apple app ID** 均为必填项。**Bundle ID** 是您的应用在 App Store 中的唯一标识符,它是订阅处理等 Adapty 核心功能的基础。**Apple app ID** 也是必须填写的,这样才能在 **Products** 页面[创建新产品并推送到商店](create-product#create-product-and-push-to-store)。 :::note 若未填写 **Apple app ID**,**Products** 页面上的 **Create a new product and push to stores** 选项将被禁用,且看板不会给出任何提示说明原因。 ::: 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 第二步:提供 Issuer ID 和 Key ID \{#step-2-provide-issuer-id-and-key-id\} **In-app purchase Issuer ID**(在 App Store Connect 中称为 **Issuer ID**)是一个特殊 ID,用于标识创建身份验证令牌的颁发者。**In-App Purchase Key ID**(在 App Store Connect 中称为 **Key ID**)是与您在[在 App Store Connect 中生成应用内购买密钥](generate-in-app-purchase-key)部分生成的加密密钥关联的唯一标识符。 1. 打开 **App Store Connect**。前往 [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs) 页面。 2. 在 **Active** 列表中,找到你在 [在 App Store Connect 中生成应用内购买密钥](generate-in-app-purchase-key) 章节里创建的密钥。 <img src="/assets/shared/img/19a2868-issuer_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 复制 **Issuer ID**,并将其粘贴到 Adapty 看板中的 **In-app purchase Issuer ID** 字段。 <img src="/assets/shared/img/c2b42e7-issuer_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 复制 **Key ID**,并将其粘贴到 Adapty 看板的 **In-app purchase Key ID** 字段中。 ## 步骤 3. 上传应用内购买密钥文件 \{#step-3-upload-in-app-purchase-key-file\} 将你在[在 App Store Connect 中生成应用内购买密钥](generate-in-app-purchase-key)章节中下载的 **In-App Purchase Key** 文件 <img src="/assets/shared/img/88cdfff-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 上传到 Adapty 看板中的 **Private key (.p8 file)** 字段。 <img src="/assets/shared/img/253b840-in-app_file_upload.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第四步:针对试用期和特殊优惠——配置促销活动 \{#step-4-for-trials-and-special-offers--set-up-promotional-offers\} :::important 如果你的应用包含[试用期或其他促销活动](offers),此步骤为必填项。 ::: 1. 将你在[第二步](#step-2-provide-issuer-id-and-key-id)中使用的同一个 Key ID 复制到 **App Store promotional offers** 部分的 **Subscription key ID** 字段中。 2. 将你在[第三步](#step-3-upload-in-app-purchase-key-file)中使用的同一个 **In-App Purchase Key** 文件上传到 **App Store promotional offers** 部分的 **Subscription key (.p8 file)** 区域。 <img src="/assets/shared/img/promo-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 5 步:输入 App Store 共享密钥 \{#step-5-enter-app-store-shared-secret\} **App Store shared secret**(即 App Store Connect Shared Secret)是一个 32 位十六进制字符串,用于应用内购买和订阅收据验证。 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps),选择您的应用,进入 **General** → **App Information** 页面。 2. 向下滚动,找到 **App-Specific Shared Secret** 子板块。 <img src="/assets/shared/img/2bd112a-shared_secret_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::info 如果 **App-Specific Shared Secret** 子部分未显示,请确认您拥有 Account Holder 或 Admin 角色。如果您已具有 Admin 角色但仍看不到 **App-Specific Shared Secret** 子部分,请联系该应用的 Account Holder(即在 App Store Connect 中创建该应用的人),让其为该应用生成 App Store shared secret。生成后,Admin 也可以看到该子部分。 ::: 3. 点击 **Manage** 按钮。 <img src="/assets/shared/img/2d8b4c0-shared_secret_apple_copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在打开的 **App-Specific Shared Secret** 窗口中,复制 **Shared Secret**。如果没有看到共享密钥,请先点击可用的 **Manage** 或 **Generate** 按钮,然后再复制 **Shared Secret**。 5. 将复制的 **Shared Secret** 粘贴到 Adapty 看板中的 **App Store shared secret** 字段。 <img src="/assets/shared/img/4f9624d-shared_secret.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 点击 Adapty 看板中的 **Save** 按钮确认更改。 ## 第六步:添加 App Store Connect API 密钥 \{#step-6-add-app-store-connect-api-key\} 生成 App Store Connect API 密钥并添加到 Adapty,即可[在 Adapty 看板中管理 App Store 产品](create-product#create-product-and-push-to-store): 1. 在 App Store Connect 中,前往 [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api),点击 **+**。 <img src="/assets/shared/img/app-store-connect-api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **Generate API key window** 中,为密钥输入名称并授予其 **Admin** 权限。 <img src="/assets/shared/img/generate-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击密钥旁边的 **Download**。请注意,该密钥只能下载一次。 <img src="/assets/shared/img/download-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 Adapty 看板中,前往 [**App settings > iOS SDK**](https://app.adapty.io/settings/ios-sdk),然后点击 **Connect API key**。 <img src="/assets/shared/img/connect-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在弹窗中填写以下字段: - **Issuer ID**:从 [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api) 复制。它位于 **API keys** 表格上方。 <img src="/assets/shared/img/issuer-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Key ID**:从 [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api) 复制。它在 **API keys** 表格中,位于您的密钥旁边。 <img src="/assets/shared/img/key-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **API key**:上传你从 App Store Connect 下载的 API 密钥文件。 <img src="/assets/shared/img/app-store-connect-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 点击 **Connect**。 **下一步** - [启用 App Store 服务器通知](enable-app-store-server-notifications) --- # File: enable-app-store-server-notifications --- --- title: "启用 App Store 服务器通知" description: "启用 App Store 服务器通知,实时追踪订阅事件。" --- 设置 App Store 服务器通知对于确保数据准确性至关重要,它能让你即时接收来自 App Store 的更新,包括退款及其他事件信息。 :::important 完整支持 App Store Server Notifications V2 需要 Adapty iOS SDK 2.10.0 或更高版本。 ::: 1. 在 Adapty 看板中复制 **URL for App Store server notification**。 <img src="/assets/shared/img/2901185-app_server_notifications.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择您的应用,进入 **General** → **App Information** 部分下的 **App Store Server Notifications** 子部分。 3. 将复制的 **URL for App Store server notification** 粘贴到 **Production Server URL** 和 **Sandbox Server URL** 字段中。 <img src="/assets/shared/img/86fb3d2-app_server_notifications_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 原始事件转发 \{#raw-events-forwarding\} 有时,您可能仍希望直接接收来自 Apple 的原始 S2S 事件。若要在使用 Adapty 的同时继续接收这些事件,只需将您的端点添加到 **URL for forwarding raw Apple events** 字段中,我们将原封不动地转发来自 Apple 的原始事件。 <img src="/assets/shared/img/e9f4bba-CleanShot_2021-03-16_at_19.30.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **下一步** 为以下平台配置 Adapty SDK: - [iOS](sdk-installation-ios) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: troubleshoot-app-store-integration --- --- title: "排查 App Store 集成问题" description: "解决常见的 Apple App Store 配置问题——协议待处理、服务器通知延迟以及价格不匹配等情况。" --- 本文介绍常见的 App Store 集成问题,每个部分均包含症状描述、根本原因及解决方案。 ## 产品未显示 \{#products-dont-appear\} 以下两种表现通常指向同一个根本原因: - App Store Connect API 密钥配置正确,但 Adapty 无法获取任何产品。 - 产品已在 App Store Connect 中创建,但未出现在 Adapty 中,或显示数量少于预期。SDK 在尝试购买时报告 "Product Id not found"。 最常见的根本原因是 **Apple 协议未签署** — 付款协议、税务表格或银行表格处于待处理或未签署状态。当协议处于待处理状态时,App Store Connect API 会在产品相关端点静默返回 403 错误。Adapty 不会收到任何明确的报错提示,产品会被静默过滤掉。 请前往 **App Store Connect → Agreements, Tax, and Banking**,签署所有待处理的协议。然后在 Adapty 的 **App settings → iOS SDK** 中重新同步。 ## App Store 服务器通知显示"Delayed" \{#app-store-server-notifications-show-delayed\} 在 App Store Connect 中,App Store 服务器通知的状态可能会显示为 **Delayed**。这意味着 Apple 在发送订阅事件通知方面出现了延迟——续订、取消和账单问题等通知会排队等待,并延迟到达。 安装统计数据不受影响。Adapty 从应用首次启动开始统计安装量,而非依赖服务器端通知。 如果续订或取消数据出现滞后,**Delayed** 状态是最可能的原因。随着 Apple 处理积压的通知,该状态通常会自动恢复正常。 ## Adapty 中的价格与 App Store 不匹配 \{#prices-in-adapty-dont-match-app-store\} Adapty 产品编辑页面上的**价格**字段的行为方式取决于产品的添加方式。 如果你在 Adapty 中创建产品并从看板推送到商店,该价格将作为商店的初始价格使用。 如果你添加的产品在商店中已存在,此价格仅作为占位符使用。Adapty 的分析、集成和 SDK 均以从 App Store 实际获取的价格为准,而非该占位符。App Store 价格发生变更后不会同步更新占位符,且目前无法在看板中手动编辑占位符。 ## CSV 价格导出为空 \{#csv-price-export-is-empty\} 如果你导出的 CSV 价格文件只有列标题,说明 App Store Connect API 密钥未完成配置。请参阅[第 6 步 — 添加 App Store Connect API 密钥](app-store-connection-configuration#step-6-add-app-store-connect-api-key)。 ## 无法将新产品推送至 App Store \{#cant-push-new-products-to-app-store\} 当你在看板中创建产品时,Adapty 可以将新产品推送至 App Store Connect。如果你的 App Store 集成尚未完整配置,推送选项将被禁用。以下两项设置为必填项: - **Apple app ID**:在 [第 1 步 — 提供 Bundle ID 和 Apple app ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id) 中进行配置。 - **App Store Connect API 密钥**:在 [第 6 步 — 添加 App Store Connect API 密钥](app-store-connection-configuration#step-6-add-app-store-connect-api-key) 中进行配置。 --- # File: enabling-of-devepoler-api --- --- title: "在 Google Play Console 中启用开发者 API" description: "启用 Adapty 的开发者 API,以便在您的应用中自动化并简化订阅管理。" --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/7dN50n5bcLc?si=c2znttIb--4VcrRO" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 如果您的移动应用已在 Play Store 上架,启用开发者 API 对于将其与 Adapty 集成至关重要。此步骤可确保您的应用与我们平台之间的无缝通信,从而支持自动化流程和实时数据分析,以优化您的订阅模式。以下 API 需要启用: - [Google Play Android Developer API](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com) - [Google Play Developer Reporting API](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com) - [Cloud Pub/Sub API](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com) 如果您的应用不通过 Play Store 分发,可以跳过此步骤。但如果您确实通过 Play Store 销售,可以暂时推迟此步骤,不过它对 Adapty 的基本功能至关重要。完成用户引导流程后,您可以在 **App settings** 部分配置应用商店设置。 以下是在 Google Play Console 中启用开发者 API 的步骤: 1. 打开 [Google Cloud Console](https://console.cloud.google.com/)。 2. 在 Google Cloud 窗口的左上角,选择您希望使用的项目或创建一个新项目。请确保在将服务账号密钥文件上传到 Adapty 之前,始终使用同一个 Google Cloud 项目。 <img src="/assets/shared/img/fd66a11-google_cloud_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 打开 [**Google Play Android Developer API**](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com) 页面。 <img src="/assets/shared/img/f754f72-google_play_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Enable** 按钮,等待状态显示为 **Enabled**。这表示 Google Android Developer API 已启用。 <img src="/assets/shared/img/d47ed14-google_play_api_create_credentials.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 打开 [**Google Play Developer Reporting API**](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com) 页面。 <img src="/assets/shared/img/966cf73-Google_play_developer_reporting_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 点击 **Enable** 按钮,等待状态显示为 **Enabled**。 <img src="/assets/shared/img/e776d77-Google_play_developer_reporting_api_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 打开 [**Cloud Pub/Sub API**](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com) 页面。 <img src="/assets/shared/img/b13f609-enable_Cloud_Pub_Sub_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. 点击 **Enable** 按钮,等待状态显示为 **Enabled**。 <img src="/assets/shared/img/3f45602-Cloud_Pub_Sub_API_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 开发者 API 已启用。 您可以在 Google Cloud Console 的 [**APIs & Services**](https://console.cloud.google.com/apis/dashboard) 页面上重新确认。向下滚动页面,验证页面底部的表格中包含以下所有 3 个 API: - Google Play Android Developer API - Google Play Developer Reporting API - Cloud Pub/Sub API <img src="/assets/shared/img/b81d174-google_enabled_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **下一步** - [在 Google Cloud Console 中创建服务账号](create-service-account) --- # File: create-service-account --- --- title: "在 Google Cloud Console 中创建服务账号" description: "了解如何在 Adapty 中为安全 API 访问创建服务账号。" --- 为了让 Adapty 自动化数据访问,需要在 Google Play Console 中创建一个服务账号。 1. 打开 Google Cloud Console 的 [**IAM & Admin** -> **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) 部分。请确保您使用的是正确的项目。 <img src="/assets/shared/img/17bbf45-google_cloud_create_service_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **Service accounts** 窗口中,点击 **Create service account** 按钮。 <img src="/assets/shared/img/b93eec1-service_account_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Create service account** 窗口的 **Service account details** 子部分中,输入您想要的 **Service Account Name**。我们建议在名称中包含"Adapty",以说明该账号的用途。**Service account ID** 将自动生成。 4. 复制服务账号的电子邮件地址并保存,以备将来使用。 5. 点击 **Create and continue** 按钮。 <img src="/assets/shared/img/e69d713-grant_access_to_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 在 **Grant this service account access to project** 子部分的 **Select a role** 下拉列表中,选择 **Pub/Sub -> Pub/Sub Admin**。启用实时开发者通知需要此角色。 <img src="/assets/shared/img/976299c-service_account_role.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击 **Add another role** 按钮。 8. 在新的 **Role** 下拉列表中,选择 **Monitoring -> Monitoring Viewer**。允许监控通知队列需要此角色。 9. 点击 **Continue** 按钮。 <img src="/assets/shared/img/ffe8d82-grant_user_access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. 无需任何更改,直接点击 **Done** 按钮。**Service accounts** 窗口将打开。 **下一步** - [在 Google Play Console 中为服务账号授予权限](grant-permissions-to-service-account) --- # File: grant-permissions-to-service-account --- --- title: "在 Google Play Console 中授予服务账号权限" description: "为服务账号授予权限,以实现安全高效的 API 访问。" --- 授予 Adapty 将用于管理订阅和验证购买的服务账号所需权限。 1. 在 Google Play Console 中打开 [**Users and permissions**](https://play.google.com/console/u/0/developers/8970033217728091060/users-and-permissions) 页面,然后点击 **Invite new users** 按钮。 <img src="/assets/shared/img/7b0e614-users_and_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **Invite user** 页面中,输入您已创建的服务用户的电子邮件地址。 <img src="/assets/shared/img/3afd002-invite_user.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 切换到 **Account permissions** 选项卡。 <img src="/assets/shared/img/4e2717b-account_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 选择以下权限: - View app information and download bulk reports (read-only) - View financial data, orders, and cancellation survey responses - Manage orders and subscriptions - Manage store presence 5. 点击 **Invite user** 按钮。 6. 在 **Send invite?** 窗口中,点击 **Send invite** 按钮。服务账号将显示在用户列表中。 **下一步** - [在 Google Play Console 中生成服务账号密钥文件](create-service-account-key-file) --- # File: create-service-account-key-file --- --- title: "在 Google Play Console 中生成服务账号密钥文件" description: "了解如何创建服务账号密钥文件,以便与 Adapty 无缝集成。" --- 要将您在 Play Store 上的移动应用与 Adapty 关联,您需要在 Google Play Console 中生成专用服务账号密钥文件,并将其上传到 Adapty。这些文件有助于保护您的应用并防止未经授权的访问。 :::warning 新服务账号通常需要至少 24 小时才能激活。不过,有一个[技巧](https://stackoverflow.com/a/60691844)可以加速此过程。在 [Google Play Console](https://play.google.com/apps/publish/) 中创建服务账号后,打开任意一个应用,导航至 **Monetize** -> **Products** -> **Subscriptions/In-app products**。编辑任意产品的描述并保存更改。这样应该可以立即激活服务账号,之后您可以将更改恢复原状。 ::: 1. 在 Google Play Console 中打开 [**Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) 部分。请确保您已选择了正确的项目。 <img src="/assets/shared/img/c3156cb-action_manage_keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在弹出的窗口中,点击 **Add key** 并从下拉菜单中选择 **Create new key**。 <img src="/assets/shared/img/44b30ee-create_new_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Create private key for [Your_project_name]** 窗口中,点击 **Create**。您的私钥将以 JSON 文件的形式保存到您的计算机。您可以通过 **Private key saved to your computer** 窗口中提供的文件名来找到该文件。 <img src="/assets/shared/img/e7b8101-cretae_private_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **Create private key for Your_project_name** 窗口中,点击 **Create** 按钮。此操作会将您的私钥以 JSON 文件的形式保存到您的计算机。如有需要,您可以使用弹出的 **Private key saved to your computer** 窗口中提供的文件名来定位该文件。 <img src="/assets/shared/img/187ddc6-Private_key_saved.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在[配置 Google Play Store 集成](google-play-store-connection-configuration)时,您将需要使用此文件。 :::warning 新服务账号通常需要至少 24 小时才能激活。不过,有一个[技巧](https://stackoverflow.com/a/60691844)可以加速此过程。在 [Google Play Console](https://play.google.com/apps/publish/) 中创建服务账号后,打开任意一个应用,导航至 **Monetize** -> **Products** -> **Subscriptions/In-app products**。编辑任意产品的描述并保存更改。这样应该可以立即激活服务账号,之后您可以将更改恢复原状。 ::: **下一步** - [配置 Google Play Store 集成](google-play-store-connection-configuration) --- # File: google-play-store-connection-configuration --- --- title: "配置 Google Play 商店集成" description: "在 Adapty 中配置 Google Play 商店连接,以顺畅处理应用内购买。" --- 本节介绍通过 Google Play 销售的移动应用与 Adapty 的集成流程。您需要将应用在 Play 商店中的配置数据填写到 Adapty 看板中。此步骤对于在 Adapty 中验证购买及接收来自 Play 商店的订阅更新至关重要。 您可以在初始用户引导期间完成此流程,也可以稍后在 Adapty 看板的 **App Settings** 中进行修改。 :::danger 配置更改仅应在您发布集成了 Adapty 付费墙的移动应用之前进行。发布后进行更改将导致集成中断,付费墙将无法在您的移动应用中显示。 ::: ## 步骤 1. 提供包名 \{#step-1-provide-package-name\} 包名是您的应用在 Google Play 商店中的唯一标识符。这是 Adapty 基本功能(如订阅处理)所必需的。 1. 打开 [Google Play 开发者控制台](https://play.google.com/console/u/0/developers)。 2. 选择您需要获取 ID 的应用,**Dashboard** 窗口将会打开。 <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在应用名称下方找到产品 ID 并复制。 4. 从 Adapty 顶部菜单打开 [**App settings**](https://app.adapty.io/settings/android-sdk)。 <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在 **App settings** 窗口的 **Android SDK** 标签页中,粘贴已复制的 **Package name**。 ## 步骤 2. 上传账号密钥文件 \{#step-2-upload-the-account-key-file\} 1. 将您在[创建服务账号密钥文件](create-service-account)步骤中创建的 JSON 格式服务账号私钥文件上传到 **Service account key file** 区域。 <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 请不要忘记点击 **Save** 按钮以确认更改。 **下一步** - [在 Google Play 控制台中启用实时开发者通知(RTDN)](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "在 Google Play Console 中启用实时开发者通知 (RTDN)" description: "通过在 Google Play Console 中为 Adapty 启用实时开发者通知 (RTDN),及时了解关键事件并保持数据准确性。了解如何设置 RTDN 以接收来自 Play Store 的退款及其他重要事件的即时更新" --- 设置实时开发者通知 (RTDN) 对于确保数据准确性至关重要,它能让您即时接收来自 Play Store 的更新,包括退款及其他事件的信息。 ## 启用通知 \{#enable-notifications\} 1. 确保已启用 **Google Cloud Pub/Sub**。打开[此链接](https://console.cloud.google.com/flows/enableapi?apiid=pubsub)并选择您的应用项目。如果尚未启用 **Google Cloud Pub/Sub**,请在此处启用。 <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 从 Adapty 顶部菜单进入 [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk),复制 **Google Play RTDN topic name** 标题旁 **Enable Pub/Sub API** 字段的内容。 <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note 如果 **Enable Pub/Sub API** 字段的内容格式不正确(正确格式以 `projects/...` 开头),请参阅[修复 Enable Pub/Sub API 字段格式错误](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field)部分获取帮助。 ::: 3. 打开 [Google Play Console](https://play.google.com/console/),选择您的应用,然后前往 **Monetize with Play** -> **Monetization setup**。在 **Google Play Billing** 部分,勾选 **Enable real-time notifications** 复选框。 4. 将您在 Adapty **App Settings** 中复制的 **Enable Pub/Sub API** 字段内容粘贴到 **Topic name** 字段中。 5. 在 Google Play Console 中点击 **Save changes**。 <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 测试通知 \{#test-notifications\} 要验证您是否已成功订阅实时开发者通知: 1. 在 Google Play Console 设置中保存更改。 2. 在 Google Play Console 的 **Topic name** 下方,点击 **Send test notification**。 <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 Adapty 中进入 [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk)。如果测试通知已发送,您将在主题名称上方看到其状态。 <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 修复 Enable Pub/Sub API 字段格式错误 \{#fixing-incorrect-format-in-enable-pubsub-api-field\} 如果 **Enable Pub/Sub API** 字段的内容格式不正确(正确格式以 `projects/...` 开头),请按以下步骤排查并解决问题: ### 1. 验证 API 启用状态与权限 \{#1-verify-api-enablement-and-permissions\} 请仔细确认所有必需的 API 已启用,且权限已正确授予服务账号。即使您已完成这些步骤,也请再次逐一核查,确保没有遗漏任何子步骤。请重复以下各节中的步骤: 1. [在 Google Play Console 中启用开发者 API](enabling-of-devepoler-api) 2. [在 Google Cloud Console 中创建服务账号](create-service-account) 3. [在 Google Play Console 中授予服务账号权限](grant-permissions-to-service-account) 4. [在 Google Play Console 中生成服务账号密钥文件](create-service-account-key-file) 5. [配置 Google Play Store 集成](google-play-store-connection-configuration) ### 2. 调整域策略 \{#2-adjust-domain-policies\} 更改 **Domain restricted contacts** 和 **Domain restricted sharing** 策略: 1. 打开 [Google Cloud Console](https://console.cloud.google.com/),选择您用于管理应用的服务账号所在的项目。 2. 在 **Quick Access** 部分,选择 **IAM & Admin**。 <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在左侧面板中,选择 **Organization Policies**。 4. 找到 **Domain restricted contacts** 策略。 <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Actions** 列中的省略号按钮,选择 **Edit policy**。 6. 在策略编辑窗口中: 1. 在 **Policy source** 下,选择 **Override parent's policy** 单选按钮。 2. 在 **Policy enforcement** 下,选择 **Replace** 单选按钮。 3. 在 **Rules** 下,点击 **ADD A RULE** 按钮。 <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **New rule** -> **Policy values** 下,选择 **Allow All**。 <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **SET POLICY**。 7. 对 **Domain restricted sharing** 策略重复步骤 4-6。 最后,重新生成 **Google Play RTDN topic name** 标题旁 **Enable Pub/Sub API** 字段的内容。该字段现在将显示正确的格式。 成功启用实时开发者通知 (RTDN) 后,请务必将已更新策略的 **Policy source** 切换回 **Inherit parent's policy**。 ## 原始事件转发 \{#raw-events-forwarding\} 有时,您可能仍希望接收来自 Google 的原始 S2S 事件。如需在使用 Adapty 的同时继续接收这些事件,只需将您的端点添加到 **URL for forwarding raw Google events** 字段,我们将原样转发来自 Google 的原始事件。 <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **下一步** 为以下平台配置 Adapty SDK: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: stripe --- --- title: "与 Stripe 的初始集成" description: "将 Stripe 与 Adapty 集成,实现无缝的订阅支付处理。" --- Adapty 通过追踪通过 [Stripe](https://stripe.com/) 完成的网页支付和订阅,支持 web2app 订阅流程。 此集成涵盖网页端发起的购买(Stripe Checkout、托管支付页面或自定义网页流程),并将其与移动应用的访问权限和数据分析进行同步。 适用于以下场景: - 为在网页端完成购买、之后安装应用并登录账户的用户自动开通付费功能 - 在单一 Adapty 看板中查看所有订阅分析数据(包括同期群、趋势预测及其他分析工具) 尽管网页端购买在应用中越来越普遍,但 Apple App Store 目前仅允许美国地区对数字商品采用应用内购买以外的支付方式。请确保不要在其他国家的应用内推广您的网页订阅,否则应用可能会被拒审或下架。 以下步骤介绍如何配置 Stripe 集成。 :::important 本集成的重点是追踪和同步 Stripe 网页端购买。如果您需要将用户从应用引导至网页结算页面,请参阅[网页付费墙](web-paywall)。 ::: ## 1\. 将 Stripe 连接到 Adapty \{#1-connect-stripe-to-adapty\} 此集成主要依靠 Adapty 通过 webhook 从 Stripe 拉取订阅数据。因此,您需要提供 API 密钥,并在 Stripe 中使用 Adapty 的 webhook URL,将您的 Adapty 账户与 Stripe 账户关联起来。为自动配置 webhook,请在 Stripe 中安装 Adapty 应用: :::note 以下步骤对 Stripe 的生产模式和测试模式均适用,但每种模式需要使用不同的 API 密钥。 ::: 0. 确认您是以测试模式还是正式模式连接 Stripe。如果您最初在测试模式下操作,之后还需要对正式模式重复以下步骤。 1. 前往 [Stripe 应用市场](https://marketplace.stripe.com/apps/adapty) 安装 Adapty 应用。请注意,沙盒模式不支持安装应用,只能在生产模式或测试模式下安装。 <img src="/assets/shared/img/stripe1.png"/> 2. 授予应用所需权限,这将允许 Adapty 访问订阅数据和历史记录。然后点击 **Continue to app settings** 继续。 在权限弹窗底部,您可以选择以正式模式还是测试模式安装应用。 <img src="/assets/shared/img/stripe2.png"/> 3. 在弹窗中生成一个新的受限密钥。您需要通过邮件、Touch ID 或安全密钥验证身份。密钥生成后将无法再次查看,请将其安全存储在密码管理器或密钥存储中。 <img src="/assets/shared/img/stripe4.png"/> 4. 从弹窗中复制生成的密钥,然后前往 Adapty 的 [App Settings → Stripe](https://app.adapty.io/settings/stripe)。根据您的模式,将密钥粘贴到 **Stripe App Restricted API Key** 对应区域。请注意,测试模式和正式模式需要生成不同的密钥。 <img src="/assets/shared/img/Stripe3.png"/> 大功告成!接下来,在 Stripe 中创建产品并将其添加到 Adapty。 <Details> <summary>已弃用的安装流程</summary> 1. 在 Stripe 中前往 [Developers → API Keys](https://dashboard.stripe.com/apikeys): <img src="/assets/shared/img/6549602-CleanShot_2023-12-06_at_17.29.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击 **Secret key** 旁边的 **Reveal live (test) key button**,复制密钥后前往 Adapty 的 [App Settings → Stripe](https://app.adapty.io/settings/stripe),将密钥粘贴到此处: <img src="/assets/shared/img/2989508-CleanShot_2023-12-07_at_14.59.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 接下来,从 Adapty 同一页面底部复制 Webhook URL。在 Stripe 中前往 [**Developers** → **Webhooks**](https://dashboard.stripe.com/webhooks),点击 **Add endpoint** 按钮: <img src="/assets/shared/img/e7149f5-CleanShot_2023-12-07_at_17.31.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将 Adapty 的 webhook URL 粘贴到 **Endpoint URL** 字段中。然后在 webhook 的 **Version** 字段中选择 **Latest API version**,并选择以下事件: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded <img src="/assets/shared/img/cbc5404-CleanShot_2023-12-07_at_17.36.232x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击"Add endpoint",然后在"Signing secret"下点击"Reveal"。这是用于在 Adapty 端解码 webhook 数据的密钥,显示后请复制: <img src="/assets/shared/img/0460cbb-CleanShot_2023-12-07_at_17.52.582x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 最后,将此密钥粘贴到 Adapty 的 App Settings → Stripe 中的"Stripe Webhook Secret"字段: <img src="/assets/shared/img/055db20-CleanShot_2023-12-07_at_14.56.212x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Details> ## 2\. 在 Stripe 中创建产品 \{#2-create-products-on-stripe\} :::note 如果您是在测试模式下进行配置,请在继续此步骤之前确认 Stripe 已切换到测试模式。 ::: 前往 Stripe 的[产品目录](https://dashboard.stripe.com/products?active=true),创建您想要销售的产品及其定价方案。请注意,Stripe 支持每个产品配置多个定价方案,无需创建额外产品即可灵活调整您的产品组合。 <img src="/assets/shared/img/b202e2e-CleanShot_2023-12-06_at_15.06.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning 目前 Adapty 仅支持**固定价格**($9.99/月)或**打包定价**($9.99/10 个单位),因为这两种方式与应用商店的行为类似。**阶梯定价**、**基于用量的收费**和**客户自定义价格**选项目前不受支持。 ::: ## 3\. 将 Stripe 产品添加到 Adapty \{#3-add-stripe-products-to-adapty\} :::warning 产品是必须配置的!请务必在 Adapty 看板中创建您的 Stripe 产品。Adapty 仅追踪与这些产品关联的交易事件,请不要跳过此步骤——否则交易事件将无法创建。 ::: 我们对待 Stripe 的方式与 App Store 和 Google Play 相同:它只是您销售数字产品的另一个渠道,配置方式也类似。只需将 Stripe 产品(即其 `product_id` 和 `price_id`)添加到 Adapty 的产品区域即可: <img src="/assets/shared/img/stripe-add-product.webp" style={{ border: 'none', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Stripe 中的产品 ID 格式为 `prod_...`,价格 ID 格式为 `price_...`。在 Stripe 的[产品目录](https://dashboard.stripe.com/products?active=true)中打开任意产品,即可轻松找到这些信息: <img src="/assets/shared/img/14a72d7-CleanShot_2023-12-06_at_17.32.512x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 添加完所有必要产品后,下一步是告知 Stripe 哪位用户正在完成购买,以便 Adapty 能够识别! ## 4\. 在网页端购买中附加用户 ID \{#4-enrich-purchases-made-on-the-web-with-your-user-id\} Adapty 依赖来自 Stripe 的 webhook 作为唯一数据来源,用于为用户提供和更新访问等级。但在使用 Stripe 时,您需要从您这端提供额外信息,才能确保集成正常运行。 为了让访问等级在各平台(网页或移动端)保持一致,您需要确保使用单一用户 ID,让 Adapty 能够通过 webhook 识别该用户。这可以是用户的邮箱、手机号,或您所使用的授权系统中的任意其他 ID。 确定您希望用于识别用户的 ID。然后在代码中找到通过 Stripe 初始化支付的部分,并将该用户 ID 以 `customer_user_id` 为键添加到 [Stripe Subscription](https://docs.stripe.com/api/subscriptions/object#subscription_object-metadata)(`sub_...`)或 [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-metadata)(`ses_...`)对象的 `metadata` 字段中,如下所示: ```json showLineNumbers title="Stripe Metadata contents" {'customer_user_id': "YOUR_USER_ID"} ``` 这一简单的改动是您在代码层面唯一需要做的事情。之后,Adapty 会解析从 Stripe 接收到的所有 webhook,提取该 `metadata`,并将订阅正确关联到您的客户。 :::warning 用户 ID 是必填项 否则,我们将无法匹配该用户并在移动端为其授予访问等级。 如果您没有在 `metadata` 中提供 `customer_user_id`,可以选择让 Adapty 在其他位置查找 `customer_user_id`:要么使用 Stripe 客户对象中的 `email`,要么使用 Stripe Session 中的 `client_reference_id`。 了解更多关于配置用户画像创建行为的信息,请参阅[下方内容](stripe#profile-creation-behavior)。 ::: :::note Stripe 中的 Customer 也是必需的 如果您使用 Checkout Sessions,请[确保创建了 Stripe Customer](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation),将 `customer_creation` 设置为 `always`。 ::: ## 5\. 为移动端用户开通访问权限 \{#5-provide-access-to-users-on-the-mobile\} 为确保从网页端进入的移动用户能够访问付费功能,只需使用与上一步相同的 `customer_user_id` 调用 `Adapty.activate()` 或 `Adapty.identify()`(详情请参阅 <InlineTooltip tooltip="识别用户">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>)。 ## 6\. 测试集成 \{#6-test-your-integration\} 请确保已分别为沙盒环境和生产环境完成上述步骤。通过 Stripe 测试模式发起的交易在 Adapty 中将被识别为沙盒交易。 :::info 大功告成! 您的用户现在可以在网页端完成购买,并在应用中访问付费功能。同时,您也可以在同一个地方查看所有订阅分析数据。 ::: ## 用户画像创建行为 \{#profile-creation-behavior\} Adapty 需要将购买记录与[客户用户画像](profiles-crm)绑定,才能在移动端使用——因此默认情况下,它会在收到 Stripe 的 webhook 时创建用户画像。您可以选择将以下内容用作 Adapty 中的客户用户 ID: 1. **默认且推荐:** 您在[上方第 4 步](stripe#4-enrich-purchases-made-on-the-web-with-your-user-id)的 metadata 中提供的 `customer_user_id` 2. Stripe 客户对象中的 `email`(参见 [Stripe 文档](https://docs.stripe.com/api/customers/object#customer_object-email)) 3. Stripe Session 对象中的 `client_reference_id`(参见 [Stripe 文档](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-client_reference_id)) 您可以在 [App Settings → Stripe](https://app.adapty.io/settings/stripe) 中配置使用哪种 ID。 :::warning **注意:** 如果来自 Stripe 的某笔交易不包含指定的 ID,我们将不会创建用户画像。该交易将保持匿名状态,直到被某个用户画像认领(例如,如果您之后使用 [S2S validate](api-adapty/operations/validateStripePurchase) 手动告知我们该交易信息)。 该交易会出现在 Analytics 中,但不会出现在依赖用户画像计数的区域(LTV、同期群、转化率等),您也无法在事件流中看到它。 ::: 您还有第四个选项——完全不创建用户画像,但由于上述分析限制,不建议这样做。 ## 当前限制 \{#current-limitations\} ### 升级、降级与按比例计费 \{#upgrading-downgrading-and-proration\} 订阅变更(如升级或降级)可能产生按比例计费。Adapty 不会在收入计算中考虑这些费用。建议通过 Stripe 看板手动禁用这些选项。您也可以通过 Stripe API 将 `proration_behaviour` 属性值设为 `none` 来禁用它们。 ### 取消订阅 \{#cancellations\} Stripe 提供两种订阅取消方式: 1. 立即取消:订阅立即取消,可选是否进行按比例计费 2. 在当前计费周期结束时取消:订阅在当前计费周期结束时取消(与应用商店中的应用内订阅类似) Adapty 支持这两种方式,但立即取消的收入计算将忽略按比例计费选项。 ### 账单问题与宽限期 \{#billing-issues-and-grace-period\} 当客户遇到付款问题时,Adapty 将生成账单问题事件并撤销访问权限。我们目前尚不支持 Stripe 的宽限期功能——这将在未来版本中实现。 ### 退款 \{#refunds\} Adapty 仅追踪全额退款,目前不支持按比例退款或部分退款。 ### 交易 ID 唯一性 \{#transaction-id-uniqueness\} Adapty 使用 `store_transaction_id` 和 `store_original_transaction_id` 来匹配用户画像和交易。这些 ID **在测试环境和生产环境之间必须保持唯一**。 #### 为何这很重要 \{#why-this-matters\} 如果同一个交易 ID 在两个环境中都存在,Adapty 会将其视为同一笔交易,从而导致: - 生产环境的购买继承测试环境的访问等级和产品 ID - API 响应中出现错误的产品 ID 和环境信息 - 用户画像关联和订阅事件被干扰 #### 如何确保唯一性 \{#how-to-ensure-uniqueness\} Stripe 的发票 ID 在测试环境和正式环境之间可能重叠。为避免跨环境冲突,请选择以下一种方式: #### 方案一:账户级编号加环境前缀 \{#option-1-account-level-numbering-with-environment-prefixes\} 为每个环境分别配置前缀: 1. 在 Stripe 看板中切换到测试模式。 2. 前往 [Settings → Billing → Invoices](https://dashboard.stripe.com/settings/account/?support_details=true)。 3. 将 **Invoice numbering** 设置为 **Sequentially across your account**。 4. 将 **Invoice prefix** 设置为 TEST-(或其他专用于测试环境的前缀)。 5. 切换到正式模式,重复第 2-4 步,使用 LIVE-(或其他专用于正式环境的前缀)作为前缀。 #### 方案二:客户级编号 \{#option-2-customer-level-numbering\} 在 [**Stripe 设置** -> **Billing** -> **Invoices** 标签页](https://dashboard.stripe.com/settings/account/?support_details=true)中,将 **Invoice numbering** 设置为 **Sequentially for each customer (customer-level)**。 即使进行了上述配置,如果您删除了某张发票,Stripe 可能会将该 ID 重新分配给同一客户的新发票。因此请尽量避免删除发票。 ### 通过 Stripe Checkout 或 Payment Links 进行的一次性购买 \{#one-time-purchases-via-stripe-checkout-or-payment-links\} Adapty 仅在 Stripe 为购买生成发票时,才会追踪通过 Stripe Checkout(`mode=payment`)或 Payment Links 完成的一次性(非订阅)购买。默认情况下,Stripe 不会为一次性 Checkout 购买创建发票。在这种情况下,`payment_intent.succeeded` 事件不包含发票数据,Adapty 无法据此记录交易。 要在 Adapty 中追踪一次性 Checkout 购买,请在创建 session 时[启用发票创建](https://docs.stripe.com/payments/checkout/receipts?payment-ui=stripe-hosted#paid-invoices-hosted)。这样 Stripe 就会生成发票并触发相关的 `invoice.created` 和 `invoice.updated` 事件,Adapty 通过处理这些事件来记录交易。 ## 充分利用您的 Stripe 数据 \{#get-more-from-your-stripe-data\} 完成 Stripe 集成后,Adapty 即可立即提供数据洞察。为充分利用您的 Stripe 数据,您可以设置额外的 Adapty 集成来转发 Stripe 事件——将所有订阅分析数据汇聚到单一 Adapty 看板中。 :::tip 为获得更丰富的分析数据,您可以在 Stripe metadata 中添加 `variation_id`,以将购买归因到特定的付费墙实例。这在实现自建网页付费墙时尤为有用,可以追踪是哪个具体的付费墙展示带来了转化。 请注意,`variation_id` 仅从 Stripe Subscription(`sub_...`)和 Checkout Session(`ses_...`)对象的 metadata 中读取: ```json showLineNumbers title="Stripe Metadata with variation_id" { 'customer_user_id': "YOUR_USER_ID", 'variation_id': "YOUR_VARIATION_ID" } ``` ::: 可用于转发和分析 Stripe 事件的集成: - [Amplitude](amplitude/) - [Webhook](webhook) - [Firebase](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [Posthog](posthog) ### 支持的 Stripe 事件 \{#supported-stripe-events\} Adapty 支持以下 Stripe 事件: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded --- # File: paddle --- --- title: "与 Paddle 的初始集成" description: "将 Paddle 与 Adapty 集成,实现无缝的订阅支付处理。" --- Adapty 通过跟踪通过 [Paddle](https://www.paddle.com/) 完成的网页支付和订阅,支持 web2app 订阅流程。 此集成涵盖网页端发起的购买,并将其与移动应用的访问权限和分析数据同步,与应用商店的应用内购买并行运作。 在以下场景中非常实用: - 在同一系统中收集应用内购买和网站购买的订阅数据 - 为在网站上完成购买的用户授予移动应用中付费功能的访问权限 - 在一个看板中查看所有销售渠道的数据分析和订阅数据 :::note 苹果现已允许美国区 App Store 的应用包含跳转至外部支付系统的链接,但应用可能仍需同时提供应用内购买选项。请查阅适用于您所在地区和应用类别的最新 App Store 审核指南。 ::: :::note 此集成专注于追踪和同步 Paddle 网页购买记录。如果需要将用户从应用内引导至网页结账页面,请使用 Adapty [网页付费墙](web-paywall)。 ::: 要设置 Paddle 集成,请按照以下步骤操作: ## 1\. 将 Paddle 连接到 Adapty \{#1-connect-paddle-to-adapty\} 该集成通过 Webhook 将订阅数据从 Paddle 发送到 Adapty。要连接您的 Adapty 和 Paddle 账户,您需要: 1. 提供您的 Paddle API 密钥。 2. 将 Adapty 的 Webhook URL 添加到 Paddle。 :::note 以下步骤同时适用于生产环境和测试环境,你可以同时配置两者。所提供的链接均为生产环境链接——如需获取测试环境链接,只需在每个 URL 开头添加 `sandbox-` 即可。例如,使用 `https://sandbox-vendors.paddle.com/authentication-v2` 代替 `https://vendors.paddle.com/authentication-v2`。 ::: ### 1.1. 获取并添加 Paddle API 密钥 \{#get-and-add-paddle-api-keys\} 1. 在 Paddle 中,前往 [Developer Tools → Authentication](https://vendors.paddle.com/authentication-v2),点击 **New API key**。 <img src="/assets/shared/img/paddle-new-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 为密钥命名并设置过期日期。要让 API 密钥与 Adapty 配合使用,需要为所有实体授予 **Read** 权限。点击 **Save**。 <img src="/assets/shared/img/paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Copy key**。 <img src="/assets/shared/img/copy-paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 Adapty 中,前往 [App Settings → Paddle](https://app.adapty.io/settings/paddle),将密钥粘贴到 **Paddle API key** 部分。 :::warning 如果你为 Paddle API 密钥设置了过期日期,必须在到期前手动生成新密钥并在 Adapty 中更新。密钥过期后,集成将无任何警告地停止工作,用户将无法完成购买。 ::: <img src="/assets/shared/img/paddle-api-keys-adapty.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.2. 添加将发送到 Adapty 的事件 \{#add-events-that-will-be-sent-to-adapty\} 1. 从 Adapty 中同一个 **Paddle** 页面复制 **Webhook URL**。 2. 在 Paddle 中,前往 [**Developer Tools → Notifications**](https://vendors.paddle.com/notifications-v2),然后点击 **New destination** 添加 webhook。 <img src="/assets/shared/img/paddle-webhook.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 为该 webhook 输入一个描述性名称。建议在名称中包含"Adapty",方便日后查找。 4. 将 Adapty 中的 **Webhook URL** 粘贴到 **URL** 字段。请确保使用的是正确环境的 webhook。 5. 将 **Notification type** 设置为 **Webhook**。 <img src="/assets/shared/img/paddle-create-webhook.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 选择以下事件: - `subscription.created` - `subscription.updated` - `transaction.created` - `transaction.updated` - `adjustment.created` - `adjustment.updated` <img src="/assets/shared/img/paddle_events.png" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击 **Save destination** 完成 Webhook 设置。 ### 1.3. 获取并添加 Webhook 密钥 \{#retrieve-and-add-the-webhook-secret-key\} 1. 在 **Notifications** 窗口中,点击刚刚创建的 Webhook 旁边的三个点,选择 **Edit destination**。 2. **Edit destination** 面板中会出现一个名为 **Secret key** 的新字段,复制它。 <img src="/assets/shared/img/paddle-webhook-secret-key-copy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 Adapty 中,前往 [App Settings → Paddle](https://app.adapty.io/settings/paddle),将密钥粘贴到 **Notification secret key** 字段中。Adapty 将使用该密钥验证 webhook 数据。 <img src="/assets/shared/img/paddle-webhook-secret-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.4. 将 Paddle 客户与 Adapty 用户画像关联 \{#match-paddle-customers-with-adapty-profiles\} Adapty 需要将每笔购买与[用户画像](profiles-crm)关联,这样才能在你的应用中使用。默认情况下,当 Adapty 收到 Paddle 的 webhook 时,会自动创建用户画像。你可以选择将哪个值用作 Adapty 中的 `customer_user_id`: 1. **默认且推荐:** 您在 `custom_data` 字段中传递的 `customer_user_id`(参见 [Paddle 文档](https://developer.paddle.com/build/transactions/custom-data)) 2. Paddle Customer 对象中的 `email`(参见 [Paddle 文档](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 3. `ctm-...` 格式的 Paddle Customer ID(参见 [Paddle 文档](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 4. 不创建用户画像。如果您希望自行管理客户的用户画像,请选择此选项。 您可以在 [App Settings → Paddle](https://app.adapty.io/settings/paddle) 的 **Profile creation behavior** 字段中配置使用哪个值。 <img src="/assets/shared/img/paddle-users.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 2. 将 Paddle 产品添加到 Adapty \{#2-add-paddle-products-to-adapty\} :::warning 请务必将您的 Paddle 产品添加到 Adapty 看板,或将 Paddle 产品 ID 添加到现有产品中。Adapty 仅跟踪与这些产品绑定的交易事件。如果跳过此步骤,将不会创建任何交易事件。 ::: Paddle 在 Adapty 中的使用方式与 App Store 和 Google Play 完全相同——它是您销售数字产品的另一个平台。要完成配置,请在 Adapty 的 [Products](https://app.adapty.io/products) 页面中,填入相应的 `product_id` 和 `price_id` 值。 <img src="/assets/shared/img/paddle-create-product.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在 Paddle 中,产品 ID 格式为 `pro_...`,价格 ID 格式为 `pri_...`。打开某个具体产品后,你可以在 [Paddle 产品目录](https://vendors.paddle.com/products-v2)中找到它们: <img src="/assets/shared/img/paddle-product-price.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 产品添加完成后,下一步是确保 Adapty 能将购买行为关联到正确的用户。 ## 3\. 为移动端用户提供访问权限 \{#3-provide-access-to-users-on-the-mobile\} 为确保在网页购买的用户能够在移动端获得访问权限,请使用购买时传入的相同 `customer_user_id` 调用 `Adapty.activate()` 或 `Adapty.identify()`。详情请参见[用户识别](identifying-users)。 ## 4\. 测试您的集成 \{#4-test-your-integration\} 完成所有设置后,您可以测试您的集成。在 Paddle 测试环境中进行的交易将在 Adapty 中显示为 **Test**。来自生产环境的交易将显示为 **Production**。 您的集成现已完成。用户可以在您的网站上购买订阅,并自动在您的移动应用中获得高级功能访问权限,同时您可以在统一的 Adapty 看板中跟踪所有订阅分析数据。 ## 重要注意事项 \{#important-considerations\} - 在 Adapty 的分析中,交易金额包含税费和 Paddle 手续费,这与 Paddle 看板中显示税后及手续费后金额的方式不同。因此,您在 Adapty 中看到的数字会高于 Paddle 看板中的数字。 - 与其他商店不同,Paddle 中的退款仅影响被退款的特定交易,不会自动取消订阅。除非明确取消,否则订阅将继续保持活跃状态。 - 您还可以在 `custom_data` 字段中包含 `variation_id`,以将购买归因到特定的付费墙实例。Adapty 将从 webhook 中处理这些数据,并将其纳入分析统计。 ### 付费试用 \{#paid-trials\} 在 Paddle 中使用付费试用时,需要在 Adapty 中创建两个产品: 1. 创建一个一次性购买产品,并将其关联到负责收取试用期费用的 Paddle 价格。 2. 然后创建一个订阅产品(月度/周度等),并将其关联到包含免费试用组件的 Paddle 价格。 从 Paddle 的角度来看,这是一个包含两个价格的单笔交易——一个价格用于收取试用费(例如 $0.99),另一个价格用于免费试用($0.00)。 从 Adapty 的角度来看,这会产生两个独立的事件:一个是针对试用付款的一次性购买事件,另一个是针对订阅产品的试用开始事件。 例如,当用户以 $0.99 开始付费试用一个 $9.99/月的订阅时,Paddle 会创建一笔包含两个价格的交易,而 Adapty 则将其处理为一笔 $0.99 的一次性购买(即时付款)和一个 $0.00 的试用开始事件(对应未来 $9.99/月的订阅)。 :::note 当用户取消付费试用时,你会收到 **Trial expired** 和 **Trial renewal canceled** 事件。 ::: ## 充分利用您的 Paddle 数据 \{#get-more-from-your-paddle-data\} :::important 要使您的 Paddle 事件能够与集成配合使用,您的用户必须至少使用其 App Store/Google Play 账户登录过一次应用。 ::: 完成 Paddle 集成后,Adapty 即可立即提供数据洞察。为了充分利用您的 Paddle 数据,您可以设置额外的 Adapty 集成来转发 Paddle 事件——将所有订阅分析数据汇聚到同一个 Adapty 看板中。 您可以使用以下集成来转发和分析 Paddle 事件: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) ## 当前限制 \{#current-limitations\} - **取消订阅**:Paddle 提供两种取消订阅的方式: 1. 立即取消:订阅立即终止。 2. 在当前周期结束时取消:订阅在当前计费周期结束后终止(类似于应用商店中的应用内订阅)。 - **退款**:Adapty 支持追踪全额退款和部分退款。 - **宽限期**:默认情况下,Paddle 为账单问题设置固定的 30 天宽限期,在此期间订阅保持有效。你可以[自定义宽限期时长及到期后的处理方式(暂停或取消订阅)](https://developer.paddle.com/build/retain/configure-payment-recovery-dunning#prerequisites)。 **试用期**:如果试用期结束后收款失败,订阅状态将变为 `past_due`。在生产环境中,Paddle 的 Retain 功能会应用催款窗口,在订阅被取消或暂停之前尝试恢复付款。在沙盒环境中,Retain 不可用,因此不会重试付款,订阅将无限期保持 `past_due` 状态。 --- **另请参阅:** - [通过服务端 API 验证 Paddle 购买、获取访问等级并从 Paddle 导入交易历史](api-adapty/operations/validatePaddlePurchase) --- # File: custom-store --- --- title: "与其他商店的初始集成" description: "Adapty 与 App Store 的初始集成:快速指南" --- 欢迎加入 Adapty!我们的首要任务是帮助您快速上手,为您的应用取得最佳成果。 初始集成仅适用于 [App Store](initial_ios)、[Google Play](initial-android)、[Stripe](stripe) 和 [Paddle](paddle),因为 Adapty 会与这些商店验证您的应用、产品和优惠。 Adapty 不会与其他应用商店验证数据,也不处理通过它们完成的购买。但是,您仍然可以标记通过其他商店销售的产品,以便 Adapty 在购买成功后授予付费内容的访问权限、在分析中记录交易,并通过集成进行共享。 <img src="/assets/shared/img/Adapty-Communication-Scheme.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::important 请确保您的后端处理购买并使用 [Adapty 服务端 API](getting-started-with-server-side-api) 将交易发送给 Adapty。只有在收到交易后,Adapty 才会提供访问权限、触发交易事件、将其发送至集成,并在分析中反映。 ::: 要将产品标记为通过自定义应用商店销售,请在创建产品时选择对应的应用商店。如果所需商店未在列表中,以下是创建商店的方法: 1. 在 **Products** 页面,打开您希望通过自定义应用商店销售的产品。 2. 选择您要通过其销售的应用商店。如果未列出,请点击 **Create Custom Store** 按钮。 <img src="/assets/shared/img/create_custom-appstore.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 输入商店的 **Title** 和 **Store ID**。 4. 点击 **Create store** 按钮。 如果您的后端配置正确,Adapty 将接收来自该自定义商店的产品交易,在分析、[**Event Feed**](event-feed) 和[集成](https://app.adapty.io/integrations)中反映这些交易,并相应地授予访问权限。 ## 从自定义商店数据中获取更多价值 \{#get-more-from-your-custom-store-data\} :::important 要使自定义商店事件与集成配合使用,您的用户必须至少使用其 App Store/Google Play 账户登录过应用一次。 ::: 设置自定义商店集成后,Adapty 即可立即提供洞察。为充分利用您的数据,您可以设置额外的 Adapty 集成来转发自定义商店事件——将所有订阅分析汇聚到单一的 Adapty 看板中。 可用于转发和分析自定义商店事件的集成: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) --- # File: transfer-apps --- --- title: "将应用转移到其他账户" description: "在 Adapty 中更换应用所有者" --- 当您的公司被收购、出售应用或重组业务实体时,需要将应用转移给其他所有者。转移过程涉及协调 Adapty、App Store Connect 和 Google Play Console 中的变更,以确保服务不中断。 ## 转移应用所有权 \{#transfer-app-ownership\} 请先完成应用商店的转移,然后再在 Adapty 中转移应用。此顺序可确保在整个转移过程中购买功能持续正常运行。 :::note 在转移过程中,请勿删除或重新创建产品。在验证转移成功完成之前,请勿更改产品 ID。 ::: ### App Store (iOS) 迁移 \{#app-store-ios-transfer\} :::important App Store Connect API 密钥(Issuer ID、Key ID、.p8 文件)属于账户级别,而非应用级别。迁移完成后,你需要从新所有者的账户生成新的 API 密钥,并在 Adapty 中更新。 应用专属共享密钥在迁移过程中仍可用于验证收据,但迁移完成后,新所有者同样需要重新生成并在 Adapty 中更新。 ::: 1. **新所有者:** 如果还没有账号,请在 [app.adapty.io](https://app.adapty.io) 注册 Adapty 账号。 2. **原所有者:** 按照 Apple 的[转让指南](https://developer.apple.com/help/app-store-connect/transfer-an-app/overview-of-app-transfer)在 App Store Connect 中发起应用转让。 3. **新所有者:** 在 App Store Connect 中接受转让。 4. **原所有者:** 发送邮件至 [support@adapty.io](mailto:support@adapty.io),申请在 Adapty 中转让应用。请提供应用名称和新所有者的邮箱地址。 5. **新所有者:** 在 Adapty 中接收应用后,按照 [App Store 集成指南](initial_ios)在你的账号下生成并配置所有凭据。 ### Google Play(Android)迁移 \{#google-play-android-transfer\} 1. **新所有者:** 如果还没有 Adapty 账户,请在 [app.adapty.io](https://app.adapty.io) 注册一个。 2. **双方所有者:** 确保两个 Google Play 开发者账户均已完成注册。 3. **原所有者:** 通过 Google Play 管理中心或 Google Play 开发者支持提交转让申请。Google 可能会要求提供额外文件,例如 DUNS 编号、合同或销售证明。 4. **新所有者:** 审核并批准转让申请。 5. **Google:** Google 支持团队处理转让请求,通常需要几个工作日,但具体时间可能因账户验证、订阅复杂程度和支付设置而有所不同。 6. **原所有者:** Google 完成转让后,发送邮件至 [support@adapty.io](mailto:support@adapty.io),申请在 Adapty 中转移应用。请提供应用名称和新所有者的电子邮件地址。 7. **新所有者:** 在 Adapty 中接收应用后,按照 [Google Play 集成指南](initial-android) 在您的账户下生成并配置所有凭据。 转让内容包括用户、订阅、统计数据、评分和商店列表。现有订阅者的账单连续性将得到保障,但付款将在转让完成后才切换到新所有者的商户账户。转让前的付款报告和订单仍保留在原账户中。详细要求请参阅 Google 的[转让指南](https://support.google.com/googleplay/android-developer/answer/6230247)。 ## 风险规避与时机选择 \{#risk-mitigation-and-timing\} **转移期间持续正常运行的功能:** - 购买与续订(专属共享密钥在转移窗口期间持续验证收据) - 现有订阅者的访问权限 - SDK 持续正常运行 **暂时停止运行的功能:** - App Store Connect API 调用(需配置新密钥后恢复) - 服务器通知(需重新配置端点后恢复) - 凭据过渡期间分析数据可能出现缺口 **推荐时间安排:** - 在低流量时段完成迁移(用户主要时区的凌晨 3 点至 6 点) - 接受商店迁移后,新所有者需立即配置凭据 - 在接受迁移与完成 Adapty 集成之间,预留 15–30 分钟 **完成迁移后:** - 立即测试收据验证 - 监控自动续订成功率,持续 48 小时 - 确认服务器通知已正常到达您的系统 - 检查新购买记录是否被正确追踪 ## 验证转移是否成功完成 \{#verify-transfer-completed-successfully\} 在完成 Adapty 和应用商店的转移后: 1. **检查看板访问权限:** 新所有者应能在其 Adapty 看板中看到该应用。 2. **验证 API 密钥连接:** 检查新的 App Store Connect API 密钥或 Google Play 服务账户是否在 Adapty 中成功连接。 3. **测试 SDK 连接:** 运行您的应用,验证 Adapty SDK 初始化时是否无报错。 --- # File: installation-of-adapty-sdks --- --- title: "安装 Adapty SDK" description: "为 iOS、Android 及跨平台应用安装 Adapty SDK。" --- 根据您的偏好,您有三种方式可以开始使用: - **遵循平台专属快速入门指南**:指南包含可直接用于生产环境的代码片段,因此实施起来不会花费太长时间。 - [iOS](ios-sdk-overview) - [Android](android-sdk-overview) - [React Native](react-native-sdk-overview) - [Flutter](flutter-sdk-overview) - [Unity](unity-sdk-overview) - [Kotlin Multiplatform](kmp-sdk-overview) - [Capacitor](capacitor-sdk-overview) - **使用大语言模型(LLM)**:我们的文档对 LLM 友好。阅读我们的[指南](adapty-cursor),了解如何充分利用 LLM 与 Adapty 文档结合使用。 - **探索示例应用**: - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native(基础示例 - 纯 RN)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native(高级示例 - 适用于开发,可处理更复杂的场景)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native(Expo 开发版本)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native(Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples) --- # File: sample-apps --- --- title: "示例应用" description: "" --- 为了帮助你快速上手 Adapty SDK,我们准备了示例应用,展示如何集成和使用其核心功能。这些应用提供了付费墙、购买流程和数据分析追踪的现成实现。 <img src="/assets/shared/img/adapty-scheme.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 为什么使用示例应用? \{#why-use-sample-apps\} - **快速集成:** 在真实应用中了解 Adapty SDK 的工作方式。 - **最佳实践:** 遵循推荐的实现模式。 - **调试与测试:** 在将 Adapty 集成到自己的项目之前,使用示例应用进行排查和实验。 ## 可用示例应用 \{#available-sample-apps\} - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native(纯 RN 基础示例)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native(高级示例——适合开发使用,可处理更复杂的场景)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native(Expo 开发构建)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native(Expo Go 及 Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor (React)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Capacitor (Vue.js)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Capacitor (Angular)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [Capacitor(高级开发工具)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) --- # File: paywall-builder-templates --- --- title: "创建流程" description: "从自定义设计的模板库或最简启动项开始新建流程。" --- 您可以从模板创建流程,也可以从头开始创建。 :::link 想了解更多关于构建流程的内容?请观看我们 [YouTube 播放列表](https://www.youtube.com/playlist?list=PLMksWqaZiWtM)中的分步视频教程。 ::: ## 创建流程 \{#create-flow\} 1. 打开 **Flows** 页面。 2. 点击 **Create flow**。 3. 选择一个选项: - **Browse templates**(打开模板库) - **Start from scratch**(创建空白流程) 4. 在编辑器中重命名流程。点击顶部标题栏中的流程名称,然后输入新名称。 :::warning Adapty 允许流程重名。请为每个新建的流程重命名,否则会创建多个难以区分的 **Untitled** 流程。 ::: ### 使用模板 \{#use-a-template\} 模板库包含多个模板,可作为你构建流程的起点。每个模板都是一个完整的流程,包含多个页面、交互元素和可用的导航功能。你可以编辑任意元素来进行自定义。 使用模板的步骤: 1. 在模板库中浏览模板卡片。每张卡片会展示该流程的预览截图。 2. 点击你想要的卡片上的 **Use as template**。 模板加载完成后,您可以在编辑工具中修改任何元素、页面或属性。 ### 从零开始 \{#start-from-scratch\} 从零开始会创建一个包含单个空白屏幕的流程。你可以使用[元素库](builder-elements)中的元素来设计该屏幕。 ## 更换模板 \{#change-the-template\} 您可以在编辑工具内切换模板。打开 Screens 面板,点击 **Templates** Templates 按钮重新打开模板库,然后选择一个新模板。 :::warning 应用新模板会替换当前的流程草稿。Adapty 会提示您确认——点击 **Use template** 继续操作,或点击 **Cancel** 保留草稿。确认后,之前的草稿将无法恢复。已发布的流程不受影响,仍正常运行。 ::: ## 模板中的自定义字体 \{#custom-fonts-in-templates\} :::link 主要文章:[Flow Builder 中的自定义字体](using-custom-fonts-in-flow-builder) ::: 带有 **Custom font** 标签的模板使用了自定义字体。这些字体不随移动端 SDK 一起提供。将鼠标悬停在标签上可查看该模板所使用的字体。 若要在设备上呈现预期的排版效果,请将字体文件添加到您的应用包中。未内置该字体的旧版应用将回退使用系统字体。 如需在不影响旧版本的情况下替换字体,请复制该流程,在副本中修改字体,并将副本限制为[包含该字体的应用版本的用户](segments)。 --- # File: builder-ui --- --- title: "流程编辑工具界面" description: "流程编辑工具界面与工作区概览。" --- 流程编辑工具的主界面包含添加视觉元素、编辑属性以及修改用户流程逻辑所需的全部工具。本文将介绍界面的各个区域:各区域的功能及其位置。 :::link 想了解更多关于构建流程的内容?请观看我们 [YouTube 播放列表](https://www.youtube.com/playlist?list=PLMksWqaZiWtM)中的分步视频教程。 ::: ## 项目控件与常用快捷键(顶部工具栏)\{#project-controls-and-useful-shortcuts-top-toolbar\} * **Close** Close:退出流程编辑器并返回流程列表页。 * **App name** App:标识该流程所属的应用。 * **All flows** Flows:打开该应用所有流程的列表。 * **Flow status**:流程名称左侧的图标表示当前[流程状态](builder-save-publish#flow-status): - **Draft** Draft - **Publishing**(旋转加载中) - **Failed** Failed - 或 **Live** Live。 * **重命名流程**:点击流程名称即可重命名。多个流程可以同名——建议[为每个新流程起一个唯一的名称](paywall-builder-templates#create-flow)。 * **视图模式切换**:在设计视图 Cursor 与[远程配置视图](customize-flow-with-remote-config)Remote Config 之间切换。 * **撤销/重做**:点击箭头图标以撤销 Undo 或重做 Redo 流程更改,也可使用 ⌘Z / Ctrl+Z 进行撤销。 * **保存草稿 / 发布**:点击 **Save draft** 可保存进度而不上线(⌘ / Ctrl+S)。展开下拉菜单 Open dropdown 可访问 [**Publish**](builder-save-publish) 按钮。只有发布后,才能将流程添加到[版位](create-placement)中。 ## 预览区域(中央) \{#preview-area-center\} 工作区中央区域模拟你的流程在移动设备上的实际显示效果。 * 点击某个元素即可选中并编辑其属性。若要选中容器内的子元素,请先点击容器,再点击子元素。 * 若要编辑页面本身的属性,请点击所有元素以外的空白区域,或在 Screens and Layers 面板中选择该页面。 * 若要调整元素的排列顺序,请在 Screens and Layers 面板中上下拖动对应条目。 :::warning 流程编辑器旨在创建响应式布局。因此,您**无法手动更改元素的位置**——只能更改它们的顺序。每个容器的布局设置决定了其中元素的排列方式。 ::: ### 设备预览上方的活动屏幕工具栏 \{#active-screen-bar-above-the-device-preview\} - **Screen name** — 显示当前屏幕名称的标签。 - **Toggle animations** Toggle animations — 开启或关闭元素动画预览;开启后动画会持续播放,直到手动关闭。仅在当前屏幕包含至少一个[动画](builder-styling#animation)时显示。不影响真机上的动画效果。 - **Add element** Plus — 在当前屏幕打开[元素库](builder-elements)。等同于"屏幕与图层"面板顶部的 **+** 按钮——在面板折叠时尤为实用。 ### 查看控件(底部工具栏)\{#view-controls-bottom-toolbar\} 底部工具栏中的工具用于控制预览效果。 * **Device**:从可用的 iPhone 和 Android 手机型号中选择一款,以更改视口尺寸和设备外观。 * **Screen orientation**:在竖屏 Portrait 和横屏 Landscape 模式之间切换,预览不同方向下的流程效果。 * **Color scheme**:在浅色 Light mode 和深色 Dark mode 模式之间切换,查看设计在不同主题下的适配效果。 * **Locale**:选择语言区域,预览本地化内容下的流程效果。 * **View options**:开启或关闭设备边框和安全区域参考线。 ## 屏幕与元素属性(右侧面板)\{#screen-and-element-properties-right-panel\} ### 屏幕设置与布局 \{#screen-settings-and-layout\} :::link 主要文章:[屏幕与图层](paywall-layout-and-products) ::: 未选中任何元素时,右侧面板允许你调整当前[流程屏幕](paywall-layout-and-products)的属性,包括以下内容: * 与系统 UI 的交互(例如是否显示状态栏) * 自动布局规则 * 背景(颜色、图片或视频) * 内边距大小 * 垂直滚动行为 如果界面包含某些元素(例如[互动问卷](onboarding-quizzes)),此列表将扩展并显示相关属性。 ### 元素属性 \{#element-properties\} 选中元素后,右侧面板允许您修改其样式和交互属性。 #### 设计属性 \{#design-properties\} :::link 了解更多:[布局与定位](manage-paywall-ui-elements),[样式与外观](builder-styling) ::: **Design** 标签页用于配置所选元素的视觉外观和布局: * **Visibility(可见性)**:显示或隐藏元素。启用 **Conditional** 可见性可设置规则,控制元素何时显示。 * **Position(位置)**:在 Relative、Absolute 或 Fixed 定位方式之间选择。 * **Content(内容)**(仅限文本元素):编辑元素的文本内容、插入[变量](#variables)并管理本地化。 * **Typography(排版)**(仅限文本元素):配置字体、字重、字号、颜色、对齐方式、修饰效果和截断方式。 * **Spacing(间距)**:设置元素的外边距和内边距。 * **Effects(效果)**:添加投影、内阴影、背景模糊或图层模糊。 * **Animation(动画)**:添加动画效果(例如 Pulse),并配置其时长和强度。 * **Appearance(外观)**:调整不透明度和旋转角度。 * **Layout(布局)**:选择布局方向(纵向或横向),并设置子元素的分布方式。 #### 交互属性 \{#interactions-properties\} :::link 了解更多:[操作](onboarding-actions),[导航与交互](onboarding-navigation-branching) ::: **Interactions** 选项卡用于定义用户与所选元素交互时会发生什么。每个交互由一个**触发器**和一个或多个**操作**组成: * **触发器**定义*何时*发生某件事——例如,**On Tap**(用户点击该元素)。 * **动作**定义*发生什么*——例如,跳转到另一个页面或修改某个变量的值。可以为同一个触发器添加多个动作,使它们按顺序依次执行。 可以为同一个元素添加多个触发器,从而按顺序执行多个动作。 ## 左侧面板 \{#left-panel\} 左侧面板的功能会根据当前激活的按钮而变化。你可以在以下选项中切换: * [屏幕与图层](#screens-and-layers) * [添加元素](#element-selection) * [产品](#products) * [样式](#saved-styles) * [变量](#variables) * [本地化](#localization) ### 屏幕与图层 \{#screens-and-layers\} :::link 主要文章:[屏幕与图层](paywall-layout-and-products) ::: 点击图层 Layers 按钮可打开屏幕与图层面板(默认在打开流程编辑器时显示)。 该面板以树形结构展示每个屏幕的图层。屏幕上的每个元素都是一个图层,容器内的子元素会嵌套显示。你可以通过拖放来调整图层顺序。 ### 元素选择 \{#element-selection\} :::link 主要文章:[元素](builder-elements) ::: 点击加号 Plus 按钮后,左侧面板会显示可用 UI 元素及其变体列表。点击某一条目,即可将其作为新图层添加到当前屏幕。 ### 产品 :::link 主要文章:[产品](paywall-product-block) ::: 产品 Products 按钮会打开产品列表,显示流程中每个屏幕所分配的产品。 该列表为只读模式。若要为屏幕分配产品,请添加一个产品元素并在右侧面板中进行配置。若要创建或编辑产品,请使用 Adapty 看板中的 **Products** 页面。 ### 已保存的样式 \{#saved-styles\} :::info 了解更多: - [样式与外观](builder-styling) - [文字内容](onboarding-text) - [深色模式](paywall-dark-mode) ::: 点击样式 Styles 按钮可打开已保存的样式。 在这里,你可以编辑和管理全局样式。如果你的流程中有多个元素使用了相同的字体排版或颜色,可以将这些数据保存为全局样式,之后只需单击即可复用。 目前,Flow Builder 支持两种全局样式——字体样式和颜色样式。每种颜色样式都可以为深色模式单独设置一个值。 ### 变量 \{#variables\} :::link 主要文章:[变量](onboarding-variables) ::: 括号 Variables 按钮用于打开变量面板。 在这里,你可以创建和管理流程中的变量。运行时,SDK 会将变量占位符替换为实际值——用户属性、产品价格、本地化字符串等。 变量分为两个标签页: * **Custom(自定义)**:通过操作创建和控制的变量。 * **Elements(元素)**:由用户交互决定的值——例如测验答案、开关状态或标签页选择。 产品变量(价格、名称及其他产品数据)不会显示在此面板中,请在编辑文本元素时直接引用它们。 变量的用途: * **绑定文本**:显示动态内容,而非静态字符串。 * **控制可见性**:根据条件显示或隐藏元素(例如,为高级用户隐藏升级按钮)。 * **与用户交互**:访问用户输入字段中的数据,例如表单或测验。 ### 本地化 \{#localization\} :::link 主要文章:[本地化](add-flow-remote-config-locale) ::: 本地化视图让你集中管理流程中所有可翻译的内容。它以表格形式展示每个文本字符串和图片,按屏幕分组排列,并为每种语言提供单独的列。在此视图中,你可以: * 添加新语言区域并直接编辑本地化字符串。 * 跟踪翻译状态——每一行都会标记为 **Done** 或 **Missing**。 * 按屏幕筛选,或仅显示缺少翻译的内容。 * 使用 **AI Translate** 自动翻译内容,或通过 **Import/Export** 批量导入/导出翻译。 --- # File: flow-builder-recipes --- --- title: "常见流程方案" description: "在流程编辑工具中构建常见屏幕模板的分步指南。" --- 本节介绍如何在流程编辑工具中逐步构建最常见的屏幕模板——从布局选择到交互操作,逐个元素详细说明。每篇指南均独立成文,使用标准的流程编辑工具元素。 <CustomDocCardList /> 观看以下快速入门视频,了解如何创建一个基础的个性化流程: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::link 想了解更多关于构建流程的内容?请观看我们 [YouTube 播放列表](https://www.youtube.com/playlist?list=PLMksWqaZiWtM)中的分步视频教程。 ::: --- # File: basic-paywall-screen --- --- title: "创建基础付费墙页面" description: "在流程编辑器中构建标准付费墙页面的分步指南。" --- 这是最常见的付费墙模板。可以作为独立页面使用,也可以放在多页面[流程](adapty-flow-builder)的末尾。 标准付费墙界面包含标题、价值描述、功能列表、产品列表、购买按钮,以及用于恢复购买、使用条款和隐私政策的页脚链接。 ## 开始之前 \{#before-you-start\} - 在 Adapty 看板中[创建产品](create-product)。 - [将 Adapty 连接到 App Store 和 Google Play](integrate-payments)。 ## 1. 设置可复用样式 \{#set-up-reusable-styles\} 可复用样式让你只需点击一次,就能在所有屏幕上统一应用相同的字体和颜色。每个新流程都内置了一套默认文字样式(H1、正文、按钮标签等),在开始添加元素之前,先根据你的设计调整好这些样式。同时,为整个屏幕中会用到的品牌色添加颜色样式。 详细说明请参阅[样式与外观——可复用样式](builder-styling#reusable-styles)。 设置样式的步骤: 1. 在左侧面板中,打开 **Styles** Styles 面板。 2. 在 **Text** 选项卡上,点击现有样式以编辑其字体、字重、大小和颜色。仅在默认样式无法满足需求时才添加新样式。 3. 在 **Colors** 选项卡上,点击 **Plus Create style**,添加您计划在整个屏幕中复用的颜色。 ## 2. 设置页面布局 \{#2-set-up-the-screen-layout\} 页面本身充当所有内容的容器。请先配置其布局、背景和内边距,以便后续添加的元素能正确排列。 有关页面属性的完整列表,请参阅[页面与图层 — 页面设置](paywall-layout-and-products#screen-settings)。 配置页面的步骤: 1. 点击画布空白区域以选择屏幕,右侧面板将切换到屏幕设置。 2. 在 **System UI** 下,关闭 **Safe area**,使内容延伸至屏幕边缘。 3. 在 **Layout** 下,将方向设置为 **Vertical** Vertical,分布方式设置为 **Space evenly**。 4. 在 **Fill** 下,选择背景类型——纯色、渐变或图片。本示例使用带有两个色标的 **Gradient** Gradient。 ## 3. 添加关闭按钮 \{#3-add-the-close-button\} 关闭按钮用于关闭付费墙。**Close** 预设已预先配置好,无需额外设置操作。 1. 在画布上,点击 **+**。 2. 选择 **Buttons** > **Close**。 ## 4. 添加标题并与关闭按钮配对 \{#add-the-title-and-pair-it-with-the-close-button\} H1 标题位于屏幕顶部,紧靠关闭按钮。要让它们水平对齐,需要将两者包裹在一个水平容器中。 添加标题的步骤: 1. 点击 **+** > **Text** > **H1**。 2. 选中 H1 后,在右侧面板中打开 **Design** 标签页,在 **Content** 字段中编辑文字内容。 将标题与关闭按钮编为一组: 1. 在 **Layers** 面板中,点击关闭按钮图层的三点菜单 Context menu,选择 **Wrap** > **Wrap in Horizontal Container**。 2. 将 H1 图层拖入新的水平容器中。 对齐两个元素: 1. 调整关闭按钮的大小和 H1 的字体大小,使它们在同一行内排列整齐。 2. 选中水平容器后,在右侧面板中设置对齐方式和分布方式,让元素正确对齐。 ## 5. 添加价值描述 \{#add-the-value-description\} 在标题下方添加一行简短的正文,向用户说明订阅所带来的权益。 1. 点击 **+** > **Text** > **Body**。 2. 选中正文元素后,在 **Design** 标签页的 **Content** 字段中编辑文本内容。 ## 6. 添加功能列表 \{#add-the-feature-list\} 功能列表用于突出展示订阅解锁后所包含的权益。每一行包含一个图标、功能标题和简短描述。 完整的列表预设,请参阅[元素 — 列表](builder-elements#list)。 添加功能列表的步骤: 1. 点击 **+** > **List**,选择一个列表预设。Icon List 是付费墙中最常见的选择。 2. 选中每一行后,在 **Content** 字段中编辑标题和描述。 3. 若需添加或删除行,请选中列表,然后在 **Layers** 面板中使用行控件进行操作。 ## 7. 添加产品列表 \{#add-the-product-list\} 产品列表展示用户可选择的订阅选项。Products 元素会为分配给该屏幕的每个产品渲染一张卡片,其中一张卡片会自动标记为默认选项。 关于管理产品的更多信息,请参阅[设置购买](paywall-product-block)。 要添加和配置产品: 1. 点击 **+** > **Products**,选择一个布局预设。Vertical List 是最常用的布局。 2. 在画布上选择每个产品卡片,然后在 **Design** 标签页的下拉菜单中选择对应的产品。下拉菜单会显示 Adapty 看板中配置的所有产品。 3. 如需更改默认选中项,选中目标卡片并在 **Design** 标签页中启用 **Set as default product**。 4. 如需自定义折扣徽章,在 **Layers** 面板中展开产品卡片,选择徽章图层,然后在 **Content** 字段中编辑其文字。通过点击其他卡片上每个徽章图层旁的眼睛图标 Show 来隐藏相应徽章。 ## 8. 添加购买按钮 \{#add-the-purchase-button\} 购买按钮会为用户当前选中的产品发起应用内购买。`products.selectedProduct` 变量始终指向当前页面上用户所选的产品。 添加购买按钮的步骤: 1. 点击 **+** > **Buttons**,选择一个按钮预设。 2. 选中该按钮后,在右侧面板中打开 **Interactions** 标签页。 3. 点击 **Add trigger** > **On tap**,然后点击 **Add action**。 4. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为 `products.selectedProduct`。 ## 9. 添加页脚链接 \{#add-footer-links\} 页脚包含服务条款和隐私政策的链接(应用商店要求),以及一个用于恢复历史购买的按钮。 添加页脚链接的步骤: 1. 点击 **+** > **Buttons** > **Links**,此操作会添加一行,包含 Restore Purchases、Terms of Use 和 Privacy Policy。 2. 在 **Layers** 面板中,选择 **Terms of Use** 按钮。打开 **Interactions** 标签页——**Open URL** 操作已自动附加。点击该操作并输入目标 URL。 3. 对 **Privacy Policy** 按钮重复以上步骤,输入您的隐私政策 URL。 4. 保留 **Restore Purchases** 按钮不变,其操作已预先配置好。 :::tip 如果某个元素的位置偏高或偏低,或者您想在任意位置增加间距,可调整该元素的外边距(margin)和内边距(padding)。 ::: ## 后续步骤 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: show-plans-bottom-sheet --- --- title: "在底部弹出框中显示所有方案" description: "构建一个带有单一行动号召按钮、'显示所有方案'链接以及底部弹出框(展示完整产品列表)的主推付费墙。" --- 此模板首先突出展示单个精选优惠,并提供一个低调的链接供用户查看完整方案列表。点击 **Show all plans** 会从底部滑出一个弹出框,其中包含其他产品、购买按钮以及页脚链接。 当某个方案的转化率明显高于其他方案时,可以使用这种布局——底部弹窗让用户随时能以一次点击切换其他选项,同时不让主屏幕显得拥挤。 ## 开始之前 \{#before-you-start\} - 在 Adapty 看板中[创建产品](create-product)。 - [将 Adapty 连接到 App Store 和 Google Play](integrate-payments)。 ## 1. 设置屏幕布局 \{#1-set-up-the-screen-layout\} 将主图作为屏幕背景,并将其余内容集中在底部,使图片填满屏幕上方区域。 完整的屏幕属性列表,请参阅[屏幕与图层 — 屏幕设置](paywall-layout-and-products#screen-settings)。 配置屏幕的步骤如下: 1. 点击画布空白区域,选中屏幕。 2. 在 **System UI** 下,关闭 **Safe area**,让主图延伸到屏幕边缘。 3. 在 **Fill** 下,选择 **Image** Image 并上传主图。 4. 在 **Layout** 下,配置方向、间距和对齐方式,将内容固定到目标位置。对于此模板,选择 **Vertical** Vertical 方向,设置较小的间距并选择 **bottom-middle** 对齐,可将标题和按钮组合在屏幕下方。 ## 2. 添加 CTA 标题 \{#add-the-cta-heading\} 标题位于屏幕下方,紧贴在订阅按钮上方,主视觉图片填充其上方区域。 1. 点击 **+** > **Text** > **H1**。 2. 选中 H1 后,打开 **Design** 标签页,在 **Content** 字段中编辑文本内容。 ## 3. 添加底部弹出层及其标题 \{#add-the-bottom-sheet-and-its-title\} 底部弹出层是一种从屏幕底部滑出的布局容器。现在先将它设置为可见状态——接下来几步你会往里填充内容,填好后再隐藏它。隐藏状态下的元素无法编辑,所以在填充完成之前,弹出层需要保持可见。 关于底部弹出层及其他布局容器的详细说明,请参阅[元素——布局](builder-elements#layout)。 按以下步骤添加底部弹出层及其标题: 1. 点击 **+** > **Layout** > **Bottom Sheet**。 2. 在 **Layers** 面板中,展开底部弹出层,选择 **Title** 图层,然后在 **Design** 标签页的 **Content** 字段中填写内容——例如 `Choose your plan`。 ## 4. 在底部弹窗中添加产品列表 \{#add-the-product-list-inside-the-bottom-sheet\} 将所有产品放入底部弹窗中。其中一个产品还将驱动主 CTA 按钮上显示的价格。 更多产品管理信息,请参阅[设置购买](paywall-product-block)。 按以下步骤添加和配置产品: 1. 点击 **+** > **Products**,选择一种布局预设。垂直列表适合大多数场景。该元素将出现在屏幕上,位于底部弹窗之外。 2. 在 **Layers** 面板中,将 Products 图层拖入底部弹窗内的 **Content** 容器中。 3. 在画布上选择每张产品卡片,然后在 **Design** 标签的下拉菜单中选择对应产品。 ## 5. 在底部弹窗中添加购买按钮 \{#add-the-purchase-button-inside-the-bottom-sheet\} 底部弹窗需要有自己的购买按钮,用于购买用户从列表中选择的方案。 1. 点击 **+** > **Buttons**,选择一个按钮预设。 2. 在 **Layers** 面板中,将新按钮拖入底部弹窗内的 **Content** 容器。 3. 选中按钮后,在右侧面板打开 **Interactions** 标签页。 4. 点击 **Add trigger** > **On tap**,然后点击 **Add action**。 5. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为 `products.selectedProduct`。 ## 6. 在底部弹窗中添加底部链接 \{#add-the-footer-links-inside-the-bottom-sheet\} :::important 不要在按钮内嵌套的文本中使用[内联链接](onboarding-text#inline-link)。请改为在按钮本身上设置 **Open URL** 操作。 ::: 使用条款、隐私政策和恢复购买链接位于弹窗底部,主屏幕保持简洁。 1. 点击 **+** > **Buttons** > **Links**,此操作会添加一行,包含 Restore Purchases、Terms of Use 和 Privacy Policy。 2. 在 **Layers** 面板中,将 Links 行拖入底部弹窗的 **Content** 容器内。 3. 在 **Layers** 面板中,选择 **Terms of Use** 按钮。打开 **Interactions** 标签页,将您的条款 URL 粘贴到 **Open URL** 字段中。 4. 对 **Privacy Policy** 按钮重复上述操作,填入您的隐私政策 URL。 5. 保持 **Restore Purchases** 链接不变,其操作已预先配置好。 ## 7. 隐藏底部弹窗 \{#hide-the-bottom-sheet\} 底部弹窗的内容设置完成后,将其隐藏,使其默认不显示在屏幕上。用户在最后一步点击 **Show all plans** 后即可看到它。 在 **Layers** 面板中,选中底部弹窗,将其状态设置为 **Hide** Hide。弹窗仍保留在图层树中,但不会在画布上渲染。 ## 8. 添加主订阅按钮 \{#add-the-main-subscribe-button\} 屏幕上的主按钮只需轻点一下即可让用户订阅月度计划。按钮标签使用月度产品的价格变量,确保按钮内容与产品信息保持同步。 1. 在 **Layers** 面板中,点击屏幕,确保新元素添加到根层,而不是底部弹窗内。 2. 点击 **+** > **Buttons**,选择一个按钮预设。 3. 选中按钮后,打开 **Design** 标签页,将光标定位到 **Content** 字段。点击 Variable icon 并选择主产品的价格变量。在变量两侧补充完整的标签文字——例如 `Subscribe for {price}/month`。 4. 切换到 **Interactions** 标签,点击 **Add trigger** > **On tap** > **Add action**。 5. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为所需的产品。与底部弹窗按钮不同,此按钮指向的是特定产品,而非 `products.selectedProduct`。 ## 9. 添加"显示所有方案"链接 \{#add-the-show-all-plans-link\} 订阅按钮下方的文字链接,点击后会展开底部弹窗。将其设置为带有 **Button Label** 样式的文本元素,既保持界面简洁,又可以绑定操作。 有关显示/隐藏操作的更多信息,请参阅[操作——显示/隐藏元素](onboarding-actions#showhide-elements)。 添加链接的步骤: 1. 在 **Layers** 面板中选中该屏幕,点击 **+** > **Text** > **Button Label**。 2. 选中文本元素后,将 **Content** 字段的内容修改为 `Show all plans`。 3. 打开 **Interactions** 标签页,点击 **Add trigger** > **On tap** > **Add action**。 4. 将 **Action** 设置为 **Show**,然后从下拉菜单中选择底部弹出层元素。 ## 下一步 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: paywall-with-tabs --- --- title: "创建带标签页的付费墙" description: "构建一个带有两个标签页的付费墙界面,可在不同功能列表、产品组和购买操作之间切换。" --- 此模板使用标签页在单个界面上切换同一优惠的两种变体。每个标签页都包含独立的功能列表、产品列表和购买按钮。点击标签页即可切换显示内容,无需离开当前界面——适用于按层级、计费周期或目标受众细分来展示不同套餐。 ## 开始之前 \{#before-you-start\} - 在 Adapty 看板中[创建产品](create-product)。 - [将 Adapty 连接到 App Store 和 Google Play](integrate-payments)。 ## 1. 设置屏幕布局 \{#1-set-up-the-screen-layout\} 屏幕作为关闭按钮、标题、标签页及标签页内容的容器。本示例中背景使用的是图片,纯色或渐变效果的配置方式相同。 有关屏幕属性的完整列表,请参阅[屏幕与图层 — 屏幕设置](paywall-layout-and-products#screen-settings)。 要配置屏幕: 1. 点击画布空白区域以选中屏幕。 2. 在 **System UI** 下,关闭 **Safe area**,使背景延伸至屏幕边缘。 3. 在 **Fill** 下,选择背景类型并进行配置。此示例使用 **Image** Image,但纯色或渐变效果的设置方式相同。 4. 在 **Layout** 下,将方向设置为 **Vertical** Vertical,并配置间距和对齐方式,使元素从顶部依次排列,标签页内容填满剩余空间。 ## 2. 添加关闭按钮 \{#2-add-the-close-button\} 关闭按钮用于关闭付费墙。**Close** 预设已预先配置好,无需设置任何操作。 1. 在画布上,点击 **+**。 2. 选择 **Buttons** > **Close**。 ## 3. 添加标题并与关闭按钮配对 \{#add-the-title-and-pair-it-with-the-close-button\} 标题位于屏幕顶部,紧邻关闭按钮。要让它们水平对齐,需将两者包裹在一个横向容器中。 添加标题的步骤: 1. 点击 **+** > **Text** > **H1**。 2. 选中 H1 后,打开 **Design** 选项卡,在 **Content** 字段中编辑文本内容。 将标题与关闭按钮组合在一起: 1. 在 **Layers** 面板中,点击关闭按钮图层上的三点菜单 Context menu,选择 **Wrap** > **Wrap in Horizontal Container**。 2. 将 H1 图层拖入新建的水平容器中。 要对齐这两个元素: 1. 调整关闭按钮的大小和 H1 的字号,让它们在同一行里显示得自然舒适。 2. 选中横向容器后,在右侧面板中设置对齐方式和分布方式,使各元素正确排列。 ## 4. 添加标签页并配置其标签 \{#add-the-tabs-and-configure-their-labels\} Tabs 元素将页面区域拆分为可切换的内容面板。每个标签页都有独立的内容容器,用户选中该标签页时即可显示对应内容。 有关 Tabs 元素的详细介绍,请参阅[元素 — Tabs](builder-elements#tabs)。有关可选择组的详细介绍,请参阅[可选择元素与组](flow-selectable-elements)。 添加标签页的步骤如下: 1. 点击 **+** > **Tabs**,选择一个预设样式——Segment control、Button Tabs 或 Underline。 2. 在画布或 **Layers** 面板中选中每个标签页的名称,在 **Design** 标签的 **Content** 字段中编辑标签文本——例如 `Premium` 和 `Pro`。 ## 5. 在第一个标签页中添加功能列表 \{#add-a-feature-list-to-the-first-tab\} 在第一个标签页中添加简洁的功能列表,让用户清楚了解该方案包含的内容。 完整的列表预设内容,请参阅[元素 — 列表](builder-elements#list)。 添加功能列表的步骤: 1. 点击 **+** > **List**,选择一个列表预设。Icon List 是付费墙中最紧凑的样式。该元素会出现在图层树的末尾。 2. 选中每一行后,在 **Content** 字段中编辑标题文本。 3. 在**Layers**面板中,将列表拖入第一个标签页的**Content**容器内。 ## 6. 在第一个标签页添加产品列表 \{#add-the-product-list-to-the-first-tab\} 产品列表展示第一个标签页的订阅选项。Products 元素会为分配给该页面的每个产品渲染一张卡片,并创建自己的可选组。 更多产品管理内容,请参阅[设置购买](paywall-product-block)。 添加并配置产品的步骤如下: 1. 点击 **+** > **Products**,选择一个布局预设。Vertical List 适合堆叠式方案展示。该元素会出现在图层树的末尾。 2. 在画布上选中每张产品卡片,然后在 **Design** 标签页的下拉菜单中选择对应的产品。 3. 在 **Layers** 面板中,将 Products 图层拖入第一个标签页的 **Content** 容器。 ## 7. 在第一个标签页中添加购买按钮 \{#add-the-purchase-button-to-the-first-tab\} 购买按钮会为用户在第一个标签页中选择的产品发起应用内购买。按钮标签显示所选产品的价格,因此始终与用户的选择保持同步。 有关购买操作的详细信息,请参阅[操作 — 购买](onboarding-actions#purchase)。 添加并配置购买按钮的步骤: 1. 点击 **+** > **Buttons**,选择一个按钮预设。该元素会出现在图层树的末尾。 2. 选中按钮后,打开 **Design** 标签页,将光标放置在 **Content** 字段中。点击变量图标 Variable icon,选择 `products.selectedProduct`,再选择 `prod_price` 属性——完整变量解析为 `products.selectedProduct.prod_price`。在变量周围补充标签文字,例如 `Subscribe for {prod_price}`。 3. 切换到 **Interactions** 标签页,点击 **Add trigger** > **On tap** > **Add action**。 4. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为 `products.selectedProduct`。 5. 在 **Layers** 面板中,将按钮拖入第一个标签页的 **Content** 容器内。 ## 8. 将第一个标签页的内容复制到第二个标签页 \{#copy-the-first-tabs-content-into-the-second-tab\} 无需从头重建相同的结构,只需将第一个标签页中的功能列表、产品列表和购买按钮复制到第二个标签页,之后只更新相应的值即可。 复制内容的方法: 1. 在 **Layers** 面板中,展开第一个选项卡的 **Content** 容器。 2. 选中其中的每个元素(功能列表、产品、购买按钮),按 ⌘C / Ctrl+C 复制,再按 ⌘V / Ctrl+V 粘贴。复制的元素会出现在图层树的末尾。 3. 将每个复制的元素拖入第二个选项卡的 **Content** 容器中。 ## 9. 更新第二个标签页的内容 \{#update-the-second-tabs-content\} 第二个标签页目前与第一个完全相同。逐一修改其中的元素,使其反映第二个方案的内容。 更新第二个标签页的步骤: 1. 编辑第二个标签页中功能列表的内容,使各行与第二个方案的功能对应。 2. 在第二个标签页的 Products 元素中选中每个产品卡片,从下拉列表中为其分配第二个方案的产品。此 Products 元素会自动成为一个独立的可选组(`products2`)。 3. 选中第二个标签页中的购买按钮。在 **Design** 标签页的 **Content** 字段中,将价格变量从 `products.selectedProduct.prod_price` 改为 `products2.selectedProduct.prod_price`。 4. 切换到 **Interactions** 标签页,将 **Purchase** 操作的 **Product** 从 `products.selectedProduct` 更新为 `products2.selectedProduct`。 ## 10. 添加共享底部链接 \{#add-the-shared-footer-links\} 无论当前激活的是哪个标签页,服务条款、隐私政策和恢复购买链接都应始终可见。将它们添加在屏幕层级——位于两个标签页内容容器之外——这样两个标签页可以共用这些链接。 添加底部链接的步骤: 1. 点击 **+** > **Buttons** > **Links**。这会在图层树末尾添加一行,包含 Restore Purchases、Terms of Use 和 Privacy Policy,正好位于屏幕根级——而不是嵌套在某个标签页内。 2. 在 **Layers** 面板中,选择 **Terms of Use** 按钮。打开 **Interactions** 标签页,将您的条款 URL 粘贴到 **Open URL** 字段中。 3. 对 **Privacy Policy** 按钮重复上述操作,填入您的隐私政策 URL。 4. **Restore Purchases** 链接保持不变,其操作已预先配置好。 ## 后续步骤 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: paywall-features-per-product --- --- title: "按产品显示不同功能" description: "使用条件可见性,根据用户选择的产品显示不同的功能列表。" --- 此模板通过条件显示来突出展示不同方案对应的功能列表。屏幕上显示两个产品(例如 Pro 和 Pro+),用户选择不同产品时,对应的功能列表会随之切换。其中一个产品被设为默认选中,因此屏幕初始加载时会显示该产品的功能列表。 ## 开始之前 \{#before-you-start\} - 在 Adapty 看板中[创建产品](create-product)。 - [将 Adapty 连接到 App Store 和 Google Play](integrate-payments)。 ## 1. 设置屏幕布局 \{#1-set-up-the-screen-layout\} 屏幕是所有内容的容器。在本示例中,背景使用的是图片,但纯色或渐变效果的设置方式完全相同。 有关屏幕属性的完整列表,请参阅[屏幕与图层 — 屏幕设置](paywall-layout-and-products#screen-settings)。 配置屏幕的步骤如下: 1. 点击画布空白区域以选中屏幕。 2. 在 **System UI** 下,禁用 **Safe area**,使背景延伸到屏幕边缘。 3. 在 **Fill** 下,选择背景类型并进行配置。本示例使用 **Image** Image,纯色或渐变的配置方式相同。 4. 在 **Layout** 下,将方向设置为 **Vertical** Vertical,并配置间距和对齐方式,使元素从顶部向下排列,内容填充剩余空间。 ## 2. 添加关闭按钮 \{#2-add-the-close-button\} 关闭按钮用于关闭付费墙。**Close** 预设已预先配置好,无需额外设置操作。 1. 在画布上,点击 **+**。 2. 选择 **Buttons** > **Close**。 ## 3. 添加标题并与关闭按钮配对 \{#add-the-title-and-pair-it-with-the-close-button\} 标题位于屏幕顶部,与关闭按钮并排显示。要让它们水平对齐,需要将两者放入一个水平容器中。 添加标题的步骤: 1. 点击 **+** > **Text** > **H1**。 2. 选中 H1 后,打开 **Design** 标签页,在 **Content** 字段中编辑文本内容。 将标题与关闭按钮组合在一起: 1. 在 **Layers** 面板中,点击关闭按钮图层上的三点菜单 Context menu,选择 **Wrap** > **Wrap in Horizontal Container**。 2. 将 H1 图层拖入新建的水平容器中。 对两个元素进行对齐: 1. 调整关闭按钮大小和 H1 字体大小,使它们在同一行上排列得舒适协调。 2. 选中水平容器后,在右侧面板中设置对齐方式和分布方式,使元素正确对齐。 ## 4. 添加产品列表 \{#add-the-product-list\} 添加用户可以选择的产品,并将其中一个设为默认,这样页面在首次加载时就有一个有意义的初始状态。 关于产品管理的更多内容,请参阅[设置购买](paywall-product-block)。 添加和配置产品的步骤: 1. 点击 **+** > **Products**,选择一个布局预设。竖向列表适合这个模板。 2. 在画布上选中每个产品卡片,然后在 **Design** 标签页的下拉菜单中选择对应产品。 3. 选中你想默认选中的卡片(例如 Pro+),在 **Design** 标签页中启用 **Set as default product**。 ## 5. 为第一个产品添加功能列表 \{#add-the-feature-list-for-the-first-product\} 第一个功能列表描述默认产品,仅在用户选中第一个产品时可见。 更多关于条件可见性的内容,请参阅[条件可见性](onboarding-element-visibility)。 :::tip 你也可以只添加一个列表,然后对列表内的文本元素设置条件,让同一个列表根据所选产品自动适配,而不必分别创建两个列表。请参阅[添加条件文本](onboarding-text#add-conditional-text)。 ::: 按照以下步骤添加并配置功能列表: 1. 点击 **+** > **List**,选择一个紧凑列表预设。Icon List 很适合用于付费墙。 2. 选中每一行后,在 **Content** 字段中编辑标题,描述第一个产品的功能特点。 3. 保持列表选中状态,打开 **Design** 标签页。在 **Visibility** 下,选择 **Conditional** Conditional。 4. 设置条件,使该列表仅在第一个产品被选中时显示。匹配 `products.selectedProduct.prod_title` 变量。对于 **Value**,点击变量图标 `{}`,选择第一个产品卡片,再选择其 `prod_title` 属性——比较结果将解析为该产品的标题。 ## 6. 为第二个产品添加功能列表 \{#add-the-feature-list-for-the-second-product\} 对第二个产品重复相同的操作。这两个列表是互斥的——同一时间只有一个可见,具体显示哪个取决于当前选中的产品。 添加第二个功能列表的步骤: 1. 点击 **+** > **List**,选择相同的紧凑预设以保持视觉一致性。 2. 编辑每一行,填写第二个产品的功能描述。 3. 在 **Visibility** 下,选择 **Conditional** Conditional,设置与第 5 步相同的条件,但将 **Value** 变量选择器指向第二个产品卡片的 `prod_title`。 ## 7. 添加购买按钮 \{#add-the-purchase-button\} 购买按钮会针对用户选择的产品发起应用内购买。按钮标签会显示所选产品的价格,因此当用户切换方案时,标签会随之更新。 关于"购买"动作的详细说明,请参阅[动作 — 购买](onboarding-actions#purchase)。 添加并配置购买按钮的步骤如下: 1. 点击 **+** > **Buttons**,选择一个按钮预设。 2. 选中按钮后,打开 **Design** 标签页,将光标定位到 **Content** 字段。点击变量图标 Variable icon,选择 `products.selectedProduct`,再选择 `prod_price` 属性——完整变量解析为 `products.selectedProduct.prod_price`。在标签的其他文字中包裹该变量,例如 `Subscribe for {prod_price}`。 3. 切换到 **Interactions** 标签页,点击 **Add trigger** > **On tap** > **Add action**。 4. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为 `products.selectedProduct`。 ## 8. 添加页脚链接 \{#add-the-footer-links\} 使用条款、隐私政策和恢复购买按钮位于主要内容下方。 添加页脚链接的步骤: 1. 点击 **+** > **Buttons** > **Links**。这会在图层树末尾添加一行,包含 Restore Purchases、Terms of Use 和 Privacy Policy。 2. 在 **Layers** 面板中,选择 **Terms of Use** 按钮。打开 **Interactions** 标签页,将您的条款 URL 粘贴到 **Open URL** 字段中。 3. 对 **Privacy Policy** 按钮重复上述步骤,填入您的隐私政策 URL。 4. 保留 **Restore Purchases** 链接不变,其操作已预先配置好。 ## 后续步骤 \{#next-steps\} - [保存并发布你的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: show-offer-on-close --- --- title: "用户点击关闭时显示优惠" description: "拦截第一次点击关闭的操作,在用户离开付费墙前展示最后机会优惠。" --- 当用户点击关闭按钮时,他们即将不付款就离开。这个方案会拦截第一次点击关闭的操作:不立即关闭付费墙,而是弹出一个最后机会优惠的浮层。当用户关闭该优惠后,关闭按钮才正常工作并退出流程。 整个逻辑只需要一个自定义布尔变量和一个条件操作: - `close_tapped` 初始值为 `False`。 - 第一次点击关闭按钮时,会显示优惠覆盖层并将 `close_tapped` 设为 `True`。 - 此后再次点击关闭按钮时,会退出流程。 ## 开始之前 \{#before-you-start\} - 构建一个带有关闭按钮的付费墙页面——例如,参考[创建基础付费墙页面](basic-paywall-screen)。 - [创建一个产品](create-product),并为其添加要在弹窗中推广的[优惠](offers)。 ## 1. 创建变量 \{#1-create-the-variable\} 有关自定义变量的更多信息,请参阅[变量](onboarding-variables#custom-variables)。 1. 在左侧面板中,点击 **{ }** 图标以打开 **Variables**。 2. 在 **Custom** 标签页中,点击 **+**。 3. 将变量命名为 `close_tapped`,并将 **Value Type** 设置为 **Boolean**。将 **Initial Value** 保持为 **False**。 4. 点击 **Create variable**。 ## 2. 构建优惠弹层 \{#2-build-the-offer-overlay\} 弹层是一个固定在设备屏幕上、覆盖在付费墙之上的容器。构建时请保持可见状态——隐藏的元素无法编辑,因此等内容布局完成后,再在第 4 步将其隐藏。 1. 点击 **+** > **Layout** > **Vertical Container**。 2. 选中该容器后,打开 **Design** 标签页,将 **Position** 设置为 **Fixed**。将水平对齐方式设置为 **Left & Right**,垂直对齐方式设置为 **Top**。由于没有垂直居中选项,请输入顶部偏移值(例如 `300`),将覆盖层移至屏幕中部位置。 3. 在 **Fill** 下设置背景——可以是纯色或图片。容器默认为透明,若不填充背景,付费墙内容将透过覆盖层显示出来。 4. 添加优惠内容。在 **Layers** 面板中选中该容器,点击 **+** > **Text** > **H2**。如需显示折扣价格,可在 **Content** 字段中插入[优惠变量](onboarding-variables#product-variables),例如 `offer_price`。 5. 点击 **+** > **Products**,选择一个布局预设,并将其拖入覆盖层。在画布上选中产品卡片,然后在 **Design** 标签页中选择产品和优惠。 6. 点击 **+** > **Buttons**,选择一个按钮预设,并将其拖入覆盖层。在 **Interactions** 标签页中,点击 **Add trigger** > **On tap** > **Add action**,将 **Action** 设置为 **Purchase**,将 **Product** 设置为您的优惠产品。 ## 3. 为遮罩层添加关闭按钮 \{#add-the-dismiss-button-to-the-overlay\} 遮罩层需要有自己的关闭按钮。该按钮的操作应隐藏遮罩层,而不是关闭整个流程。 1. 选中遮罩层后,点击 **+** > **Buttons** > **Close flow**。 2. 选中该按钮后,打开 **Interactions** 标签页。预设中已预配置了 **Close Flow** 操作。点击该操作,将其类型改为 **Hide element**,并将目标设置为遮罩层容器。 :::important 不要在弹窗的关闭按钮上保留预配置的 **Close Flow** 操作——这会关闭整个流程,而不是隐藏弹窗。 ::: ## 4. 隐藏遮罩层 \{#hide-the-overlay\} 遮罩层必须在第一次点击关闭之前保持不可见。 在 **Layers** 面板中,选中遮罩层容器,将其状态设置为 **Hide** Hide。遮罩层仍保留在图层树中,但不再在画布上渲染。 ## 5. 设置关闭按钮 \{#5-set-up-the-close-button\} 将关闭按钮的默认操作替换为基于 `close_tapped` 条件分支的条件操作。 有关条件操作的更多信息,请参阅[操作——条件操作](onboarding-actions#conditional-actions)。 1. 选择付费墙的关闭按钮——即屏幕上的那个,而非遮罩层的关闭按钮。 2. 打开 **Interactions** 标签页,点击预配置的 **Close Flow** 动作,将其类型更改为 **Conditional Action**。 3. 在 **if** 块中,点击 **Add condition** 并设置:`close_tapped` **Equals** **False**。 4. 在 **then** 块中,添加两个动作: - **Set Variable**:将 `close_tapped` 设置为 **True**。 - **Show element**:将目标设置为优惠弹层。 5. 在 **else** 块中,添加 **Close Flow** 操作。 现在,第一次点击关闭按钮会显示优惠。用户关闭优惠后,再次点击关闭按钮将匹配 **else** 分支并退出流程。 ## 后续步骤 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: strikethrough-price --- --- title: "显示带折扣标识的划线价格" description: "在年度计划的月均价格旁边划掉月度计划的价格,让折扣一目了然。" --- 在实际价格旁边显示一个划掉的价格,能让折扣一目了然。本教程将改造年度产品卡片,在年度计划的月均价格旁边显示月度计划的月费价格(带删除线),并在顶部添加折扣标识。 删除线格式会应用于整个文本元素——你无法只对较大文本中的某个变量添加删除线。因此,带删除线的原价需要放在独立的文本元素中,与折扣价并排放置在一个水平容器里。 :::tip 如果只需为同一产品显示一个虚高的"原价"——例如以删除线显示当前价格的两倍——可以直接添加现成的[原价元素](onboarding-text#add-an-old-price)。本文介绍的是删除线价格来自另一个产品真实价格的情况。 ::: ## 开始之前 \{#before-you-start\} - 搭建一个包含产品的付费墙页面——例如,参考[创建基础付费墙页面](basic-paywall-screen)。本文以提供年度和月度产品的付费墙为例进行说明。 ## 1. 叠加价格行 \{#1-stack-the-price-rows\} 年度卡片目前只有一行价格——年度产品的 `prod_price` 变量后跟 `/year`,显示效果例如 `$29.99/year`。在它上方再添加一行月度价格。 1. 在 **Layers** 面板中,选择年度产品卡片内的价格文本。由于它已处于卡片的垂直容器中,复制的副本将堆叠在其下方。 2. 点击图层上的三点菜单 Context menu,选择 **Duplicate**。副本出现在原件下方。下一步中,上方文本将成为月度价格行,下方文本保留年度价格。 ## 2. 将月度行拆分为两个价格 \{#2-split-the-monthly-row-into-two-prices\} 月度行包含两个文本元素:划线的月度方案价格和年度方案的每月均摊价格。 :::important 变量选择器仅显示当前流程屏幕中已有产品的价格。如需引用不在流程中的产品,请创建一个空白屏幕,在其中添加**产品**元素并将该产品分配给它,同时确保没有任何导航操作会跳转到该屏幕。 ::: 1. 选中顶部文本元素,点击其三点菜单,选择 **Wrap** > **Wrap in Horizontal Container**。 2. 点击新容器内文本层的三点菜单,选择 **Duplicate**。 3. 选择第一个文本元素,清空其 **Content** 字段。点击变量图标 Variable icon,选择月付产品,然后选择其 `prod_price_per_month` 属性。在变量后输入 `/month`。 4. 在 **Design** 标签页的 **Typography** 下,将 **Decoration** 设置为 **Strikethrough** Strikethrough(参见[样式与外观 — Decoration](builder-styling#decoration))。 5. 选择第二个文本元素,以相同方式替换其内容,但选择年付产品的 `prod_price_per_month` 属性。在变量后输入 `/month`。 ## 3. 弱化年付价格 \{#tone-down-the-yearly-price\} 复制的月付价格继承了原有的价格样式,因此三个价格目前大小和字重完全相同。让年付价格更低调一些,使月付行更加突出。 1. 在画布上,选中最下方的文本元素——即年付价格。 2. 在元素上方的工具栏中,打开文本样式下拉菜单,选择一种更低调的样式——例如 **Caption**。或者,在 **Design** 标签页的 **Typography** 下选择其他样式。 ## 4. 添加折扣徽章 \{#add-the-discount-badge\} 徽章用于在价格旁边突出显示节省金额。 1. 点击 **+** > **Badge**。 2. 在 **Layers** 面板中,将徽章拖入年度产品内部,放在包含价格的垂直容器与单选按钮之间。 3. 选中徽章的文本图层,编辑 **Content** 字段——例如填入 `Save 75%`。 折扣百分比没有对应的变量——请根据你的实际价格计算后,以静态文本方式输入。 现在,年度卡片以月度套餐为参照价格:划线的月价、年度套餐折算的月均价,以及折扣徽章并排显示在同一行,完整年价显示在下方。 ## 后续步骤 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 --- # File: onboarding-flow-tutorial --- --- title: "构建个性化用户引导流程" description: "通过一个完整示例,逐步了解如何构建多屏幕用户引导流程——包括页面设计、内容编排、导航设置和条件分支。" --- 流程编辑工具中的多屏幕流程是由导航动作连接的一系列屏幕。流程可以保持线性,也可以根据用户在前面屏幕上的输入进行分支跳转。本教程将以一个四屏幕用户引导流程为例,完整演示整个过程——创建屏幕、构建内容、连接导航以及添加条件分支。 示例包含以下内容: - 一个**姓名输入**,将用户姓名作为变量用于个性化内容。 - 一个**单选测验**,根据答案决定用户进入哪个下一屏。 - **两条分支路径**,分别为不同目标受众定制文案。 - 一个**付费墙**作为最终屏幕。 同样的模式适用于任何根据用户输入个性化内容的流程。 更喜欢视频格式?以下快速入门教程从头到尾演示了相同的流程: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 开始前的准备 \{#before-you-start\} - 在 Adapty 看板中[创建产品](create-product)。示例流程使用两个产品——年度订阅和月度订阅。 - [将 Adapty 连接到 App Store 和 Google Play](integrate-payments)。 ## 1. 设置可复用样式 \{#set-up-reusable-styles\} 可复用样式让你只需单击一次,即可在每个屏幕上统一应用排版和颜色。颜色样式包含浅色和深色两种变体,因此流程可自动支持这两种主题。 详细说明请参阅[样式与外观——可复用样式](builder-styling#reusable-styles)。 设置样式的步骤: 1. 在左侧面板中,打开 **Styles** Styles 面板。 2. 在 **Colors** 标签页,点击 **Plus Create style** 添加需要复用的颜色。为每种颜色选择 Light 值,切换到 Dark 标签页后再选择 Dark 值。 3. 在 **Text** 标签页,点击已有样式可编辑其字体、字重和字号,或点击 **Plus Create style** 添加自定义预设。 ## 2. 创建屏幕 \{#2-create-the-screens\} 流程是一组屏幕的序列。先为第一个屏幕配置共用基础(布局、背景和安全区域),再通过复制来生成其余屏幕。这样每个屏幕都能共享同一基础,只需设置一次即可。 有关屏幕管理的更多内容,请参阅[屏幕与图层——管理屏幕](paywall-layout-and-products#manage-screens)。 按以下步骤设置屏幕: 1. 点击第一个屏幕画布的空白区域,打开屏幕设置。 2. 在 **System UI** 下,禁用 **Safe area**,使背景和对齐边缘的元素能够延伸至屏幕边缘。 3. 在 **Fill** 下,选择背景类型并进行配置——例如,选择 **Image** Image,使其显示在流程每个屏幕的最底层。 4. 在 **Layout** 下,将方向设置为 **Vertical** Vertical,并选择适合你设计的分布方式。 5. 在左侧面板的 **Screens** 部分,点击第一个屏幕上的三点菜单 Context menu,选择 **Duplicate**。重复此操作,直到共有四个屏幕——第二条分支路径稍后通过复制第一条来添加。 6. 将每个屏幕重命名以匹配其对应的角色——在本示例中分别为:`Welcome`、`Quiz`、`Rock path` 和 `Paywall`。 ## 3. 构建介绍页面 \{#build-the-introduction-screen\} 第一个页面通常定下整体基调——包括标题、功能列表和引导用户进入后续流程的行动号召按钮。在我们的示例中,这就是欢迎页面。 在 **Screens** 面板中点击 **Welcome** 页面,然后添加以下元素: 1. 添加主图。点击 **+** > **Media** > **Image**,上传您的图片,并根据需要调整边距。 2. 添加标题:点击 **+** > **Text**,从已保存的文字样式中选择一种标题样式,然后编辑 **Content** 字段。 3. 添加功能列表。点击 **+** > **List** > **Icon Cards**,然后编辑每张卡片上的图标和标签。 4. 在底部添加一个主导航按钮。该按钮的跳转动作将在导航步骤中进行配置。 ## 4. 构建输入与问答界面 \{#4-build-the-input-and-quiz-screen\} 第二个界面用于收集用户输入。在本示例中,它会询问用户名称以及一个单选题,用户的回答将决定后续显示哪条路径。 如需了解更多关于输入框和问答的内容,请参阅[输入与表单](builder-inputs-and-forms)和[调查与问答](onboarding-quizzes)。 在 **Screens** 面板中点击 **Quiz** 界面,然后添加元素。界面上的每组内容——介绍、问题 + 输入框、问题 + 问答题——各自放置在独立的垂直容器中,以便相关元素在视觉上保持聚合。 1. 添加引导页标题和正文。点击 **+** > **Text** > **H1** 添加标题,点击 **+** > **Text** > **Body** 添加说明文字。 2. 将引导页内容分组。点击 **+** > **Layout** > **Vertical Container**,将新容器拖到图层树顶部,再把 H1 和正文拖入其中。 3. 添加第一个问题和输入框。点击 **+** > **Text** 添加问题标题,然后点击 **+** > **Inputs** > **Text** 添加输入字段。 4. 在 **Design** 标签页中设置输入框的 **Element ID**——本例中为 `name`。这样其他屏幕就可以通过变量引用该值。 5. 将说明文字和输入框放入垂直容器中进行分组,方式与前面的引导步骤相同。 6. 添加第二个问题和测验题。点击 **+** > **Text** 添加说明文字,然后点击 **+** > **Quiz** 并选择一个布局预设,例如 Icon Options。配置选项——在本示例中为 `Rock` 和 `Hip hop`。 7. 用同样的方式将说明文字和测验题放入垂直容器中进行分组。 8. 设置选项 ID。选中每个测验选项,打开 **Interactions** 标签页,并设置其 **Element ID**。这些 ID 将在后续的条件导航中被引用。 9. 将测验切换为单选模式:点击画布空白区域打开 **Screen settings**,向下滚动至 **Selectable Groups**,点击测验组的名称,将类型设置为 **Single choice**。 10. 在底部添加一个主按钮——即触发分支逻辑的"下一步"按钮。 ## 5. 构建第一个分支路径 \{#build-the-first-branching-path\} 每个路径页面都会针对特定的目标受众定制内容。在本示例中,摇滚路径展示以摇滚为主题的内容——歌单、艺人和推荐。 如需了解更多关于变量的信息,请参阅[变量](onboarding-variables)。 构建页面的步骤: 1. 在 **Screens** 面板中,点击 **Rock path** 屏幕。 2. 添加一个标题。将光标放在 **Content** 字段中需要插入个性化内容的位置,点击变量图标 Variable icon,然后打开 **Elements** 标签页。选择包含输入框的屏幕——在我们的示例中为 **Quiz**——再选择输入框的值变量。选择器会将其解析为 `<elementId>.value`——在我们的示例中为 `name.value`。运行时,标题会根据用户输入的内容动态更新。 3. 将正文内容作为附加文本元素添加,针对此路径的目标受众进行调整。 4. 在底部添加一个主要按钮。 ## 6. 构建第二条分支路径 \{#build-the-second-branching-path\} 路径页面通常共用同一套布局——只有文案内容不同。复制第一条路径页面并更新内容即可。 操作步骤如下: 1. 在 **Screens** 面板中,选中第一条路径页面,按 ⌘D / Ctrl+D 进行复制。副本会出现在页面列表末尾。 2. 重命名副本——在本示例中命名为 `Hip hop path`——然后将其拖到页面列表中的正确位置,使其紧邻被复制的页面。 3. 为另一目标受众更新正文内容。个性化标题仍然有效——变量会自动沿用。 ## 7. 构建付费墙 \{#build-the-paywall\} 最后一个界面是付费墙——用户可以在这里订阅。如需完整了解付费墙的构建流程,请参阅[创建基础付费墙界面](basic-paywall-screen)。以下内容为该流程的精简版。 在 **Screens** 面板中点击 **Paywall** 界面,然后添加以下元素: 1. 在顶部添加一个 **Horizontal Container**,并在其中放入 **Close** 按钮。Close 预设已预先配置好,开箱即用。 2. 添加主图、标题(使用与路径界面相同的个性化变量)以及作为辅助文案的副标题。 3. 添加产品:点击 **+** > **Products**,选择 **Vertical List**。在 **Design** 标签页的下拉菜单中为每张卡片分配一个产品。 4. 点击默认产品的卡片,启用 **Set as default product**,使其在页面加载时预先选中。 5. 添加购买按钮。点击 **+** > **Buttons**,选择一个预设样式。在 **Interactions** 标签页中,点击 **Add trigger** > **On tap** > **Add action**,将 **Action** 设置为 **Purchase**,**Product** 设置为 `products.selectedProduct`。 6. 将 **Button** > **Links** 模板添加到屏幕中。该模板包含三个页脚链接:Restore Purchases、Terms of Use 和 Privacy Policy。 Restore 链接已预配置完成。如需配置其他链接,请选中相应按钮元素,打开 **Interactions** 选项卡,并为 **Open URL** 操作设置目标地址。 ## 8. 连接各屏幕之间的导航 \{#wire-navigation-between-the-screens\} 屏幕之间不会自动建立连接。使用 **On tap** 触发器和 **Navigate to** 动作,将每个屏幕的主按钮与下一个屏幕关联起来。如果某个屏幕需要根据用户输入进行分支跳转,则使用**条件动作**替代。 有关导航和条件动作的详细说明,请参阅[导航与交互](onboarding-navigation-branching)和[动作 — 条件动作](onboarding-actions#conditional-actions)。 以下是示例流程的导航连接方式: 1. **从介绍页面进行静态导航。** 打开 Welcome 屏幕,选择主按钮,切换到 **Interactions** 标签页。点击 **Add trigger** > **On tap** > **Add action**,将 **Action** 设置为 **Navigate to**,然后选择下一个屏幕——在本示例中为 Quiz 屏幕。 2. **从测验进行条件导航。** 打开 Quiz 页面,选中 Next 按钮,添加 **On tap** 触发器并设置 **Conditional action**。配置 IF/ELSE 规则: - 在变量选择器中,打开 **Elements** 标签,选择 **Quiz** 页面,并选中 `quiz.selectedOptionId`。 - 使用 **Equals** 运算符,与其中一个选项的 ID 进行比较——在本示例中为 Rock 选项。 - **IF** 条件匹配,触发 **Navigate to** 并选择第一条路径页面。 - **ELSE**,触发 **Navigate to** 并选择第二条路径页面。 3. **从每个分支路径到付费墙的静态导航。** 在每个路径页面上重复步骤 1 中的操作,将付费墙设为目标页面。 ## 下一步 \{#next-steps\} - [保存并发布您的流程](builder-save-publish)。 - [将流程添加到版位](create-placement),开始向用户展示。 - 如需针对不同目标受众使用不同流程(而非在流程内部进行分支),可创建目标受众市场细分,并在版位页面为各目标受众分配不同流程。 :::link 想了解更多关于构建流程的内容?请观看我们 [YouTube 播放列表](https://www.youtube.com/playlist?list=PLMksWqaZiWtM)中的分步视频教程。 ::: --- # File: migrate-to-flows --- --- title: "迁移到流程" description: "将独立的用户引导和付费墙合并到一个 Adapty 流程中——了解具体变化,以及如何在不影响旧版本用户的情况下平稳上线。" --- 在 Adapty 中,*流程*将用户引导和付费墙合并为一个整体,统一挂载到同一个版位下。流程取代了原先需要分别构建和投放的独立用户引导和付费墙。 本指南介绍迁移到流程后有哪些变化,以及如何在不影响旧版本用户的情况下平稳完成迁移。 :::important 目前,流程功能支持 iOS、Android、React Native、Flutter 和 Capacitor SDK v4 及更高版本。其他平台和框架的支持即将推出。 ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/8Cby6lVGI0o?si=rYA1HtdayyF1ffWd" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 流程 vs. 用户引导和付费墙 \{#flows-vs-onboardings-and-paywalls\} 使用独立的用户引导和付费墙,你需要维护两个编辑工具和两个版位,还要在自己的代码中处理从用户引导到付费墙的跳转逻辑。 而流程将两者合而为一——引导页、测验和购买页面都在同一个编辑工具中构建,并从同一个版位提供服务。 下表对比了各选项的功能: | | 流程 | 付费墙编辑工具付费墙 | 用户引导 | |---|---|---|---| | 多屏幕支持 | 是 | 否——单屏幕 | 是 | | 渲染方式 | 原生 | 原生 | WebView | | 产品与版位 | 一个版位;直接在流程中添加产品 | 一个版位;直接在付费墙中添加产品 | 一个版位,但本身不含产品——如需销售,需单独创建付费墙并通过独立版位提供 | ## 是否需要迁移?\{#should-you-migrate\} 您现有的用户引导和付费墙仍可正常运行,Adapty 也会继续为其提供支持。但新功能将优先面向流程发布,而不再针对独立的用户引导和付费墙编辑工具。 **如果您着眼于长期开发,流程是更好的基础** — 请在合适的发版节点迁移至流程。 ## 如何迁移 \{#how-to-migrate\} 迁移分四个步骤。大部分工作是一次性的 SDK 升级——构建和预览流程无需编写代码。 1. **[创建您的流程](#build-your-flow)**:在无代码编辑器中创建流程,无需开发人员参与。 2. **[在设备上预览](#preview-on-device)**:通过 Adapty 移动应用在真实设备上查看流程效果,无需构建应用。 3. **[为您的流程创建新版位](#create-a-new-placement-for-your-flow)**:创建一个具有唯一 ID 的新流程版位,并决定它如何与现有版位共存。 4. **[更新 SDK](#update-the-sdk)**:升级至 iOS、Android、React Native 或 Capacitor SDK v4,从版位获取流程,并验证沙盒购买。这是开发人员的主要工作。 ### 搭建你的流程 \{#build-your-flow\} 在 **Flows** 页面,点击 **Create flow** 开始搭建,将用户引导和付费墙整合为一体。了解更多关于编辑工具的内容: - **[Flows 文档](adapty-flow-builder)**:带你了解编辑工具及其功能。 - **[常用流程配方](flow-builder-recipes)**:最常见页面的分步操作指南。 - **Ask AI**:遇到问题时,使用任意文档页面上的对话框寻求帮助。 :::note 从现成流程模板构建流程或使用 AI 生成流程的功能尚不可用——这两项功能即将推出。目前,每个新流程都会从几个常用屏幕开始,你可以根据需要对其进行编辑和调整。 ::: ### 在设备上预览 \{#preview-on-device\} 无需修改应用,即可在真实设备上预览流程。从 App Store 下载 [Adapty 应用](https://apps.apple.com/us/app/adapty/id6739359219),然后在流程编辑器中点击 **Test on device**,选择语言区域并用设备扫描二维码,即可看到真实的页面、分支逻辑、文案和设计效果。 :::note 在预览模式下,Adapty 无法访问应用商店中的产品,因此预览中显示的价格并非真实价格。真实购买需在集成了 v4 版本 SDK 的构建包中使用沙盒账号进行验证——详见[更新 SDK](#update-the-sdk)。 ::: ### 为您的流程创建新版位 \{#create-a-new-placement-for-your-flow\} 一个版位只能承载一种内容类型——流程、付费墙或用户引导,三者互不兼容。您无法将现有的用户引导版位或付费墙版位转换为流程版位(参见[版位类型](create-placement))。流程需要单独创建新版位。 **为新的流程版位指定一个全新且唯一的版位 ID。** 该 ID 不能与任何现有付费墙版位或用户引导版位的 ID 重复或复用。 :::warning 过渡期间请保留旧版位 使用旧版应用的用户,其 onboarding 和付费墙版位 ID 已编译进应用中。他们仍会调用 onboarding 和付费墙方法,并看到你现有的 onboarding 和付费墙,直到他们更新应用。请等到 SDK v4 的采用率足够高后,再停用旧版位。 ::: 您不必一次性将所有版位都迁移到 flow。在 SDK v4 中,`getFlow` 方法可以同时从 flow 版位和付费墙版位获取内容,因此您的应用在所有地方都调用同一个方法。可以将付费墙编辑工具中的付费墙保留在原有版位,在其余版位使用 flow。 在过渡期间,每种版位类型会分别追踪各自的数据图表。当新旧两个版本的应用同时运行时,数据会分散到两组版位中:旧版用户引导和付费墙版位涵盖旧版本,新版 flow 版位则对应 SDK v4 及以上版本。建议将这两组数据作为独立的同期群进行比较,随着用户陆续更新,flow 版位的占比将逐步提升。 你可以对流程进行 A/B 测试:在流程版位上针对不同流程实验变体运行[常规 A/B 测试](ab-tests)。跨版位 A/B 测试目前仅支持付费墙,暂不支持跨流程版位运行。若要将新流程与旧付费墙进行对比,属于同期群对比,而非单一测试——两者分属不同的版位类型。 ### 更新 SDK \{#update-the-sdk\} 流程版位配置完成后,将应用指向该版位。Flow 仅在 Adapty SDK v4 及更高版本上渲染。请升级 SDK,并使用 `getFlow` 从新版位获取流程。具体升级步骤请参阅各平台的 v4 迁移指南 — [iOS](migration-to-ios-sdk-v4)、[Android](migration-to-android-sdk-v4)、[React Native](migration-to-react-native-sdk-v4) 或 [Capacitor](migration-to-capacitor-sdk-v4)。 接好流程后,像测试其他购买流程一样进行验证:在真机或模拟器上运行,并完成一次沙盒购买([iOS](ios-test) / [Android](testing-on-android)),确认产品、购买行为和访问等级均正常工作。 :::note 只有在安装了 SDK v4+ 构建版本的应用之后,用户才能看到流程。使用旧版应用的用户仍会看到原有的用户引导和付费墙,这也是过渡期间旧版位保持在线的原因。在尚不支持流程的平台上同样如此。 ::: --- # File: paywall-layout-and-products --- --- title: 界面与图层 description: "在流程编辑器中管理界面及每个界面内的元素层级。" --- 一个流程由一个或多个界面组成。每个界面代表用户旅程中的一个步骤——例如付费墙、问卷或产品介绍页。 每个界面上的元素按图层层级进行组织。 要管理屏幕、图层和元素,请打开默认的 **Screens and Layers** 视图。它将显示你的屏幕序列以及每个屏幕的图层结构。 ## 管理屏幕 \{#manage-screens\} 左侧面板的顶部区域列出了流程中的所有屏幕,每个条目显示一个编号标签和缩略图预览。 * **选择页面**:点击某个页面条目将其激活。可视化编辑器会显示所选页面,下方的图层部分也会随之更新,展示该页面的图层层级。 * **添加页面**:点击页面部分顶部的 Plus 按钮,向流程中添加一个新的空白页面。 * **打开模板库**:点击页面部分顶部的 Templates 按钮,浏览并应用[流程模板](paywall-builder-templates)。 * **调整页面顺序**:拖放页面条目以更改其在流程中的排列顺序。 :::important 如果你的流程中存在未使用的空白屏幕,将无法发布。请在发布前删除所有草稿屏幕。 ::: ### 屏幕操作 \{#screen-actions\} 点击屏幕条目上的三点图标 Context 可打开上下文菜单。 | 操作 | 快捷键 | 说明 | |--------|----------|-------------| | **播放动画** | | 预览此屏幕上配置的动画 | | **复制** | ⌘C / Ctrl+C | 将屏幕复制到剪贴板 | | **粘贴到此处** | ⌘V / Ctrl+V | 粘贴之前复制的屏幕 | | **复制副本** | ⌘D / Ctrl+D | 创建屏幕的副本并添加到流程中 | | **重命名** | | 修改屏幕的显示名称 | | **删除** | ⌘⌫ / Ctrl+Del | 从流程中移除该屏幕 | :::tip 剪贴板内容在不同流程之间保持有效。你可以从一个流程中复制屏幕或元素,打开另一个流程后直接粘贴。 ::: :::warning 当你删除某个屏幕时,所有指向该屏幕的 [导航到屏幕](onboarding-navigation-branching) 动作都会**失去目标**,但该动作本身**不会被删除**。请为其分配新的目标页面,或者删除该动作——否则,你将无法[预览或发布流程](builder-save-publish#publish-a-flow)。 ::: ## 在屏幕之间导航 \{#navigate-between-screens\} :::link 主要文章:[导航与交互](onboarding-navigation-branching) ::: 列表中屏幕的顺序本身并不决定导航方式。如需连接屏幕,请使用元素交互:将按钮配置为跳转到另一个屏幕。 ## 屏幕设置 \{#screen-settings\} 要查看当前屏幕的属性和设置,请点击屏幕预览中的空白区域,右侧面板将切换到屏幕设置视图。 ### 系统界面 \{#system-ui\} 控制屏幕与设备硬件的交互方式。 * **Safe area** 添加内边距,使内容避开刘海和系统状态栏区域。 * **Status bar** 控制系统状态栏(时间、电量、信号图标)的显示与隐藏。 ### 在进度指示器中包含当前屏幕 \{#include-screen-in-progress-indicator\} 如果你在流程中添加了[进度指示器](builder-loaders-and-progress-bars#progress-indicators)元素,Adapty 会在每个屏幕上显示它。 取消勾选 **Include screen in progress indicator**,可将某个特定屏幕从进度指示器中移除。适用于欢迎屏幕、最终付费墙,或任何你不希望计入进度的步骤。 ### 屏幕布局 \{#screen-layout\} :::link 完整文章:[布局与定位](manage-paywall-ui-elements) ::: **Layout** 部分决定屏幕如何排布其子元素。这些属性适用于任意容器元素。 * **Free**:子元素独立定位。 * **Vertical**:元素从上到下排列,类似 flexbox 列方向。 * **Horizontal**:元素从左到右排列,类似 flexbox 行方向。 对于垂直和水平布局,你还可以配置间距和对齐方式。 * **Alignment**:元素沿交叉轴的位置。 * **Gap**:相邻元素之间的间距。 * **Distribution**:子元素之间及周围空间的分布方式。 #### RTL 布局 \{#rtl-layout\} 勾选 **Mirror for RTL** 复选框,可为从右向左书写的语言镜像布局。水平容器中的元素顺序将会翻转。 ### 屏幕背景 \{#screen-background\} :::link 主要文章:[背景](paywall-head-picture) ::: **Fill** 用于将[屏幕背景](paywall-head-picture)设置为纯色、渐变、图片或视频。背景会填满整个设备视口,包括刘海和系统状态栏后面的区域——即使启用了 **Safe area** 也不例外。 #### 循环播放背景视频 \{#loop-background-video\} 启用 **Loop** 开关,可让背景视频持续循环播放。 #### 自定义媒体 ID \{#assign-a-custom-media-id\} 与[任何图片或视频](custom-media)一样,您可以为屏幕背景分配自定义媒体 ID,以便在 SDK 中引用它。 ### 屏幕间距 \{#screen-spacing\} 调整屏幕四侧(上、右、下、左)的内边距。 ### 滚动 \{#scroll\} 控制溢出行为。启用 **Vertical scroll** 可在屏幕内容超出视口高度时允许滚动。 ### 可选组 \{#selectable-groups\} :::link 主要文章:[可选元素与组](flow-selectable-elements) ::: **Selectable groups** 部分列出了当前屏幕上的所有可选组——来自[测验](onboarding-quizzes)、[产品](paywall-product-block)、[标签页](builder-tabs)、[试用切换](builder-toggles)或任意[自定义可选元素](flow-selectable-elements#make-an-element-selectable)。 点击某个组条目可对其重命名、更改类型、查看其公开的变量或将其删除。 ## 管理图层 \{#manage-layers\} 屏幕上的每个元素都以图层的形式呈现。图层面板显示当前屏幕上各元素的排列顺序。 :::important Flow 中的图层不像图形设计软件中的图层那样相互叠加。它们代表的是屏幕上各个独立的组件。只有当元素使用[绝对定位或固定定位](manage-paywall-ui-elements)时,才会发生叠加。叠加顺序由 `z-index` 属性决定,而非图层树中的位置。 ::: 树状结构反映了父子层级关系。点击任意父层级上的箭头可展开或折叠其子层级。 您无法直接创建层级。通过[添加元素](builder-elements)视图添加的每个元素都会作为新层级显示在树中。 * **选择图层**:点击图层即可选中它。可视化编辑器会在画布上高亮显示对应元素,右侧面板则展示其[设计](builder-styling)和[交互](onboarding-navigation-branching)属性。 * **调整图层顺序**:在树状结构内拖放图层,改变其在父容器中的排列顺序。树中的顺序与屏幕上的视觉顺序一致。 * **显示或隐藏图层**:将鼠标悬停在图层上,右侧会出现眼睛 Eye 图标。点击它即可切换图层的可见性。隐藏的图层仍保留在树中,但不会在可视化编辑器或设备上显示。如需在运行时通过逻辑控制可见性,请使用[条件可见性](onboarding-element-visibility)。 * **折叠所有图层**:点击"图层"区域右上角的折叠 Collapse 按钮,可将整个树状结构全部折叠。 ### 图层操作 \{#layer-actions\} 点击三点图标 Context 打开上下文菜单。 | 操作 | 快捷键 | 说明 | |--------|----------|-------------| | **Copy** | ⌘C / Ctrl+C | 将图层复制到剪贴板 | | **Paste here** | ⌘V / Ctrl+V | 将已复制的图层作为子元素粘贴 | | **Duplicate** | ⌘D / Ctrl+D | 在同一容器中创建该图层的副本 | | **Rename** | | 修改图层的显示名称。默认情况下,图层以其内容或组件类型作为名称 | | **Delete** | ⌘⌫ / Ctrl+Del | 删除该图层及其所有子元素 | | **Wrap** | | 将图层包裹在新容器中:**Wrap in Horizontal Container** 或 **Wrap in Vertical Container** | | **Unwrap / Ungroup** | | 移除包裹容器,并将其子元素上移一级 | | **Move up** | ↑ | 在父容器中将图层向上移动一个位置 | | **Move down** | ↓ | 在父容器中将图层向下移动一个位置 | --- # File: manage-paywall-ui-elements --- --- title: "布局与定位" description: "通过布局、定位模式、尺寸和间距在屏幕上排列元素。" --- Flow Builder 创建的是响应式布局。你不需要将元素拖放到精确坐标——而是将它们嵌套在**容器**中,由容器自动排列其子元素。 容器决定元素的排列方向(垂直或水平)、对齐方式和间距。各个元素可以进一步微调自身的尺寸和外边距,或在需要时通过绝对定位或固定定位脱离正常流。 :::link 关于填充、边框和效果等视觉属性,请参阅[样式与外观](builder-styling)。 ::: <Tabs groupId="video"> <TabItem value="align" label="对齐与定位"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aRS4Bzb6W4I?si=qH7B6t3kMab70gBi" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> <TabItem value="layout" label="布局、尺寸与间距"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/WQ9fpxrndok?si=ROMdIPvJ32tSwUX6" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## 布局 \{#layout\} 布局是在屏幕上排列元素的核心工具。每个容器会按照一组规则(方向、对齐方式和间距)自动分配其子元素。编辑工具中可用的布局元素如下: * **[垂直容器](builder-containers#containers)**:从上到下排列子元素 * **[水平容器](builder-containers#containers)**:从左到右排列子元素 * **[分隔线](builder-containers#dividers)**:元素之间的视觉分隔符 * **[轮播图](builder-containers#carousel)**:可水平滚动的幻灯片组 * **[底部弹层](builder-containers#bottom-sheet)**:一个滑动覆盖面板,当用户点击按钮时显示额外内容 容器是屏幕的基本构建单元,可以相互嵌套来实现复杂布局。右侧面板的 **Layout** 部分用于控制容器内子元素的排列方式。 如需将元素归组到新容器中,可使用 **Wrap** [图层操作](paywall-layout-and-products#layer-actions);如需移除容器并将子元素提升到上一层,则使用 **Unwrap**。 :::link 有关屏幕和图层层级结构的详细信息,请参阅[屏幕与图层](paywall-layout-and-products)。 ::: ### 方向 \{#direction\} * Free **Free**:无自动布局。子元素独立定位(适用于子元素使用绝对定位的场景) * Vertical **Vertical**:子元素从上到下堆叠,如列中的行 * Horizontal **Horizontal**:子元素从左到右排列,如行中的项目 ### 元素顺序 \{#element-order\} 子元素按照**图层**面板中的排列顺序渲染。在垂直容器中,列表最上方的元素显示在屏幕顶部;在水平容器中,最上方的元素显示在左侧。你可以在图层面板中拖拽元素来调整顺序,也可以使用**上移**和**下移**[图层操作](paywall-layout-and-products#layer-actions)。 ### 对齐方式 \{#alignment\} 对齐网格控制子元素在容器交叉轴上的位置。在垂直容器中,对齐方式控制子元素的水平位置(左对齐、居中或右对齐)。在水平容器中,它控制子元素的垂直位置(顶部、居中或底部)。 ### 分布 \{#distribution\} 分布决定了子元素在主轴方向上的空间分配方式: * **Gap** Gap(默认):相邻子元素之间的固定像素间距 * **Space Between**:子元素分布到两端,元素之间的间距相等 * **Space Around**:每个子元素两侧拥有相等的空间,但边缘处的间距为中间间距的一半 * **Space Evenly**:所有子元素前后及之间的空间完全相等 ### 裁剪内容 \{#clip-content\} 从视觉上裁剪超出容器边界的内容。关闭此选项可允许内容溢出(例如,一个有意延伸到卡片边缘之外的徽章)。 ## 位置 \{#position\} 默认情况下,每个元素的位置由其容器的布局自动决定。**Position** 切换开关允许你将元素从正常流中脱离出来,手动设置其位置。 ### 相对定位(默认)\{#relative-default\} 元素保持在正常的布局流中,其位置由父容器的布局规则自动决定——你无法自由拖动它。可使用**外边距(Margin)**来调整相对定位元素周围的间距。 绝大多数内容都适合使用相对定位,例如文本块、图片、卡片、按钮和列表项。 ### 绝对定位 \{#absolute\} 元素脱离正常文档流,覆盖在其他内容之上,不再影响相邻元素的布局。 选择 **Absolute** 后,会出现以下额外控件: * **偏移字段**(T、L、R、B):设置元素到父容器各边缘的像素距离 * **锚点网格**:点击 3×3 网格上的某个点,选择元素锚定到父容器的哪个角、边或中心 * **水平锚点** Horizontal positioning(Left / Center / Right)和**垂直锚点** Vertical positioning(Top / Center / Bottom):下拉菜单,与网格控制相同的锚点位置 * **Z-index**:数字输入框,控制元素相对于同级元素的[层叠顺序](#stacking-order)。数值越大,元素显示越靠前 对装饰性叠加层、徽章、关闭按钮以及放置在图片上方的图标,请使用绝对定位。 :::tip 如需让绝对定位元素横向撑满父容器,将水平锚点设置为 **Left**,然后将 **Right** 偏移设为 0,元素即可同时贴合两侧边缘。 ::: ### 固定 \{#fixed\} 该元素完全忽略父容器,直接固定在屏幕上。用户滚动时它始终可见,页面内容在其下方移动。 固定定位使用与绝对定位相同的控件(偏移量、锚点网格、Z-index)。所有偏移量均相对于屏幕安全区域计算,而非相对于父元素。例如,底部偏移量为 0 时,元素会保持在 Home 指示条上方。如需从屏幕物理边缘计算偏移量,请启用[忽略安全区域](#ignore-safe-area)。 对于需要悬浮在滚动内容上方的元素,请使用固定定位,而不是为其预留空间——例如浮动的关闭或恢复按钮、顶部固定横幅、返回顶部控件以及导航栏。如果需要专用的底部操作区域,请改用 [Footer](builder-containers#footer)。 ### 忽略安全区域 \{#ignore-safe-area\} 安全区域是屏幕上避开刘海、状态栏和主屏指示器的区域。默认情况下,定位元素会保持在安全区域内。 勾选位置类型选择器下方的 **Ignore safe area** 复选框,可将元素的偏移量改为从物理屏幕边缘开始计算。元素将可延伸至刘海和主屏指示器后面。 使用以下方法实现全屏媒体效果:将图片或视频定位方式设置为 **Fixed**,将四个偏移量均设为 0,并勾选 **Ignore safe area**。这样,媒体内容将覆盖整个屏幕,延伸至每一条边缘。 该复选框仅适用于绝对定位和固定定位。对于相对定位的元素,该选项不可用;将元素切换回 **Relative** 后,该设置也会自动清除。 ## 尺寸 \{#sizing\} 每个元素都有 **Width** 和 **Height** 控件。点击下拉菜单可选择尺寸模式: * **Fill**:元素会拉伸以占满父容器中所有可用空间。显示的像素值为计算结果。 * **Hug**:元素会收缩以适应其内容。显示的像素值为计算结果。 * **Fixed**:元素使用你指定的精确像素值,与父容器或内容大小无关。这是绝对定位或固定定位元素的唯一可用模式。 ## 间距 \{#spacing\} 可以为元素的每一侧分别设置间距值。 * **Margin**:元素与相邻元素之间的空间。无论设置多大,都不会超出父容器的边界。 * **Padding**:元素边界与其内容之间的空间。 文本元素只有 Margin,屏幕只有 Padding,容器及其他包含子内容的元素则两者皆可使用。 ## 堆叠顺序 \{#stacking-order\} 相对定位的元素不会相互重叠——每个容器按顺序排列其子元素。只有当某个元素通过**绝对**或**固定**定位脱离正常文档流时,才会产生重叠。 当元素发生重叠时,在 **Layers** 面板中排列靠后的兄弟元素会渲染在靠前的元素上方——即使靠后的元素是相对定位,靠前的元素是绝对定位也是如此。 **Absolute** 和 **Fixed** 元素有 **Z-index** 字段用于精细控制:数值越大优先级越高。Relative 元素没有 Z-index——仅通过图层顺序决定其层叠关系。 使用 **Move up** 和 **Move down** [图层操作](paywall-layout-and-products#layer-actions) 来调整元素顺序。 --- # File: builder-styling --- --- title: "样式与外观" description: "配置元素的视觉外观——填充、边框、效果、排版、状态以及项目级样式。" --- **Design** 标签位于右侧面板,用于控制每个元素的视觉外观。可用属性取决于元素类型,但大多数元素共享一些通用样式选项。 :::link 有关尺寸、间距和定位,请参阅[布局与定位](manage-paywall-ui-elements)。 ::: ## 可见性 \{#visibility\} **Visibility** 开关用于控制该元素是否显示在屏幕上。 * Show **Show**(默认):元素始终可见。 * Conditional **Conditional**:仅在满足特定条件时元素才可见。详情请参阅[条件可见性](onboarding-element-visibility)。 * Hide **Hide**:元素始终隐藏。可用此选项临时将元素从流程中移除,而无需删除它。 <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 填充 \{#fill\} **Fill** 部分用于控制元素的背景。共有四种填充类型:纯色、渐变、图片和视频。 使用此属性可为整个屏幕设置主图或主视频。 * **纯色** Solid color。使用颜色选择器、输入十六进制值,或指定[项目级颜色样式](#color-styles)。调整**不透明度**可使背景呈半透明效果。 * **渐变** Gradient。添加包含两个或更多色标的渐变填充。拖动色标可调整过渡效果,修改渐变角度可控制渐变方向。 * **Image** Image 或 **Video** Video。将[图片/视频](custom-media)设置为元素的背景。 ## 边框 \{#border\} 边框默认关闭。点击右侧面板中 **Border** 旁边的 Plus 可添加边框。若要移除边框,点击 **Border** 标题旁边的 Close。 添加边框后,可配置以下选项: * **Color**:使用拾色器、输入十六进制值,或指定[项目级颜色样式](#color-styles)。调整**透明度**可使边框呈半透明效果。 * **Width**:边框粗细,单位为像素。 ## 圆角 \{#corners\} **圆角**部分用于控制边框圆角半径。 * **圆角滑块**:为四个角设置统一的圆角半径 * **单独设置切换** Per Corner:启用后可为每个角单独设置不同的圆角半径 ## 效果 \{#effects\} 点击 **Effects** 旁边的加号按钮 Plus 可添加一个或多个视觉效果: * **Drop shadow**:元素背后的阴影 * **Inner shadow**:元素边界内的阴影 * **Background blur**:模糊背景 * **Layer blur**:模糊元素及其子元素 同一元素可叠加多个效果。点击可见性图标 Show 可临时禁用某个效果。 ## 动画 \{#animation\} 点击 **Animation** 旁边的 Plus 按钮,即可添加动画效果。目前仅支持 **Pulse** 动画——元素会周期性地放大缩小,以吸引用户注意。 通过以下参数配置 Pulse 动画: | 参数 | 说明 | |-----------|-------------| | Scale amount (%) | 元素相对于原始大小的缩放比例 | | Duration (ms) | 单次动画循环的时长 | | Delay between loops (ms) | 两次循环之间的停顿时长 | | Shadow color | 脉冲阴影效果的颜色 | | Shadow size (px) | 脉冲阴影的大小 | ### 预览动画 \{#preview-the-animation\} 编辑工具默认显示静态界面——动画效果不会播放,需要手动开启。有两种方式: - 点击设备预览上方的 **Toggle animations** Toggle animations 按钮,可以开启或关闭当前界面的动画效果。开启后动画会持续循环播放,再次点击即可停止。只有当前界面包含至少一个动画时,该按钮才会显示。 - 打开界面的[上下文菜单](paywall-layout-and-products#screen-actions)(界面图层旁边的三点图标),选择 **Play Animation**。 ## 外观 \{#appearance\} * **Opacity**:范围从 0%(完全透明)到 100%(完全不透明) * **Rotation**:输入角度值来旋转元素 ## 排版属性(文本元素)\{#typography-properties-text-elements\} 文本元素会显示一个**排版**部分,包含以下控件: ### 字体 \{#font\} :::link 另请参阅:[自定义字体](using-custom-fonts-in-flow-builder) ::: 点击字体下拉菜单 Font select 打开字体选择器。它包含两个选项卡: * **Styles**:列出项目中已保存的[文字样式](#text-styles)。选择一个样式即可一次性应用其全部排版设置。 * **Fonts**:列出所有可用的字体族。可通过搜索或滚动找到所需字体。内置字体在**不同设备上的渲染效果可能存在差异**——如需保持一致的渲染效果,请上传[自定义字体](using-custom-fonts-in-flow-builder)。 ### 大小与粗细 \{#size-and-weight\} :::warning 对于[自定义字体](using-custom-fonts-in-flow-builder),**Weight**、**Bold** 和 **Italic** 控件仅影响编辑器内置预览效果。如需显示不同的字重和样式,请将每种字体变体作为单独文件上传。 ::: * **Weight**:从下拉菜单中选择字重 * **Size**:从下拉菜单中选择大小,或手动输入自定义值 ### 颜色 \{#color\} 点击色块可打开颜色选择器。你可以输入十六进制值、使用调色板,或从[可复用样式](#reusable-styles)中选择。拖动不透明度滑块可让文字呈现半透明效果。 ### 对齐方式 \{#alignment\} 两组对齐控件: * **水平方向**:左对齐 Align left、居中 Align center、或右对齐 Align right * **垂直方向**:顶部对齐 Align top、居中 Align middle、或底部对齐 Align bottom ### 装饰 \{#decoration\} * **无** None:无装饰(默认) * **下划线** Underline:为文字添加下划线 * **删除线** Strikethrough:为文字添加删除线 ### 截断 \{#truncation\} 启用截断功能,可在文本超出 **Max Lines** 设置时自动裁切。这在支持多语言时非常实用:如果译文比原文长,截断可以防止布局被撑乱。 :::note 选中文本元素后,画布上方会出现一个**内联工具栏**。通过它可以快速设置字体、字重、大小和对齐方式,无需在右侧面板中滚动查找。 ::: ## 状态专属设置(交互元素)\{#state-specific-settings-interactive-elements\} 交互元素支持多种视觉状态。选中此类元素后,右侧面板会出现 **States** 区域。切换不同状态,即可为每个状态单独配置视觉属性。 每个状态都可以覆盖任意视觉属性——填充、边框、字体颜色、透明度等。 ### 可选状态 \{#selectable-states\} :::link 主要文章:[可选元素](flow-selectable-elements) ::: 属于可选组的元素(测验选项、产品、标签页、试用切换开关)默认提供两种状态: * **Default**:元素的正常外观 * **Selected**:用户选中该选项后的外观。可覆盖填充色、边框颜色和文字颜色等属性,以突出显示当前激活的选项 要在不可交互时为可选元素设置样式,需手动添加第三种状态。打开 **States settings** Settings 并添加 **Disabled state**。 **Disabled** 状态由条件驱动。选中该状态后,点击 **Set conditions** set conditions 来定义元素在运行时何时变为禁用状态,例如当必填字段为空时。 ### 输入状态 \{#input-states\} 输入字段提供以下额外状态: * **Default**:正常、未聚焦的外观 * **Active**:字段已聚焦,可以输入内容 * **Invalid**:输入的值未通过验证 * **Disabled**:字段不可交互 ### 其他具有状态样式的元素 \{#other-state-bearing-elements\} 部分元素的状态样式不遵循标准的**默认 / 已选中 / 已禁用**模式: - **[进度指示器步骤](builder-loaders-and-progress-bars#step-states)** — 每个步骤有三种状态:**Completed**、**Current** 和 **Upcoming**。 - **[轮播图圆点](builder-containers#dots)** — 两种颜色变体:**Color** 用于非活跃圆点,**Active Color** 用于当前幻灯片的圆点。 ## 可复用样式 \{#reusable-styles\} 左侧边栏中的 **Styles** Styles 面板可让你定义适用于整个流程的可复用样式。目前支持两种样式类型:文本样式和颜色样式。若要启用深色模式支持,必须使用颜色样式。 ### 文字样式 \{#text-styles\} :::link 主要文章:[文字内容](onboarding-text) ::: 文字样式存储了一套完整的排版设置,包括字体系列、字重、字号、行高、对齐方式和装饰效果。每个流程模板都包含默认预设,你也可以创建自定义样式。 创建文字样式的步骤: 1. 打开 **Styles** Styles 面板,选择 **Text** 标签页。 2. 点击 **Plus Create style**。 3. 输入名称并配置排版设置。 4. 点击 **Create**。 如需应用文字样式,请选中一个文字元素,然后在 **Typography** 区域的字体下拉菜单中选择对应样式。 ### 颜色样式 \{#color-styles\} 颜色样式是可以在整个流程中引用的命名颜色。每种颜色样式都有名称(如"Primary text"或"Brand")、十六进制值,以及显示有多少元素引用了它的使用计数。 创建颜色样式: 1. 打开 **Styles** Styles 面板,选择 **Colors** 标签页。 2. 点击 **Plus Create style**。 3. 输入名称并选择颜色。 当你更新某个颜色样式时,所有引用该样式的元素都会自动更新。 ### 深色模式 \{#dark-mode\} :::link 主要文章:[深色模式](paywall-dark-mode) ::: 如有需要,你可以为每种颜色样式添加两个变体——一个用于浅色模式 Light mode,一个用于深色模式 Dark mode。SDK 会根据设备当前的配色方案自动应用对应的变体。 要在编辑工具中预览深色模式,请使用[底部工具栏](builder-ui#view-controls-bottom-toolbar)中的**主题切换**按钮 Dark mode。 --- # File: paywall-product-block --- --- title: "设置购买" description: "在流程编辑工具中为页面分配产品、添加产品元素,并连接购买按钮。" --- 要在页面上设置购买功能,需要添加一个购买按钮并配置其 **Purchase** 操作。该操作可以针对特定产品,也可以针对用户从页面上的产品元素中选择的任意产品。 <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/LLIZCd94PlE?si=t_8BitA1FBpbd8ue" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 添加产品 \{#add-products\} 产品元素是在画布上展示产品的可视化卡片。 添加产品元素的步骤: 1. 在画布上,点击目标屏幕上的 **+**。 2. 选择 **Products**。 3. 选择一种布局预设:垂直列表、水平列表、功能轮播、功能卡片、横幅列表或底部弹窗。 4. 选中每张产品卡片,在 **Design** 面板的下拉菜单中为其分配对应的产品。 :::important 一个未关联产品的产品元素会[阻止预览和发布](builder-save-publish#troubleshooting)。请为其分配一个产品或删除该元素。 ::: 若要在卡片上显示划线的原价,请在其中添加一个[原价元素](onboarding-text#add-an-old-price)。 :::note 你也可以直接将 **Purchase**(购买)动作附加到产品卡片的 **On tap** 交互上。这样点击卡片即可触发购买,无需单独设置购买按钮。 ::: :::important 如果你删除了某个产品组并用新组替换,请确认所有动作和变量都已指向新组。若仍有引用指向已删除的组,将会[阻止预览和发布](builder-save-publish#troubleshooting)。 ::: ## 添加购买按钮 \{#add-a-purchase-button\} 购买按钮会在用户点击时触发**购买**操作。 添加购买按钮的步骤: 1. 在画布上,点击屏幕上的 **+**。 2. 选择 **Button**,然后选择一个按钮预设。 3. 选中该按钮后,在右侧面板中打开 **Interactions** 选项卡。 4. 点击 **Add trigger** > **On tap**,然后点击 **Add action**。 5. 将 **Action** 设置为 **Purchase**,然后将 **Product** 设置为以下之一: - `products.selectedProduct`:购买用户从屏幕上的 Products 元素中选择的产品。 - 指定产品:无论屏幕上选择了什么,始终购买该产品。 ### 在按钮上显示价格 \{#show-the-price-on-the-button\} 要将所选产品的价格插入按钮标签,请使用变量: 1. 选中按钮后,在右侧面板中打开 **Design** 标签页。 2. 在 **Content** 字段中,将光标放置在价格应显示的位置。 3. 点击变量图标,选择 `products.selectedProduct`,然后选择 `prod_price` 属性。完整变量解析为 `products.selectedProduct.prod_price`。 4. 在变量周围添加静态文本,例如 `Subscribe for {prod_price}`。 当用户选择不同产品时,标签会自动更新。 ## 恢复购买 \{#restore-purchases\} 要让用户恢复之前的购买记录,请在界面上添加一个恢复按钮或链接。 添加恢复购买元素的步骤: 1. 在画布上,点击界面中的 **+**。 2. 选择 **Button**,然后选择 **Links** 添加文字链接,或选择其他按钮类型添加样式按钮。 3. 选中该元素后,在右侧面板中打开 **Interactions** 标签,然后点击 **Add trigger**。 4. 选择 **On tap**,然后点击 **Add action**。 5. 在 **Action** 下拉菜单中,选择 **Restore purchases**。 ## 根据所选产品显示额外元素 \{#display-additional-elements-based-on-the-selected-product\} 如果页面上有产品,你可以根据用户选择的产品来显示或隐藏其他元素。 设置条件可见性的步骤: 1. 在 **Products** 元素中,选择一个产品卡片。 2. 在右侧面板中打开 **Interactions** 标签页,然后点击 **Add trigger**。 3. 选择 **On tap**,然后点击 **Add action**。 4. 在 **Action** 下拉菜单中,选择 **Show** 或 **Hide**。 5. 选择当该产品被选中时要显示或隐藏的元素。 ## 查看流程中的产品 \{#review-products-in-flow\} 左侧边栏中的 **Products** 面板将现有产品映射到流程中的每个屏幕。 每个屏幕包含两个部分: - **Default** — 单个产品,在屏幕加载时默认选中。 - **Other** — 同一屏幕上可选的其他产品。 --- # File: flow-selectable-elements --- --- title: "可选元素与分组" description: "让元素支持选中,将其组织成分组,并在流程中通过条件使用其状态。" --- 可选元素是用户可以点击选中或取消选中的流程元素。其状态可用于驱动整个流程中的导航、可见性及其他逻辑。你可以实现以下功能: - [使用默认可选元素](#default-selectable-elements) — 问卷选项、产品、标签页和试用开关均支持开箱即用的选择功能 - [将任意元素设为可选](#make-an-element-selectable) — 将任意元素转变为可选元素并分配到某个分组 - [创建与管理分组](#create-a-group) — 将可选元素整理为单选、多选或开关分组 - [在条件中使用已选状态](#use-selectable-state-in-conditions) — 在流程中任意屏幕的条件里引用分组的值 ## 默认可选元素 \{#default-selectable-elements\} 某些元素类型默认即为可选状态——它们已属于自动创建的分组,无需额外配置: - **测验选项**:每个测验答案都是测验分组中的可选元素。请参阅[测验](onboarding-quizzes)。 - **产品**:产品分组中的产品卡片。请参阅[产品模块](paywall-product-block)。 - **标签页**:标签页分组中的标签项。请参阅[标签页](builder-tabs)。 - **试用切换**:属于某个分组并具有选中状态的容器。请参阅[切换](builder-toggles)。 ## 使元素可选 \{#make-an-element-selectable\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/btpZPOm9VRY?si=1P959iwNfIJ1ZP7N" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 在某些情况下,您可能希望让额外的元素也可被选中。例如,您可以添加一个**不再询问**复选框,使其作为测验组内的一个元素运行。 要使某个元素可被选中: 1. 在屏幕上或**Layers**面板中选择该元素。 2. 在右侧切换到**Interactions**面板。 3. 选择**Turn into selectable element**。 4. 在 **Group** 下拉菜单中,选择一个现有分组或[新建分组](#create-a-group)。 5. 设置 **Element ID** ——该元素在分组内的唯一标识符。 6. 如果希望该元素默认处于选中状态,请勾选 **Set as default in group** 复选框。 ## 创建分组 \{#create-a-group\} 分组用于组织屏幕上的可选元素,并定义选择方式——用户可以选择单个选项、多个选项,或进行切换。 创建分组的步骤: 1. 选择一个元素并[将其设为可选](#make-an-element-selectable)。 2. 在 **Group** 下拉菜单中,选择 **Create group**。 3. 输入 **Group name**。 4. 选择[分组类型](#group-types)。 该分组现已在 **Group** 下拉菜单中可用,可供同一屏幕上的其他可选元素使用。 ## 分组类型 \{#group-types\} :::important 大多数[测验预设](onboarding-quizzes)默认为**多选**模式。如需限制为单选,请更改[分组类型](#manage-groups)。 ::: - **单选**:同一时间只能选中分组内的一个元素。选中新元素后,上一个选中项会自动取消。 - **多选**:可以同时选中多个元素。 - **切换**:每次点击,元素在选中与取消之间切换,各元素互不影响。 ## 管理分组 \{#manage-groups\} 要查看和编辑分组,请打开 **Screen settings** 面板,找到 **Selectable groups** 部分,其中列出了当前屏幕上的所有分组。 点击某个分组 ID 可以: - 修改分组 ID - 修改[分组类型](#group-types) - 查看该分组元素在条件中的引用情况 ## 在条件中使用可选状态 \{#use-selectable-state-in-conditions\} 你可以在流程中任意屏幕的条件里引用一个组的选中状态——不仅限于定义该组的屏幕。例如:`IF quiz.photo is selected, THEN navigate to the Photo screen`。 :::important 一个组内的所有元素必须位于同一屏幕。不能将来自不同屏幕的元素添加到同一个组。但是,你可以在流程中任意屏幕的条件里引用组的值。 ::: 可选状态适用于以下场景: - **[条件操作](onboarding-actions#conditional-actions)**:根据用户选择的元素,将其引导至不同的屏幕或触发不同的动作。 - **[动态导航](onboarding-navigation-branching)**:根据问卷答案、切换状态或其他选择对流程进行分支。 - **[条件可见性](onboarding-element-visibility)**:根据用户在之前屏幕上的选择,显示或隐藏相应元素。 --- # File: builder-element-states --- --- title: "元素状态" description: "按状态设置元素样式,并使用条件在运行时禁用元素。" --- 交互式流程元素会根据用户操作改变外观:被点击的测验选项变为 **Selected**(已选中),获得焦点的输入框变为 **Active**(激活)。某些状态由条件驱动——例如,你可以**禁用**一个按钮。为每个状态单独设置样式,即可在无需编写应用代码的情况下向用户提供视觉反馈。 <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/gdsNfHpKAqQ?si=VY5mqZgH1j0RB6fE" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 各元素类型的可用状态 \{#available-states-by-element-kind\} | 元素类型 | 内置状态 | 可添加状态 | |---|---|---| | [可选择元素](#selectable-element-states) | **Default**、**Selected** | **Disabled** | | [输入框](#input-states) | **Default**、**Active**、**Invalid** | **Disabled** | | [任意带点击交互的元素](#condition-driven-disabled-state)——按钮、图片、图标、容器等 | **Default** | **Disabled** | | [进度指示器步骤](#step-states-for-progress-indicators) | **Completed**、**Current**、**Upcoming** | — | **可添加**状态默认不显示——打开 **States settings** Settings 即可添加。它们是[**条件驱动**](#condition-driven-disabled-state)的:由你定义触发条件。 ## 如何为状态设置样式 \{#how-to-style-a-state\} 1. 选择一个元素。右侧面板的 **States** 部分会列出该元素支持的所有状态。 2. 在 **States** 部分,激活目标状态。如有需要,添加[条件驱动的 Disabled 状态](#condition-driven-disabled-state)。 3. 修改任意属性——填充色、边框、字体排版等。该更改仅作用于当前状态。 嵌套元素会与父元素一起变为有状态。对子元素的任何更改都限定在父元素的活动状态范围内。 4. 编辑工具在运行时应用匹配的样式。 ## 可选元素状态 \{#selectable-element-states\} 可选元素——测验选项、产品、标签页、试用开关以及任何[自定义可选元素](flow-selectable-elements#make-an-element-selectable)——开箱即带有两种状态: - **Default**:元素的默认外观。 - **Selected**:用户点击元素时生效。用户取消选中后,编辑工具恢复为 Default 状态。 在单选组中,选中一个元素会自动取消其他元素的选中状态。多选组允许同时选中多个元素。切换开关相互独立——选中其中一个不会影响其他同级元素。请参阅[组类型](flow-selectable-elements#group-types)。 :::tip 需要为多个元素(例如测验选项)设置相同状态的样式?先为一个元素设置好样式,然后复制它。状态样式不会在同级元素之间共享——目前复制是唯一的解决方法。 ::: ## 输入框状态 \{#input-states\} - **Default**:输入框的默认外观。 - **Active**:输入框获得焦点时生效。 - **Invalid**:输入框内容未通过验证时生效。例如,邮箱字段中不包含 `@`。请参阅[输入框验证](builder-inputs-and-forms#input-validation)。 - **Disabled**:输入框不可交互。需手动添加此状态,请参阅[条件驱动的禁用状态](#condition-driven-disabled-state)。 每种状态的样式设置方式与可选元素相同:激活目标状态,然后修改属性。 ## 条件驱动的禁用状态 \{#condition-driven-disabled-state\} 禁用状态会阻止用户与元素进行交互。与默认、已选中、活跃或无效状态不同,禁用状态不会自动激活——它需要用户自定义的触发条件。 禁用状态适用于: - **输入项**:任意[输入字段](builder-inputs-and-forms)——文本、邮箱、密码、数字、电话、日期和/或时间。 - **可选元素**:问卷选项、产品、标签页、试用开关,以及任意[自定义可选元素](flow-selectable-elements#make-an-element-selectable)。 - **带点击交互的任意元素**:例如触发导航操作的按钮、图片或图标。 ### 添加禁用状态 \{#add-the-disabled-state\} 要添加和配置禁用状态: 1. 选择目标元素。 2. 在 **States** 部分,点击 **Settings** Settings。 3. 选择 **Add Disabled state**。Disabled 状态将出现在 **States** 部分。 4. 在新的 Disabled 状态旁边,点击 **Edit conditional state** Edit conditional state。 5. 添加条件。如果您希望在输入未通过验证时禁用 **submit** 按钮,可将输入的 `isValid` 变量与 `false` 进行比较。 6. 为 Disabled 状态设置样式,以直观地传达限制信息(例如,降低透明度)。 { } Adapty SDK 会在运行时评估该条件,并在适当时应用禁用状态——无需任何应用代码。 ## 进度指示器的步骤状态 \{#step-states-for-progress-indicators\} :::link 主要文章:[进度指示器](builder-loaders-and-progress-bars#step-states) ::: 进度指示器向用户展示他们在用户引导流程中的进展情况。每个步骤有三种状态: - **已完成**:用户已经经过的步骤。 - **当前**:用户当前所在的步骤。 - **即将到来**:用户尚未到达的步骤。 --- # File: builder-containers --- --- title: "布局元素:容器、轮播图和底部弹窗" description: "在流程编辑工具中将元素分组为容器、轮播图和底部弹窗。" --- 布局元素用于将其他元素组合在一起,并控制它们在屏幕上的排列方式。 Flow Builder 包含五种布局元素类型: - **容器(Containers)**:沿轴方向排列子元素——竖向或横向 - **轮播图(Carousel)**:可滑动的容器,每次显示一张幻灯片 - **底部弹窗(Bottom Sheet)**:从屏幕底部滑出的面板,叠加显示在底层内容之上 - **页脚(Footer)**:固定在屏幕底部、位于滚动区域之外的面板 - **分隔线(Dividers)**:用于分隔行或列的细线 :::link **选项卡(Tabs)** 也属于此类,但有独立的文章介绍。详见 [选项卡](builder-tabs)。 ::: ## 容器 \{#containers\} :::link 主要文章:[元素定位](manage-paywall-ui-elements) ::: 容器可以将元素按垂直或水平方式分组。**Vertical Container** 将元素排列为行;**Horizontal Container** 将元素排列为列。 :::tip 将容器相互嵌套,可以构建更复杂的布局。 ::: ### 更改容器方向 \{#change-container-direction\} 容器的方向并非固定不变。你可以随时在右侧面板的 **Layout** 区域切换 **Vertical**、**Horizontal** 和 **Free** 模式,无需删除并重新创建容器。 间距、对齐和分布均可在同一 **Layout** 区域中配置。子元素按照它们在 **Layers** 面板中的顺序进行渲染——拖拽即可调整顺序。 ### 包装与解包 \{#wrap-and-unwrap\} 要将现有元素转换为容器,请选中该元素,然后使用 **Wrap** [图层操作](paywall-layout-and-products#layer-actions)。从 **Layers** 面板中将其他元素拖入新容器。若要移除容器并将其子元素提升一级,请使用 **Unwrap**。 ## 轮播图 \{#carousel\} **轮播图**是一种可滑动的容器,每次显示一张幻灯片。用户可以横向滑动切换到下一张,也可以设置定时自动播放。 轮播图由一组**幻灯片**图层组成。当某张幻灯片处于激活状态时,该图层上的元素就会显示在屏幕上。 与 Tabs 不同,轮播图的当前幻灯片不会作为[可选组](flow-selectable-elements)公开——幻灯片无法在条件或动态文本中被引用。轮播图适合用于视觉轮播展示,而非用户驱动的分支逻辑。 ### 切换当前幻灯片 \{#change-active-slide\} 选中轮播组件后,编辑工具会在顶部显示一个弹出控制栏,其中包含 **Slide** 下拉菜单和 **+ Add Slide** 按钮。 - 点击 **+ Add Slide** 可添加一张新的空白幻灯片。 - 使用 **Slide** 下拉菜单切换画布上的当前幻灯片——也可以直接在 **Layers** 面板中点击对应的幻灯片图层。 如需调整幻灯片顺序,在 Layers 面板中将其拖拽到轮播组件内的目标位置即可。 {/* TODO: on-device GIF */} ### 属性 \{#properties\} #### 自动滚动 \{#auto-scroll\} 自动滚动会自动切换幻灯片,用户无需手动滑动即可查看所有内容。 两个时间控制项决定其行为: - **Delay** — 每张幻灯片的停留时长(毫秒)。 - **Duration** — 幻灯片切换动画的持续时长(毫秒)。 #### 轮播图尺寸 \{#carousel-sizing\} 专用控件用于设置轮播组件的尺寸以及相邻幻灯片之间的间距。将 **Height** 设为 **Fixed**,可防止用户在内容长度不同的幻灯片之间滑动时布局发生偏移。 #### 幻灯片尺寸 \{#slide-sizing\} 每张幻灯片的 **Width** 和 **Height**。默认值为 Fill,即每张幻灯片跟随轮播组件的尺寸变化。设置固定宽度可实现"窥视"效果,使相邻幻灯片部分可见。 #### 指示点 \{#dots\} 轮播图底部的页面指示器,显示幻灯片总数及当前激活的幻灯片。 关闭 **Show dots** 开关可隐藏幻灯片指示器。指示器显示时,以下属性控制其外观: - **Color** — 未激活圆点的填充颜色。 - **Active Color** — 当前可见幻灯片对应圆点的填充颜色。 - **Size** — 每个圆点的直径,单位为像素。 - **Gap** — 相邻圆点之间的间距。 - **Padding** — 圆点行与上方轮播内容之间的间距。 ## 底部弹出面板 \{#bottom-sheet\} :::link 操作指南:[在底部弹出面板中展示所有方案](show-plans-bottom-sheet) ::: **底部弹出面板(Bottom Sheet)**是一种从屏幕底部向上滑出的布局面板,会覆盖在底层内容之上。 该面板始终会对背后的内容应用模糊效果,且无法关闭。建议通过点击触发它——例如绑定在 **Show all plans** 链接后面——而不是在页面加载时自动弹出。 ### 结构 \{#structure\} 底部弹窗包含两个顶层图层: - **Heading** — 位于弹窗顶部的容器,预置了 **Title** 文本图层和 **Close button** Close。可按需编辑或删除。 - **Content** — 主内容容器。可在其中放置产品、按钮、链接或其他任意元素。 {/* TODO: on-device GIF */} ### 初始可见性 \{#initial-visibility\} 默认情况下,底部弹出层在屏幕渲染后会立即显示。如需改为按需打开,请按以下步骤操作: 1. **先完成弹出层内容的编辑** — 隐藏状态下的图层无法编辑,因此在内容填充完毕之前,弹出层必须保持可见。 2. 在 **Layers** 面板中,选择底部弹出层。 3. 将 **Visibility** 设置为 **Hide** Hide。 弹出层仍保留在图层树中,但不再在屏幕上渲染。 ### 触发底部弹窗 \{#triggering-the-bottom-sheet\} 要打开隐藏的底部弹窗,需将 **Show** 操作绑定到另一个元素上: 1. 选中触发元素(例如按钮或文本链接)。 2. 在右侧面板中打开 **Interactions** 标签页。 3. 点击 **Add trigger** > **On tap**,然后点击 **Add action**。 4. 将 **Action** 设置为 **Show**,并从下拉菜单中选择对应的底部弹窗。 ## 页脚 \{#footer\} **页脚**是一个固定在屏幕底部的容器。它可以是任意高度,内容也不受限制,可以放一个按钮,也可以放多行文字。当屏幕其余部分滚动时,需要保持不动的内容(如 CTA 按钮、法律声明、链接)都适合放在页脚中。 与普通元素不同,页脚会延伸到设备底部安全区域:其背景色会一直铺到屏幕边缘。 每个屏幕只允许有一个页脚。您不能复制已有的页脚,也不能新增页脚。 ### 底部栏与普通固定元素的区别 \{#footer-vs-a-regular-fixed-element\} 两者都会在内容滚动时保持在屏幕上。根据你的需求选择合适的方案: - **使用 Footer(底部栏)** 放置页面主要底部内容(CTA 按钮、法律文字、链接)。它会自动预留自身高度,确保内容始终可以滚动到其上方而不被遮挡,并自动处理底部安全区域。 - **使用[固定元素](manage-paywall-ui-elements)** 放置需要悬浮在滚动内容上方的控件(而非预留空间),或固定在非底部边缘的控件——例如浮动关闭/恢复按钮、顶部常驻横幅、返回顶部按钮。此类元素的安全区域间距需要你自行管理。 ## 分隔线 \{#dividers\} **水平分隔线**和**垂直分隔线**是用于分隔内容的细线。水平分隔线用于分隔行,垂直分隔线用于分隔水平容器内的列。可在右侧面板中调整粗细、颜色和长度。 --- # File: using-custom-fonts-in-flow-builder --- --- title: "Flow Builder 中的自定义字体" description: "在 Flow Builder 中上传并使用自定义字体。" --- 在构建流程时,你可能希望使用自定义字体以与应用的整体风格保持一致。下面介绍如何添加自定义字体并在流程中使用它们。 :::tip 在开始设计流程之前,先在 **Styles** 面板中[配置字体](onboarding-text)。这样,你所做的任何更改都会全局生效。 ::: ## 内置字体 \{#built-in-fonts\} 在编辑工具中创建流程时,Adapty 默认使用系统字体。iOS 通常为 SF Pro,Android 通常为 Roboto,具体取决于设备型号。你也可以从常用字体中选择,例如 Arial、Times New Roman、Courier New、Georgia 和 Helvetica。这些字体均提供多种字体样式选项。 这些字体并非 Adapty SDK 的内置资源,仅用于预览目的。我们无法保证它们在所有设备上都能完美呈现。不过,根据我们的测试,大多数设备无需任何额外配置即可识别这些字体。你也可以[查看 iOS 默认可用的字体列表](https://developer.apple.com/fonts/system-fonts/)。 ## 添加自定义字体 \{#add-a-custom-font\} :::warning 您上传的文件**仅用于编辑器预览** — Adapty 不会将其推送到用户设备。要在设备上渲染字体,请[将文件添加到您的应用包中](#add-the-font-files-to-your-apps-bundle)。否则,SDK 在运行时将回退至 SF Pro(iOS)或 Roboto(Android)。 ::: 如果您需要使用系统默认字体以外的字体,可以添加自定义字体。 添加自定义字体的步骤: 0. 如果字体是可变字体,请将其拆分为具有唯一名称的单样式文件。粗细、粗体和斜体控件不适用于自定义字体。Adapty 每个自定义字体文件只注册一种样式。如需[设置文本样式](onboarding-text),请将字体切换到对应的变体。 1. 在任意字体下拉菜单中选择 **Upload new font**。 2. 在 **Add custom font** 窗口中,填写以下字段: :::warning **Font name in Builder**、**iOS font name** 和 **Android font name** 在应用中的所有自定义字体文件中必须各自唯一。 ::: - **Font name in Builder**:输入字体的显示名称,该名称将出现在付费墙编辑工具的字体下拉列表中。 - **iOS font name**:输入字体的 PostScript 名称,可在字体册(Font Book)→ PostScript 名称中找到,或通过 [`UIFont` API](https://developer.apple.com/documentation/uikit/uifont) 获取。 - **Android font name**:输入 `res/font/` 目录下的文件名,只能使用小写字母、数字和下划线。 - **Font file**:拖放字体文件或点击 **Select files**,支持的格式:`.ttf`、`.otf`、`.woff`、`.woff2`。 3. 点击 **Save font**。 上传字体文件即表示您确认拥有在应用中使用该字体的合法权利。 ### 删除自定义字体 \{#delete-a-custom-font\} 从看板中删除自定义字体后,所有草稿和已发布流程中对该字体的引用都会被静默替换为系统字体。此操作没有任何提示,也无法撤销。删除前,请确认没有正在使用该字体的线上流程。 ## 流程模板中的自定义字体 \{#custom-fonts-in-flow-templates\} [流程模板库](paywall-builder-templates)中包含使用自定义字体的模板。将鼠标悬停在模板卡片上的 **Custom font** 标签,即可查看该模板使用的字体名称。 Adapty 不内置这些字体,需要你自行获取,字体名称以标签中显示的为准。部分字体可能需要商业授权。 获取字体文件后,请按照以下步骤将其打包到项目中。 ## 将字体文件添加到应用程序包中 \{#add-the-font-files-to-your-apps-bundle\} 如果你已经在应用中的其他地方使用了自定义字体,只需以相同的方式添加付费墙所需的字体即可。如果还没有,请确保将字体文件添加到应用项目和程序包中。具体操作方法请参考以下文档: - iOS 端:[Apple 官方文档](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) - Android 端:[Android 官方文档](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) --- # File: paywall-head-picture --- --- title: "背景" description: "在 Flow Builder 中为屏幕填充纯色、渐变、图片或视频背景。" --- 在任意屏幕的 [**Screen settings**](paywall-layout-and-products#screen-settings) 下,通过 **Fill** 面板设置背景。可从四种背景类型中选择——纯色、渐变、图片或视频。 ## 图片 \{#image\} 上传 `.JPG`、`.PNG`、`.GIF` 或 `.WEBP` 格式的文件,大小不超过 20 MB。图片会缩放以覆盖整个背景。 :::note 背景图片和视频会填充整个视口,包括刘海和系统栏后面的区域——即使启用了**安全区域**也不例外。请将重要内容远离边缘,以免被裁切。 ::: 如需在运行时通过应用代码动态替换背景图片,请启用[自定义媒体 ID](custom-media#custom-media-id)。 ## 视频 \{#video\} 上传 `.MP4` 或 `.WEBM` 文件,大小不超过 50 MB。预览区显示的是静态帧,但在设备上运行时会播放视频。 开启 **Loop** 可让视频循环播放。 如需在运行时动态切换背景视频,请启用[自定义媒体 ID](custom-media#custom-media-id)。 ## 纯色 \{#solid-color\} 输入十六进制色值,并将不透明度设置为 0 到 100%。从色板中选取已保存的[颜色样式](builder-styling)来应用品牌色——背景会自动跟随浅色和深色主题切换。 ## 渐变 \{#gradient\} 构建多色标线性渐变: - **Direction** — 将渐变旋转 0 到 360°。 - **Stops** — 沿色条拖动以调整位置。点击色标可编辑其十六进制颜色值和不透明度。 --- # File: custom-media --- --- title: "图片、视频与图标" description: "在 Flow Builder 的页面中添加图片、视频和图标元素,并通过自定义媒体 ID 在运行时动态替换媒体内容。" --- Flow Builder 在 **Media** 分类下提供三种媒体元素类型:图片(Image)、视频(Video)和图标(Icon)。 :::tip 要让图片或视频覆盖整个屏幕(包括刘海和 Home 指示器后面的区域),请将其定位为固定位置,所有偏移量设为 0,并勾选 **Ignore safe area**。详见[布局与定位](manage-paywall-ui-elements#ignore-safe-area)。 ::: ## 图片 \{#image\} 上传 `.JPG`、`.PNG` 或 `.GIF` 文件,大小不超过 20 MB。 - **Aspect** — 控制图片在容器中的适配方式: - **Fit** — 缩放图片以适应容器,不裁剪。 - **Fill** — 拉伸图片以填满容器。 - **Cover** — 缩放图片以覆盖容器,必要时进行裁剪。默认选项。 - **Use custom media ID** — 参见下方[自定义媒体 ID](#custom-media-id)。 ## 视频 \{#video\} 上传 `.MP4` 或 `.WEBM` 文件,大小不超过 50 MB,时长不超过 30 秒。视频最低分辨率为 640x640 像素。 - **Aspect** — Fit、Fill 或 Cover,默认为 Fill。 - **Loop** — 循环播放视频,默认开启。 - **Use custom media ID** — 请参阅下方的[自定义媒体 ID](#custom-media-id)。 视频在编辑器预览中不会播放——画布上仅显示静态帧。在设备上运行时,视频默认静音播放。开启 Loop 后,视频将无限循环。 ### 视频播放结束时触发动作 \{#trigger-an-action-when-the-video-ends\} :::link 主要文章:[动作](onboarding-actions) ::: 视频元素支持 **On playback finished** 触发器,当视频播放到结尾时触发。在 **Interactions** 面板中进行设置,可跳转到其他页面、显示 CTA,或执行任何其他动作。 ## 图标 \{#icon\} 从内置的 [Tabler Icons](https://tabler.io/icons) 库中选取图标,提供两种视觉风格,图标数量丰富: - **Stroke** — 仅描边轮廓。 - **Filled** — 实心填充。 在选择器中通过关键词搜索图标。在 **Color** 选择器中设置图标颜色——可选择已保存的[颜色样式](builder-styling),也可自定义颜色。 ## 自定义媒体 ID \{#custom-media-id\} :::important 你也可以为图片和视频[背景](paywall-head-picture)设置自定义媒体 ID。 ::: 为图片或视频元素添加自定义媒体 ID,即可在运行时通过应用代码动态替换内容。这适用于[个性化视觉效果](get-pb-paywalls#customize-assets)——例如,显示用户选择的头像。 你在 Flow Builder 中上传的媒体文件将作为备用内容。如果代码在运行时未为该 ID 提供媒体,则会显示备用内容。 要为图片或视频元素启用自定义媒体 ID: 1. 勾选上传区域下方的 **Use custom media ID** 复选框。 2. 输入媒体 ID。 3. 上传备用图片或视频。 在应用代码中,通过 ID 获取媒体资源——SDK API 详见[自定义素材](get-pb-paywalls#customize-assets)。 --- # File: paywall-buttons --- --- title: "Flow 编辑工具中的按钮" description: "在 Flow 编辑工具中添加和配置操作按钮。" --- :::info 本节介绍新版 Flow 编辑工具,该工具需配合 Adapty SDK 4.0 或更高版本使用。 ::: 按钮是流程编辑工具中响应用户点击的交互元素。适用场景包括: - 购买 CTA,与产品关联并自动处理交易 - 导航——在页面之间跳转(下一步、返回、关闭、跳过) - 实用链接——恢复购买、服务条款和隐私政策 :::tip 将购买 CTA、恢复链接和法律链接放置在 [Footer](builder-containers#footer) 中——它固定在屏幕底部,并遮挡下方滚动的元素。 ::: ## 添加按钮 \{#add-buttons\} 添加任意按钮的步骤: 1. 点击 **+** 并选择 **Button**。 2. 选择按钮类型。 <img src="/assets/shared/img/button-type.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 购买按钮、链接和关闭按钮均已预配置操作。对于链接,[配置用于导航用户的 URL](#links)。对于其他按钮类型,请前往 **Interactions** 面板。在 **Button triggers** 部分,设置按钮需要执行的[操作](onboarding-actions)。 <img src="/assets/shared/img/button-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **Design** 面板中配置[按钮设计](builder-styling)。 ## 按钮类型 \{#button-types\} ### 购买按钮 \{#purchase-buttons\} :::link 若要让购买按钮正常工作,请将产品绑定到屏幕并添加 **Products** 元素。请参阅[指南](paywall-product-block)。 ::: 购买按钮会触发用户在屏幕上选中产品的应用内购买流程。SDK 会自动处理交易,因此无需在应用代码中手动处理购买逻辑。 添加购买按钮的步骤: 1. 点击 **+** 并选择 **Button**,然后选择一个按钮预设。 2. 选中按钮后,在右侧面板中打开 **Interactions** 标签页。 3. 点击 **Add trigger** > **On tap**,然后点击 **Add action**。 4. 将 **Action** 设置为 **Purchase**,将 **Product** 设置为 `products.selectedProduct`。`products.selectedProduct` 变量始终解析为当前屏幕上已选中的产品。 :::tip 你可以通过添加动画效果让购买按钮更加醒目。付费墙编辑工具目前支持 **Pulse** 动画类型。 在 **Design** 面板中配置动画样式。 ::: <img src="/assets/shared/img/purchase-button.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 链接 \{#links\} :::important **Terms of Use** 和 **Privacy Policy** 按钮内置了 **Open URL** 操作。请在该操作中设置目标 URL。空的 Open URL 以及[内联链接](onboarding-text#inline-link)会阻止预览和发布。 ::: 为满足部分应用商店的要求,您可以添加以下链接: - 服务条款 - 隐私政策 - 购买恢复 添加链接的方法: 1. 点击 **+**,选择 **Button > Links**。这会添加一行内联按钮,包含预设操作:恢复购买或打开 URL。如果不需要其中某些按钮,可在图层面板中删除多余的按钮。 <img src="/assets/shared/img/add-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 接下来,设置按钮操作: - **Restore purchases** 按钮已自动处理购买恢复功能。 - 对于其余每个链接: 1. 点击按钮将其选中,然后切换到右侧的 **Interactions** 标签页。 2. 将 URL 粘贴到输入框中。 3. 默认情况下,URL 会在应用内浏览器中打开,以提供流畅的用户体验。如果希望在外部浏览器中打开,请勾选 **Open in external browser** 复选框。 <img src="/assets/shared/img/pb-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 关闭流程 \{#close-flow\} **关闭** 按钮可自动关闭流程。 要添加关闭按钮,点击 **+** 并选择 **Button > Close flow**。 <img src="/assets/shared/img/close-flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip 使用 **Absolute** 定位将关闭按钮放置在屏幕角落。 ::: 您也可以通过[操作](onboarding-actions)将任意其他按钮配置为关闭流程。 ### 自定义按钮 \{#custom-buttons\} 你添加的每个按钮都可以配置点击后执行的操作: - 跳转到下一个页面 - 显示弹窗提示 - 设置[变量](onboarding-variables) - [显示或隐藏页面元素](onboarding-element-visibility) - 打开 URL - 恢复购买 - 执行条件操作 --- # File: builder-tabs --- --- title: "标签页" description: "在流程中添加可切换内容面板的标签页导航。" --- **标签页**将屏幕的某个区域拆分为可切换的内容面板——用户点击标签页标头后,下方面板会随之更新。 {/* TODO: on-device GIF */} ## 添加、删除和选择标签页 \{#add-remove-and-select-tabs\} 每个标签页由两部分组成: - **标签页头部** — 可点击的标签名称(Tab 1、Tab 2 等)。 - **标签页内容** — 每个标签页对应一个内容容器,在该容器中添加的内容会在相应标签页被选中时显示。 点击 **Add tab** 可添加新标签页,每个新标签页都会自动创建一个对应的内容容器。 若要设置某个标签页在页面首次显示时默认处于激活状态,请开启 **Selected by default**。 ## 设置标签页样式 \{#style-the-tabs\} ### 模板 \{#templates\} Flow Builder 提供三种开箱即用的标签页模板: - **Segment control** — 胶囊形切换器,选中标签带有圆角边框。 - **Button Tabs** — 独立的按钮式标签页。 - **Underline** — 文字标签,选中标签下方带有下划线标记。 ### 选项卡状态 \{#tab-states\} 每个选项卡都有一个状态切换器(**Default / Selected**),可以分别为激活和未激活状态设置样式——包括字体、颜色、填充色和边框。 ## 可选组 \{#selectable-group\} 标签页是一种**单选可选组**——同一时间只有一个标签处于激活状态。在 **Screen settings** 面板的 [Selectable groups](paywall-layout-and-products#selectable-groups) 部分管理该组。 该组提供两个变量: - `tabs.selectedOptionId` — 当前选中标签页的 ID,可用于条件判断。 - `tabs.selectedOptionTitle` — 当前选中标签页的标签文本,可用于动态文本。 如果你重命名了该组,请将 `tabs` 替换为你自定义的 **Group ID**。 详细说明请参阅[可选元素与组](flow-selectable-elements)。 --- # File: builder-toggles --- --- title: "切换开关" description: "为您的支付流程添加切换开关。" --- :::warning Apple 可能会拒绝使用预选试用切换开关的应用。默认设置为"开启"的切换开关可能被认定为违反 App Store 审核指南的暗黑设计模式——它在用户未明确选择的情况下暗示其同意免费试用。 为避免被拒绝,请将切换开关默认设置为 **off**,让用户自行选择是否开启试用。 ::: 试用切换开关是一个二元开关,允许用户在付费墙上选择标准产品或试用型产品。当用户改变其状态时,可以立即触发某个操作——例如切换产品组、更新变量,或显示/隐藏元素。 要添加试用切换开关,点击目标屏幕上的 **+**,然后选择 **Trial toggle**。 每个试用切换开关都是 **Toggle** 类型的可选元素。每个可选元素都分配有一个变量来反映其状态——例如,名为 `trial` 的切换开关会获得一个值为 `True` 或 `False` 的 `trial.is_selected` 变量。 要让其他元素依赖切换开关的状态,请基于此变量设置条件[动作](onboarding-actions)或[条件可见性](onboarding-element-visibility)。 --- # File: builder-reviews-and-testimonials --- --- title: "评论与用户推荐" description: "在付费墙中添加评论、评分和社交证明。" --- **User Engagement** 元素分类提供了四个模板,用于在付费墙上展示评论、评分和社交证明。每个模板都是完全可编辑的组合——替换占位文本,并应用你的[颜色样式](builder-styling)和[排版设置](onboarding-text)以与整个流程保持一致。 ## 评论 \{#review\} 带有评分、引言和作者署名的卡片。适合展示一条令人印象深刻的用户评价。 ## 评分 \{#rating\} 计数与星级行,例如"17000+ 评分"。用于突出显示评分数量。 ## 应用评分 \{#app-rating\} 带有样本量的突出评分,例如"4.9 / 基于 1000+ 条评价"。适合用来展示整体高评分。 ## 社交证明 \{#social-proof\} 带有头像组和成员数量的展示区,例如"加入 50,000+ 用户"。用于强调社区规模。 --- # File: flow-timer --- --- title: "倒计时器" description: "在付费墙中添加倒计时器。" --- **倒计时器**从固定时长开始倒数至零——归零后画面将停止。 ## 模板 \{#templates\} 该分类提供四种视觉样式: - **Blocks** — 将天、时、分、秒分别显示在带标签的独立格子中。 - **Inline Units** — 带单位后缀的单行文本。 - **Inline** — 纯数字显示。 - **Badge** — 胶囊形数字展示。 ## 设置 \{#settings\} ### 设置时长 \{#set-the-duration\} 在右侧面板的 **Countdown** 部分,输入倒计时的起始时长(天、小时、分钟、秒)。 ### 配置行为 \{#configure-the-behavior\} **Behavior** 下拉菜单用于控制计时器的启动时机: - **Every appear** — 每次用户打开该页面时重新开始计时。默认选项。 - **First appear** — 在当前 App 会话中用户首次查看该页面时开始计时。若用户在同一会话内返回该页面,计时继续;重新启动 App 后重置。 - **First appear (persisted)** — 在用户首次打开该页面时开始计时,并在 App 重启后持续计时。 ### 计时器结束时触发动作 \{#trigger-an-action-when-the-timer-ends\} :::link 主要文章:[动作](onboarding-actions) ::: 添加 **On timer end** 触发器,在倒计时归零时执行动作——例如跳转到另一屏幕或隐藏折扣标签。 --- # File: onboarding-quizzes --- --- title: "流程中的测验" description: "在您的 Adapty 流程中添加互动测验,以收集用户偏好并驱动个性化流程——无需编写代码。" --- 使用测验向用户呈现预定义的选项。与输入框不同,测验没有文字输入字段——用户从您定义的选项中进行选择。可用于收集用户偏好、进行市场细分,或根据用户的回答对流程进行分支跳转。 ### 添加测验 \{#add-a-quiz\} 1. 点击左上角的 **+**。 2. 选择 **Quiz**。 3. 选择测验类型: - **Icon/image/emoji options:** 纵向排列的可选选项列表,每个选项包含图标、图片或表情符号以及文字标签。 - **Icon/image/emoji grid:** 网格形式的可选选项,每个选项包含图标、图片或表情符号。 - **Rating:** 供用户表达评分的量表——支持数字或星级形式。 ### 设置条件导航 \{#set-up-conditional-navigation\} 如需根据用户的选择将其引导至不同页面,请在**导航按钮**上设置条件动作,而不是在测验选项上设置: 1. 选择导航按钮。 2. 在 **Interactions** 面板中,添加一个 **On Tap** 触发器,并选择 **Conditional** 动作。 3. 在 **Edit Action** 对话框中,构建 **if** 行: - 在左侧,点击 `{}` 并选择 **Elements → Screen → `<quizElementId>.selectedOptionId`**,以引用用户的选项。 - 将运算符保持为 `=`。 - 在右侧,输入要匹配的 elementId,例如 `rock`。 4. 在 **then** 下方,将动作设置为 **Navigate to**,并选择目标页面。 5. 在 **else** 下方,设置一个备用的 **Navigate to** 目标,或点击 **+ Add else/if** 为其他选项添加更多条件。 :::link 参阅相关指南,了解如何使用测验答案: - [条件导航](onboarding-navigation-branching) - [变量](onboarding-variables) - [操作](onboarding-actions) ::: ### 更改测验类型 \{#change-quiz-type\} 默认情况下,测验为**多选**模式——用户可以同时选择多个选项。如果希望用户只能选择一个选项,请切换为**单选**模式。 1. 选择包含测验的屏幕。 2. 在 **Screen settings** 中,滚动到 **Selectable groups**,然后点击你的测验。 3. 在 **Edit group** 对话框中,打开 **Group type** 并选择: - **Single choice** — 每次只能选择一个选项。 - **Multi choice** — 用户可以选择多个选项。 4. 点击 **Save**。 --- # File: builder-inputs-and-forms --- --- title: "Flow Builder 中的输入框与表单" description: "添加文本字段、复选框等交互式表单元素。" --- 使用输入框收集用户填写的数据——例如姓名、电子邮件地址或出生日期。将用户的回答保存下来,并在流程的其他地方引用,比如在后续页面上用姓名称呼用户。 ## 添加输入框 \{#add-an-input\} 1. 点击左上角的 **+**。 2. 选择 **Input**。 3. 选择输入类型: - **Text:** 任意短文本输入。 - **Email:** 电子邮件地址,可选格式验证。 - **Password:** 安全文本输入,可配置密码要求。 - **Number:** 数字值,可配置格式。 - **Phone number:** 电话号码。 - **Date:** 打开日期选择器。 - **Time:** 打开时间选择器。 - **Date and time:** 打开组合选择器。 ## 配置输入项 \{#configure-an-input\} :::link 有关视觉设置(布局、样式和可见性)的更多详情,请参阅[样式与外观](builder-styling)。 ::: 对于所有输入类型,您可以在 **Design** 标签页中配置以下内容: - **Type(类型):** 更改输入类型(Text、Email、Password、Number、Phone number、Date、Time 或 Date and time)。 - **Element ID(元素 ID):** 用于在流程中其他位置引用该输入值的标识符。详见下方[使用输入值](#use-input-values)。 - **Placeholder(占位符):** 显示在空字段内的提示文字。 - **State(状态):** 定义输入框在不同情况下的外观。可在 **Default**、**Active**、**Invalid** 和 **Disabled** 之间切换,并为每种状态设置不同的视觉样式。 - **Typography(排版):** 字段中显示内容的文字样式。 - **Leading and trailing icons(前置与后置图标):** 在输入框内添加图标。 某些设置仅适用于特定的输入类型: | 设置 | 输入类型 | |----------------------|-------------------------| | 清除按钮 | 文本、电子邮件 | | 验证电子邮件格式 | 电子邮件 | | 显示密码图标 | 密码 | | 编辑密码要求 | 密码 | | 数字格式 | 数字 | | 日期/时间格式 | 日期、时间、日期和时间 | | 最小和最大日期 | 日期、日期和时间 | ## 使用输入值 \{#use-input-values\} 每个输入都会自动作为变量使用,无需任何配置或 **On Submit** 操作。输入值通过 **Element ID** 引用,该 ID 在 **Input Settings** 中设置。 要在流程的其他位置使用输入值(例如个性化文案、填充其他字段或驱动条件导航),插入一个变量并选择: **Element > Screen > `<elementId>.value`** :::link 查看以下相关指南,了解如何使用已保存的输入值: - [条件导航](onboarding-navigation-branching) - [变量](onboarding-variables) ::: ## 输入验证 \{#input-validation\} 验证行为取决于输入类型。每个输入都会暴露一个只读布尔变量 `<elementId>.isValid`,用于反映输入值是否通过了该输入的验证规则。你可以在条件动作或条件可见性中使用它——例如,在电子邮件格式有效之前隐藏"下一步"按钮。 :::note - `isValid` 变量是只读的——你无法设置它。 - 空输入始终被视为有效。 - 文本输入没有验证规则。`textInput.isValid` 始终返回 `True`。 ::: | 输入类型 | 验证行为 | |---|---| | 文本 | 无内置验证规则。 | | 电子邮件 | 可选。在 **Design** 面板中启用 **Validate email format**,以对输入的值进行电子邮件格式校验。 | | 电话号码 | 内置电话号码格式检查。无法在编辑工具中配置——该规则在运行时进行评估。 | | 密码 | 可配置。请参阅下方的[密码要求](#password-requirements)。 | | 数字 | 基于格式。输入的值必须符合所选的数字格式。请参阅下方的[数字格式](#number-format)。 | | 日期、时间、日期和时间 | 内置。选择器仅接受有效的日期或时间值。 | **Invalid** [视觉状态](builder-styling#input-states)会在用户提交表单时触发——例如按下键盘上的 Enter 或 Done 键。在此之前,输入框显示 **Active** 或 **Default** 状态。 ### 密码要求 \{#password-requirements\} 密码输入框支持可配置的验证规则。在 **Design** 面板中点击 **Edit password requirements** 即可打开规则编辑器。已启用的规则会以实时清单的形式显示在输入框下方——每条规则满足条件时,对应项目前会出现勾选标记。 可用规则: - **Min length** — 最少字符数。默认值:8。 - **Max length** — 最多字符数。默认值:32。 - **Uppercase letter** — 至少包含一个 A–Z 大写字母。 - **Lowercase letter** — 至少包含一个 a–z 小写字母。 - **Number** — 至少包含一个数字。 - **Special character** — 至少包含一个非字母数字字符(例如 `!@#$%`)。 只有满足所有已启用的规则,密码才视为有效。 ### 数字格式 \{#number-format\} **Number** 输入设置中的 **Format** 下拉菜单控制输入值的解析方式: - **Integer** — 仅限整数(例如 `4`)。 - **Decimal (Point)** — 以小数点作为分隔符的小数(例如 `4.89`)。 - **Decimal (Comma)** — 以逗号作为分隔符的小数(例如 `4,89`)。 不符合所选格式的值将被视为无效。 ## 根据输入事件触发动作 \{#trigger-actions-on-input-events\} :::link 主要文章:[动作](onboarding-actions) ::: 你可以通过 **Interactions** 面板在用户输入时触发相应动作: - **On changed** — 当用户更改输入值时触发。所有输入类型均可使用。 - **On submit** — 当用户通过键盘按下 Enter 或 Done 提交文本输入时触发。日期和时间选择器不支持此触发器。 --- # File: onboarding-navigation-branching --- --- title: "导航与分支" description: "使用静态路由和动态分支,引导用户浏览各个屏幕。" --- 导航与分支功能让你能够引导用户完成流程中的每一步:使用静态路由将所有人引导至核心屏幕,使用动态导航则可根据用户的选择灵活调整流程走向。 :::link 导航是一种操作类型。如需了解更多关于操作的信息,请参阅[操作](onboarding-actions)。 ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/OLl-WziDMhU?si=_eUtsmbEuFAaLj1r" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## 在屏幕之间导航 \{#navigate-between-screens\} 你可以使用不同的流程元素配置静态和动态导航。 ### 静态导航 \{#static-navigation\} 静态导航会将所有用户引导到同一个目标页面。设置步骤如下: 1. 选择用户可以点击的任意元素——按钮、问卷答案或切换开关。 2. 在右侧打开 **Interactions** 面板,点击 **Add trigger**。 如果希望用户点击问卷选项后立即跳转,而无需单独点击按钮,请在此处选择问卷选项元素,而不是按钮。 3. 设置 **On tap** 触发器: - **Action**:选择 **Navigate to screen**。 - **Destination**:选择目标页面。 ### 动态导航 \{#dynamic-navigation\} 动态导航根据用户的测验答案、切换元素状态以及自定义属性来路由用户。 任何[可选元素](flow-selectable-elements)都可以作为动态导航的条件。 设置步骤: 1. 选择要用于导航用户的元素。 2. 在右侧打开 **Interactions** 面板,点击 **Add trigger**。 如果希望用户点击测验选项后立即跳转,而无需再单独点击按钮,请在此处选择测验选项元素,而非按钮。 3. 设置 **On tap** 触发器: - **Action**:选择 **Conditional**。 - **Conditions**:设置条件导航动作。详情请参阅[此处](onboarding-actions#conditional-actions)。 ## 关闭流程 \{#close-flow\} 如果你的用户旅程需要关闭流程,可以通过按钮或单选问答来实现: 1. 添加并选中点击后需要关闭流程的元素。 2. 打开右侧的 **Interactions** 面板,点击 **Add trigger**。 3. 设置 **On tap** 触发器: - **Action**:选择 **Close flow**。 --- # File: onboarding-actions --- --- title: "操作" description: "在编辑工具中定义由用户交互触发的操作。" --- **Interactions** 面板用于定义流程元素如何响应事件——例如点击、元素出现以及表单提交。对于每个事件,你可以分配一个或多个操作:在屏幕间导航、显示或隐藏元素、打开 URL、设置变量等。使用条件可根据用户数据自定义流程。 每个交互遵循三段式结构: 1. **元素**:触发交互的屏幕组件——按钮、问卷答案、输入框或其他任何控件。 2. **触发器**:激活逻辑的事件,例如点击、元素出现或表单提交。 3. **动作**:流程在响应时执行的任务。一个触发器可以按顺序执行多个动作。 ## 设置交互 \{#set-up-interactions\} 设置交互的步骤如下: 1. 在画布上或 **Layers** 面板中选择一个元素。 2. 在右侧切换到 **Interactions** 面板,点击 **Add trigger**。 3. 在 **Button triggers** 部分,选择[触发器类型](#trigger-types)。 4. 点击 **Add action**,点击操作名称,然后在 **Edit action** 窗口的下拉菜单中选择[动作类型](#action-types)。 5. 根据你选择的[操作类型](#action-types)配置相应的操作属性。 6. 如需添加更多操作,点击 **Add action** 为同一触发器继续添加。 ## 触发器类型 \{#trigger-types\} 触发器会在用户行为、元素状态变化或屏幕加载时触发。**On screen appear** 是通用触发器,其余触发器与特定元素绑定。 | 触发器 | 触发时机… | 支持的元素 | |---|---|---| | **On screen appear** | 屏幕加载时 | 所有元素 | | **On tap** | 用户点击该元素时 | [按钮](paywall-buttons)、[测验选项](onboarding-quizzes)、[开关](builder-toggles)、[倒计时](flow-timer)、[视频](custom-media) | | **On changed** | 用户更改输入值时(输入文字、选择日期或时间) | 所有[输入元素](builder-inputs-and-forms) | | **On submit** | 用户按下键盘上的 Enter 或 Done 提交文本输入时 | [文本类输入](builder-inputs-and-forms) | | **On timer end** | [倒计时](flow-timer)元素归零时 | [倒计时](flow-timer) | | **On playback finished** | [视频](custom-media)播放结束时 | [视频](custom-media) | 对于没有内置交互的元素(例如 [Loader](builder-loaders-and-progress-bars)),**On screen appear** 是唯一可用的触发器。 ## 操作类型 \{#action-types\} :::important **任何导航操作**(即将用户切换到其他页面的操作)都应始终放在操作列表的最后。排在它之后的操作(例如"设置变量")可能不会执行,因为应用已经切换页面了。 ::: ### 导航至指定屏幕 \{#navigate-to-screen\} 这是在屏幕之间切换用户的主要操作,可将用户带到指定的目标屏幕。 使用此操作时,只需设置目标屏幕即可。如需启用动态导航,请参阅[导航与分支](onboarding-navigation-branching)或[条件操作](#conditional-actions)部分。 ### 导航到下一屏 \{#navigate-next\} 将用户推进到流程中的下一个屏幕。适用于线性流程——屏幕在编辑器中的排列顺序即为用户实际看到的顺序。 ### 返回上一页 \{#navigate-back\} 返回用户导航历史中的上一个页面,而非序列中的上一个页面。 ### 打开 URL \{#open-url\} :::tip 使用[内联链接](onboarding-text#inline-link)在正文中插入链接。 ::: 打开指定的网页地址。可用于将用户引导至网页、文章或应用外部的社交媒体主页。 对于此操作,您可以配置两项设置: - **URL address**:设置一个 URL 地址。此外,您还可以将其设置为动态 URL —— 例如,根据用户的问卷答案或提交的数据将其导航到不同页面。为此,请点击 Variable icon 并选择您要使用的变量。 - **Open in external browser**:定义外部链接的打开方式。默认情况下,链接会在应用内浏览器中打开,以使用户留在应用内。如果您希望在外部浏览器中打开链接,请勾选 **Open in external browser** 复选框。 ### 关闭流程 \{#close-flow\} 关闭当前流程。 ### 显示/隐藏元素 \{#showhide-elements\} 显示或隐藏屏幕上的特定元素。 此操作会覆盖 **Design** 面板中 **Visibility** 的初始状态设置。如果 **Visibility** 设置为 **Hide**,则 **Show** 操作会使该元素显示出来。 :::important **Show** 或 **Hide** 操作若没有指定目标元素,将[阻止预览和发布](builder-save-publish#troubleshooting)。请选择目标元素或删除该操作。 ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ### 显示提示框 \{#show-alert\} 显示原生系统弹出窗口。用户必须点击 **Ok** 才能继续。 对于提示框,你需要设置其 **Title** 和 **Message**。在这两个字段中,你都可以使用变量来实现动态内容。点击 Variable icon 并选择要使用的变量即可。 :::important 配置为空或不完整的 **Show alert** 操作会[阻止预览和发布](builder-save-publish#troubleshooting)。请填写两个字段,或删除该操作。 ::: ### 设置变量 \{#set-variable\} 更新流程中某个变量的值。在添加此操作之前,请先在左侧 **Variables** 面板中创建变量(参见[变量](onboarding-variables))。 点击 **Add variable**,根据需要设置任意数量的变量及其值。 :::important 未配置赋值的 **Set variable** 操作会[阻止预览和发布](builder-save-publish#troubleshooting)。请至少配置一条赋值,或删除该操作。 ::: ### 购买 \{#purchase\} 直接在用户引导的按钮或交互操作中触发购买流程。使用此功能可让用户无需离开当前流程即可完成订阅或购买产品。 您可以为此操作配置两种行为: - **In-app store**:发起原生购买。将 **Product** 设置为特定产品,或设置为 `products.selectedProduct` 以使用用户当前在屏幕上的选择。 - **Web payment**:将用户引导至[付费墙网页](web-paywall),而非触发原生购买。当您希望在应用外处理交易时使用此选项,例如基于 Web 的订阅方案。 :::important **Purchase** 操作如果没有指定目标 **Product** 或 **Web Paywall URL**,将[无法预览和发布](builder-save-publish#troubleshooting)。请为其指定目标或删除该操作。 ::: ### 恢复购买 \{#restore-purchases\} 在设备上触发恢复购买流程。当用户之前在其他设备上购买过订阅,或重新安装应用后需要恢复其权益时,可点击此按钮。 此操作无需任何配置——Adapty 会通过原生商店流程处理恢复操作。 **Restore purchases** 操作也预先配置在 **Links** 按钮预设的 **Restore** 链接上(请参阅[设置购买](paywall-product-block#restore-purchases))。 ## 自定义动作 \{#custom-actions\} 自定义动作会触发一个具名的 **Action ID**,由你自己的应用代码来处理。当内置动作类型无法满足需求时,可以使用它。 Adapty 负责触发,你的应用负责实现具体行为: 1. 在编辑工具中,为某个元素的交互指定一个 **Action ID**。 2. 当用户触发该交互时,流程会将此 ID 传递给你的应用。 3. 你的应用根据 ID 进行匹配,并执行相应代码。 ### 设置自定义动作 \{#set-up-a-custom-action\} 1. 在 **Edit action** 窗口中,为其分配一个 **Action ID**——一个你的应用能识别的字符串(例如 `show_discount`)。 2. 在应用代码中,为该 Action ID 实现一个处理程序。具体实现细节和代码示例请参阅[处理付费墙动作](handle-paywall-actions)。 :::important 未设置 **Action ID** 的 **Custom** 动作会[阻止预览和发布](builder-save-publish#troubleshooting)。请为其分配 Action ID 或删除该动作。 ::: ### 自定义动作的应用场景 \{#what-you-can-do-with-custom-actions\} 自定义动作本身不会执行任何操作。你需要在编辑工具中设置一个静态 Action ID,然后由应用代码来处理接收到该 ID 后的逻辑。以下所有使用场景都遵循同一模式:在流程中分配 ID,然后在代码中处理它。 - **触发应用内事件**:触发一个 ID(如 `viewed_special_offer`),然后在应用收到时将该事件记录到你的分析系统。 - **请求系统权限**:触发一个 ID(如 `request_location`),然后在应用中调用系统权限弹窗。对于无法通过弹窗授予的权限,改为打开手机的系统设置。Adapty 不显示弹窗——由你的应用来处理。 - **启动原生身份验证**:触发一个 ID(如 `login_google`),然后展示你自己的登录界面。该流程无法直接为用户登录。 - **执行业务逻辑**:触发一个 ID(如 `apply_discount`),然后在你这边解锁内容或更改应用状态。 - **将问卷答案传递给应用**:为每个选项分配不同的 Action ID(例如 `goal_weight_loss` 和 `goal_muscle`),然后在代码中读取该 ID。用这个 ID 设置一个[自定义用户属性](setting-user-attributes#custom-user-attributes),以便后续进行市场细分。由于动作只携带固定 ID,这是上报用户所选内容的唯一方式——流程本身无法发送所选的值。 :::important 自定义动作在用户选择选项的瞬间触发。如果用户更改了答案,流程也会触发新的 Action ID。你的应用会依次收到两个信号,例如先收到 `goal_weight_loss`,再收到 `goal_muscle`。请确保你的处理器是幂等的,以最新信号为准。 ::: ### 自定义动作的局限性 \{#what-custom-actions-cant-do\} 自定义动作是静态的。Action ID 在构建流程时就已固定——它无法读取[变量](onboarding-variables)或[用户输入](builder-inputs-and-forms)。当动作触发时,你的应用只会收到该 ID,而不会收到用户输入的邮箱、手机号或其他内容。输入字段作为变量保留在流程内部,用于分支逻辑和个性化处理。如需在应用中使用这些值,请通过自己的 UI 或 API 来收集。 自定义操作也是单向的。你的应用无法向流程返回结果,流程也不会等待你的代码执行完毕。如果自定义操作后面跟着 **Navigate next** 操作,即使代码执行失败,用户也会跳转到下一个屏幕——例如,用户关闭登录界面但未完成登录时。再加上静态的 Action ID,这就决定了你无法在应用中验证用户输入——例如,检查用户输入的短信验证码并根据结果进行分支跳转。如果后续流程依赖于你的代码执行结果,请[将流程拆分到两个版位中](#continue-the-flow-based-on-the-result)。 ### 根据结果继续执行流程 \{#continue-the-flow-based-on-the-result\} 如果某些屏幕只应在自定义操作成功后才显示——例如,登录后才显示的屏幕——可以将流程拆分为两个[版位](placements),由应用决定何时显示第二部分: 1. 在第一个版位中,创建一个以自定义操作结束的流程(例如 `login`)。 2. 在应用中处理 Action ID:展示登录界面并判断用户是否已成功登录。 3. 如果用户已登录,则从第二个版位加载后续屏幕并展示流程。 这样,您的应用程序可以根据实际结果来控制页面跳转,而不是让流程无论结果如何都自动向前导航。 ## 条件操作 \{#conditional-actions\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/xmWSEPxnI0s?si=mazHQHE89qEDxvPA" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 使用条件动作,可以根据用户数据将流程拆分为不同路径。 常见使用场景包括: - 页面上有一个测验,您希望根据用户的答案将其导航到不同的页面。此时,为按钮添加条件动作即可。 - 您希望向不同用户群体提供不同的产品和优惠。将它们分别放置在不同页面上,并为导航按钮设置条件。 - 您希望跳过已在上一个应用会话中完成教程的用户的某些步骤。 条件操作的工作方式类似于 if / else-if / else 链。应用从上到下依次读取规则,匹配到第一条后即停止: 1. **IF**:流程检查主要条件。 - 条件为 True?流程立即执行 THEN 操作并停止。 - 条件为 False?流程跳到下一节。 2. **ELSE IF**:可在此添加额外条件(例如"如果不是高级用户,该用户是否处于试用期?")。 3. **ELSE**(兜底):如果上述所有规则均未匹配,流程将执行此最终节中的操作。 :::important - 如果某条规则已添加但未分配动作,则匹配该条件后不会执行任何操作。 - 不完整的规则(缺少运算符或值)会[阻止预览和发布](builder-save-publish#troubleshooting)。 ::: 每条规则需选择一个待评估的变量和一个要执行的动作。每条规则可设置多个动作。 :::important 流程只执行第一条匹配的规则。如果需要同时执行 **IF** 和 **ELSE IF**,请将两个动作都添加到 **IF** 中。 ::: 要了解如何将元素设为可选并将其组织成组以便在条件中使用,请参阅[可选元素与组](flow-selectable-elements)。 ## 故障排除 \{#troubleshooting\} 任何缺少必填字段的操作都会阻止预览和发布。完整列表请参阅[保存并发布流程](builder-save-publish#troubleshooting)。 --- # File: builder-loaders-and-progress-bars --- --- title: "进度指示器与加载动画" description: "在流程中显示步骤进度和加载状态。" --- **Progress** 分类提供两种元素类型——一种用于在多屏幕流程中显示步骤进度,另一种用于原位加载状态指示。 ## 进度指示器 \{#progress-indicators\} ### 进度条样式 \{#indicator-styles\} **Progress** 元素用于显示用户在多屏流程中的当前位置。该类别提供三种视觉样式: - **Linear** — 单条进度条,随用户前进而填充。 - **Segmented** — 每步对应一段独立的进度条,逐步填充。 - **Connectors** — 带标签的圆圈通过连线串联(例如,依次显示 Step 1、Step 2、Step 3)。 ### 将步骤与屏幕对应 \{#match-steps-to-screens\} 默认情况下,进度指示器会追踪流程中的每个屏幕。若只想追踪其中几个屏幕,可在 **Screens** 下拉菜单中选择。也可以打开想要排除的屏幕,取消勾选 **Include screen in progress indicator** 复选框。 如果需要更精细地控制步骤数量,可以关闭 **One segment per screen** 开关。 :::warning 步骤位置取决于屏幕列表中的顺序,而非用户实际浏览的顺序。在非线性流程中,指示器显示的步骤编号可能会跳跃或倒退。 ::: ### 步骤状态 \{#step-states\} 每个步骤都有三种状态——**Completed**、**Current** 和 **Upcoming**。在进度指示器中选择某个步骤,即可在右侧面板中编辑该状态的样式。使用 **Apply changes to all states** 可将当前编辑同步到另外两种状态。 修改某个步骤会影响同一指示器内的所有步骤。 ### 布局与定位 \{#layout-and-positioning\} 进度指示器是一个全局元素,因此无法将其放置在[容器](builder-containers)内,也无法设置其位置——它默认使用绝对定位。当用户滚动屏幕时,指示器保持固定不动,内容在其下方滚动。 要控制指示器周围的间距,请使用 **Spacing** 区域中的 **Margin** 和 **Padding** 控件,而不是直接移动元素。如果布局显示不正确,可以同时调整指示器及其相邻元素的外边距或内边距。 ## 加载动画 \{#loaders\} **加载动画(Loader)** 是一种动态元素,用于提示用户当前正在处理某项任务,例如:正在分析用户的问卷答案以生成个性化方案。 该分类提供三种模板: - **Spinner** — 圆形旋转加载动画。 - **Spinner with label** — 带说明文字的圆形旋转加载动画(例如:"Loading...")。 - **Loader** — 随进度填充的横向进度条。 {/* - **Loader with label** — A horizontal bar with a caption and percentage (e.g., "Analyzing... 47%"). */} :::warning 加载器需要通过**触发器**来控制显示和隐藏。请打开 **Interactions** 标签页来设置相关逻辑——例如,在用户提交测验后显示加载器。 ::: --- # File: onboarding-variables --- --- title: "变量" description: "使用变量在流程中显示动态数据。" --- 变量让你在流程中展示动态内容——产品定价、优惠详情,以及其他根据每位用户上下文实时更新的数据。你可以用变量控制元素的显示状态,并个性化页面内容。 点击左侧面板中的 **{ }** 图标即可打开变量面板。该面板包含三个标签页: - **[自定义](#custom-variables)**:由您自行创建和管理的变量。 - **[产品](#product-variables)**:从商店中获取本地化产品和优惠数据的内置变量。 - **[元素](#element-variables)**:绑定到画布上元素状态的变量。 ## 自定义变量 \{#custom-variables\} ### 创建自定义变量 \{#create-a-custom-variable\} 1. 在变量面板中,点击 **+**。 2. 输入变量名称。 3. 选择类型:String、Number 或 Boolean。 4. 设置初始值。这是流程启动时变量所持有的值。 5. 点击 **Create variable**。 :::tip 在名称中使用点号可以将相关变量分组,例如 `user.score` 或 `user.goal`。 ::: ### 通过交互更新变量 \{#update-a-variable-via-an-interaction\} :::link 详情请参阅 [Actions](onboarding-actions) 文章。 ::: 你可以在运行时通过为任意元素添加 **Set up variables** 动作来更新变量值。 1. 在画布上选择一个元素。 2. 在 **Interactions** 标签页中,点击 **Add trigger**。 3. 选择 **On tap**,然后点击 **Add action**。在 **Action type** 下拉菜单中选择 **Set up variables**。 4. 点击 **Add variable**,选择变量并设置新值。 :::tip 例如,您可以根据用户选择的测验答案为 `user.goal` 赋予不同的值,然后使用该变量将用户导航到不同的页面。 ::: ## 产品变量 \{#product-variables\} 产品变量直接从各应用商店获取本地化数据。你可以在文本字段中使用它们来展示本地化价格、标题和优惠详情,也可以在条件判断中根据优惠资格显示或隐藏相应内容。 | 变量 | 描述 | 示例 | | :--- | :--- | :--- | | `prod_title` | 产品的本地化标题 | Premium Subscription | | `prod_price` | 每个计费周期的本地化价格 | $9.99 | | `prod_price_per_day` | 订阅价格除以计费周期天数。非订阅商品为空。 | $0.33 | | `prod_price_per_week` | 订阅价格除以计费周期周数。非订阅商品为空。 | $2.33 | | `prod_price_per_month` | 折算为一个月的订阅价格。非订阅商品为空。 | $9.99 | | `prod_price_per_year` | 折算为一年的订阅价格。非订阅商品为空。 | $119.88 | | `offer_price` | 新用户优惠或促销活动的本地化价格。若用户不符合任何优惠条件则为空。 | $0.99 | | `offer_billing_period` | 优惠的本地化计费周期。对于免费试用和预付优惠,与 `offer_full_duration` 相同。若用户不符合条件则为空。 | 1 week | | `offer_full_duration` | 优惠的本地化完整时长。若用户不符合条件则为空。 | 1 month | | `is_free_trial` | 若用户符合免费试用优惠条件,返回 `true`。 | true | | `is_pay_up_front` | 若用户符合预付优惠条件,返回 `true`。 | true | | `is_pay_as_you_go` | 若用户符合按量付费优惠条件,返回 `true`。 | true | :::tip 使用 `is_free_trial`、`is_pay_up_front` 和 `is_pay_as_you_go` 配合条件可见性,根据用户符合条件的优惠类型来显示或隐藏元素。例如,仅在 `is_free_trial` 为 `true` 时显示免费试用时间线。 ::: 优惠变量的值取决于用户符合条件的优惠类型。以一个名为"Premium Subscription"的每周订阅为例,售价 $5,包含三种可能的优惠: - **按使用量付费**:前 3 周每周 $3(按周计费),之后每周 $5。 - **预付款**:前 3 周一次性付 $8,之后每周 $5。 - **免费试用**:第一周免费,之后每周 $5。 在此示例中,`prod_title` 返回"Premium Subscription",`prod_price` 返回 $5。优惠变量的值取决于用户有资格享受哪种优惠: | 变量 | 按使用量付费 | 预付款 | 免费试用 | | :--- | :--- | :--- | :--- | | `offer_price` | $3 | $8 | $0 | | `offer_billing_period` | 1 周 | 3 周 | 1 周 | | `offer_full_duration` | 3 周 | 3 周 | 1 周 | 对于"预付款"和"免费试用"优惠,`offer_billing_period` 和 `offer_full_duration` 返回相同的值。对于"随用随付",两者不同,因为计费周期为一周,但完整时长为三周。 :::note 如需了解更多关于优惠及其配置方式,请参阅[优惠](offers)。 ::: ## 元素变量 \{#element-variables\} 元素变量用于捕捉用户的选择——他们在测验中选了什么、当前处于哪个标签页,以及试用切换是否开启。 元素变量的类型取决于所属分组: - **单选**:单选测验和标签页: - `selected_id`:用于条件判断的元素 ID - `selected_title`:用于动态文本的元素标题 - **多选**:多选测验: - `selected_ids`:用于条件判断的元素 ID 列表 - `selected_titles`:用于动态文本的元素标题列表 - **切换**:试用切换: - `is_selected`:布尔值 常见使用场景包括: - 根据试用切换是否开启来展示不同内容。 - 根据用户的问卷答案[将用户导航至不同页面](onboarding-navigation-branching)。 ## 在文本中使用变量 \{#use-variables-in-text\} 在文本元素中插入变量: 1. 在画布上选择一个文本元素。 2. 在 **Design** 标签页中,找到 **Content** 字段并输入文本内容。 3. 点击字段中的 **{ }** 图标。 4. 从列表中选择一个变量。 :::tip 你也可以在其他元素中使用变量: - 在链接和提示框中使用变量,使其内容动态化 - 基于变量创建动态条件。例如,条件可以是 `if experience.current > experience.target, navigate to...` ::: ### 样式变量 \{#style-variables\} 变量本身不支持富文本格式。在 **Content** 字段中选中变量后,对其应用加粗、斜体、下划线、删除线或更改颜色均不会生效。 富文本设置只对整个文本块生效。如需设置文本样式,请使用 **Design** 标签页的 **Typography** 部分,或应用已保存的[文本样式](onboarding-text#set-up-text-styles)。 ### 在多个屏幕之间复用内容 \{#reuse-content-across-screens\} 流程中有些内容会在多个屏幕重复出现——比如"继续"这样的按钮文字、反复出现的行动号召,或者在多个屏幕都需要展示的免责声明。较长的文本也是如此,例如在多个屏幕复用的功能描述。与其在每个元素中逐一输入这些内容,不如将其保存为自定义变量。当你需要将不同用户引导至不同屏幕,但又希望各处措辞保持一致时,这种方式尤为实用。 1. [创建一个自定义变量](#create-a-custom-variable),类型选择 String,并将其初始值设置为你想复用的文本。例如,将其命名为 `button.navigation`,值设置为 `Continue`。 2. 将此变量插入每个需要显示该文本的元素的 **Content** 字段中。 只需更新变量的初始值,文本就会在所有用到该变量的地方同步更新,无需逐一手动编辑每个页面。 --- # File: onboarding-element-visibility --- --- title: "条件可见性" description: "根据条件显示或隐藏元素。" --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 你可以通过为元素添加条件来控制其显示与否。设置了条件的元素仅对满足指定条件的用户可见。 :::important 如果你使用 **Show** 或 **Hide** [操作](onboarding-actions)来显示或隐藏某个元素,该操作会覆盖该元素上设置的 **Visibility** 条件。 对于始终需要根据固定条件显示或隐藏的元素,请使用 **Visibility** 条件。如果可见性需要根据用户交互而变化——例如,在用户回答完问卷问题后显示某个按钮——请使用操作。 ::: 为元素添加条件: 1. 在画布或图层面板中选中该元素。 2. 在右侧面板的 **Visibility** 部分,选择 **Conditional**。 3. 通过从三个标签页中选择属性类型来设置条件: - **Custom**:由你创建和管理的变量,其值可通过用户交互更新。详情请参阅[变量](onboarding-variables)。 - **Products**:流程中产品的属性,例如价格或名称。 - **Elements**:流程中其他元素的状态,例如试用开关是否已激活。 4. 输入要匹配的 **Value**。 5. 点击运算符可根据需要进行更改。 6. (可选)点击 **Add condition** 添加更多条件。使用选择器来要求所有条件都匹配,或只需满足其中任意一个。 --- # File: paywall-dark-mode --- --- title: "深色模式" description: "在 Adapty 中为流程配置深色模式,提升用户体验。" --- Adapty 流程原生支持深色模式。默认情况下,颜色样式同时具有浅色和深色两种变体——将颜色样式应用于元素后,流程会根据设备当前的显示模式自动选用对应的值。Adapty 提供了一套预配置的颜色样式,你也可以创建自己的样式。 ## 配置颜色样式 \{#configure-color-styles\} 每个**颜色样式**都定义了浅色和深色两种备选颜色。当某个元素使用了命名样式后,会在两者之间自动切换。 你可以在左侧的 **Style** > **Colors** 中管理颜色样式。 添加颜色样式的步骤: 1. 在 **Style** > **Colors** 中,点击 **Create style**。 2. 选择浅色和深色备选颜色。 如需重命名样式,点击其旁边的 **⋮**,然后选择 **Rename**。 ## 设置状态栏主题 \{#set-the-status-bar-theme\} 如果在 **Screen settings** 面板中启用了 **Status bar**,你可以单独设置其主题:从 **Status bar theme** 选项中选择 **Light**、**Dark** 或 **Auto**。 ## 预览浅色与深色模式 \{#preview-light--dark-modes\} 要预览流程在各模式下的显示效果,请使用预览区底部的太阳/月亮图标切换按钮。 ## 移除深色模式 \{#remove-dark-mode\} 如需完全移除深色模式支持,请在 **Style** > **Colors** 面板中,点击 **⋮** > **Delete dark theme**。 --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "在流程编辑工具中添加语言区域" description: "在 Adapty 的流程编辑工具中添加本地化内容,让全球用户都能看到自己语言的界面。" --- 为流程添加本地化支持后,它就能以多种语言呈现给用户。在流程编辑工具中,本地化按屏幕组织,每个屏幕都会显示翻译完成百分比,方便追踪进度。 :::tip 在添加其他语言之前,请先用默认语言区域完成流程的所有设置。 ::: ## 添加并配置本地化 \{#add-and-set-up-localization\} 1. 在左侧面板中,点击 Localizations,然后点击 **Add locale**,选择要添加的语言。 2. 每个已添加的语言区域将作为一列显示在本地化表格中,并预先填入默认语言的内容。 3. 如果只想查看尚未翻译的内容,可以在左侧面板中开启 **Missing only** 开关,表格将只显示未翻译的行。 ## 导出与导入以供外部翻译 \{#export-and-import-for-external-translation\} 您可以将本地化文件导出,发送给翻译人员,并在翻译完成后导入结果。 在顶部工具栏中,点击 **Import / Export**。 ### 导出文件格式 \{#export-file-format\} 导出会生成一个 `.tsv`(制表符分隔)文件,每行对应一个可翻译元素。各列说明如下: | 列名 | 说明 | |--------|-------------| | `Screen` | 该元素所属的屏幕(例如 `Welcome`、`Quiz`) | | `Element` | 该屏幕内自动生成的元素标识符。可在 **Interactions** > **Element ID** 中修改。 | | `Property` | 属性类型(例如 `content`) | | `[default_locale]` | 默认语言代码(例如 `en`) | | `[locale]` | 每个已添加的语言对应一列(例如 `fr`、`es`) | 示例: :::note 对于未翻译的行,将对应语言列留空——Adapty 会将其视为缺失内容。 ::: ### 导入文件要求 \{#import-file-requirements\} - **格式**:`.tsv`(制表符分隔值) - **标题行**:必须包含 `Screen`、`Element`、`Property` 列,以及至少一个语言区域列 - **语言区域列名**:必须与流程中已添加的语言区域代码一致。若文件中包含流程里不存在的语言区域代码,将会报错。 - **部分导入**:可以只包含部分行;文件中未包含的行将保留其当前值 ## 手动翻译 \{#translate-manually\} 你也可以直接在本地化表格的任意单元格中输入翻译内容。 要管理某一行,请打开其右侧的上下文菜单(**⋮**): - **Reset to default**:将该行的翻译恢复为默认语言的值。 ## 预览本地化内容 \{#preview-the-localization\} 要检查翻译效果,请在流程编辑工具中切换当前语言区域,并逐屏查看。 --- # File: add-flow-remote-config-locale --- --- title: "使用远程配置本地化流程" description: "为流程的远程配置添加语言区域设置,以便按语言或地区提供不同的内容。" --- 流程的远程配置可以为每个语言区域保存独立的 JSON 数据。在运行时,SDK 会返回与用户语言区域匹配的数据,这样你就能在不发布新版本的情况下,提供翻译后的文案、不同的图片或其他与语言区域相关的内容。 ## 添加语言区域 \{#add-a-locale\} 要为流程的远程配置添加语言区域: 1. 在 Flow Builder 中打开流程。 2. 点击屏幕预览上方的远程配置图标。 3. 点击编辑器上方的 **Add locale**。 4. 填写对话框: - **Code**:语言区域代码,例如 `en`、`fr` 或 `de`。 - **Name**:显示名称,例如 English 或 French。 Adapty 会在 JSON 编辑器中为该语言区域新增一列。 ## 按语言版本编辑值 \{#edit-values-per-locale\} 每个语言版本的列都接受各自的 JSON 格式数据。各列使用相同的键,为每个语言版本翻译对应的值。 例如,英文列: ```json showLineNumbers { "title": "Try for free!", "cta": "Continue", "trial_days": 7 } ``` 西班牙语列: ```json showLineNumbers { "title": "¡Prueba gratis!", "cta": "Continuar", "trial_days": 7 } ``` 各列相互独立——编辑其中一列不会影响其他列。 ## 在应用中读取匹配语言的远程配置 \{#read-the-matching-locale-in-your-app\} SDK 会在 `AdaptyFlow.remoteConfigs` 上为每种语言暴露一个 `AdaptyRemoteConfig` 条目。选取 `locale` 与当前用户匹配的条目,然后读取其 `dictionary` 或 `jsonString`,即可在运行时使用这些值。 ## 备份或迁移语言设置 \{#back-up-or-move-locales\} 使用编辑器上方的 **Import/Export** 菜单来备份远程配置,或将其复制到其他流程中。导出的 JSON 文件包含所有语言的配置内容。文件格式详见[使用远程配置自定义流程](customize-flow-with-remote-config)。 --- # File: customize-flow-with-remote-config --- --- title: "使用远程配置自定义流程" description: "使用远程配置 JSON 数据自定义 Flow Builder 流程。" --- :::important 本指南介绍 Flow Builder 的远程配置。若需了解不使用 Flow Builder 创建的经典付费墙,请参阅[使用远程配置设计付费墙](customize-paywall-with-remote-config)。 ::: 远程配置允许你存储自定义 JSON 数据,SDK 在运行时读取这些数据。你可以用它来设置标题、图片、字体、颜色或功能开关等内容,无需发布新版本。 ## 使用远程配置 \{#work-with-remote-config\} 要打开某个流程的远程配置,请点击流程编辑器中屏幕预览上方的 Remote Config 图标。 在 **JSON** 视图中,你可以输入任意 JSON 格式的数据。编辑器会为你添加的每个语言区域显示一列: :::warning 如果远程配置包含无效的 JSON,你将无法**保存**或**发布**该流程。完整的阻止预览和发布的问题列表,请参阅[保存与发布流程](builder-save-publish#troubleshooting)。 ::: 之后,你可以通过 SDK 从 `AdaptyFlow` 的 `remoteConfigs` 数组中访问这些数据。Adapty 会为每个语言区域存储一个 `AdaptyRemoteConfig` 条目;选择与用户语言区域匹配的条目,读取解析后的 `dictionary` 或原始的 `jsonString`,即可在运行时动态调整流程。以下是远程配置的一些使用示例。 <Tabs> <TabItem value="Titles" label="标题" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or other texts ``` </TabItem> <TabItem value="Images" label="图片"> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your flow ``` </TabItem> <TabItem value="Fonts" label="字体"> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="颜色"> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML"> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed in the flow ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="软性/硬性付费墙"> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disallow skipping the paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="翻译"> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> 你可以自由组合上述任意模式,也可以自定义键名,用于测试不同的文案、布局或交互行为。 接下来,[创建一个版位](create-placement)并将流程添加到其中。然后在应用中渲染该流程:[iOS](present-remote-config-paywalls) 或 [Android](present-remote-config-paywalls-android)。 ## 添加语言区域 \{#add-a-locale\} 要为流程添加本地化,请在编辑器上方点击 **Add locale** 并选择语言区域。 Adapty 会在编辑器中为该语言区域新增一列。每列可独立编辑——运行时,SDK 会返回 `locale` 与用户选择相匹配的 `AdaptyRemoteConfig` 条目。 ## 导入和导出 JSON \{#import-and-export-json\} 使用编辑器上方的 **Import/Export** 菜单,可以一次性备份、分享或批量编辑所有语言区域的远程配置。 - **Export JSON**:下载一个包含所有语言区域的 JSON 文件。 - **Import JSON**:上传相同格式的 JSON 文件。上传后将替换当前的远程配置。 该文件以语言区域代码作为顶层键,对应的值为该语言区域的数据内容: ```json showLineNumbers { "en": { "title": "Get Premium", "cta": "Continue", "trial_days": 7, "features": ["sync", "export", "ai"] }, "fr": { "title": "Passez à Premium", "cta": "Continuer", "trial_days": 7, "features": ["synchronisation", "exportation", "IA"] } } ``` 每个语言区块遵循与直接输入语言列相同的 JSON 结构。 --- # File: paywall-device-compatibility-preview --- --- title: "预览流程" description: "预览流程在不同设备上的兼容性,以获得最佳体验。" --- 你可以通过两种方式在不同屏幕类型上预览流程: - **在设备上预览**:在开发的任意阶段查看流程在真实设备上的效果。 - **在 Adapty 看板中预览**:在设计流程时实时预览效果。 ## 在设备上预览 \{#preview-on-devices\} 要在真实设备上预览你的流程: 1. [从 App Store 下载 Adapty 应用](https://apps.apple.com/us/app/adapty/id6739359219)。 2. 在流程编辑器中,点击 **Test on device**。 3. 选择流程的语言区域。 4. 用设备摄像头扫描二维码,或直接打开链接。这将在 Adapty 移动应用中打开你的流程。 :::note 在测试模式下,Adapty 无法访问你在应用商店中的产品,因此流程中显示的价格并非真实价格。 ::: ### 故障排除 \{#troubleshooting\} 如果存在以下任何问题,您将无法发布或预览流程。 - 交互配置不完整。常见情况包括: - **打开 URL** 操作没有设置目标 URL。 - **跳转到页面** 操作没有设置目标页面——如果在设置操作后目标页面被删除,也会出现此问题。 - **条件操作** 没有设置运算符或值。 - **设置变量** 操作没有指定变量/值。 - **购买** 操作没有关联产品(应用内购买)或没有设置 Web 付费墙 URL(网页支付)。 - **自定义** 操作没有设置 Action ID。 - **显示弹窗** 操作的标题或消息为空。 - **显示**或**隐藏**操作没有选择任何元素。 - **页面中没有任何元素**。 - 产品元素**没有关联产品**——如果删除了所引用的产品,可能会出现此问题。 - **远程配置** JSON 无效会导致整个交付流程中断——您甚至无法保存草稿。 ## 在 Adapty 看板中预览 :::tip 为确保你的流程已准备好发布,请[在真实设备上预览](#preview-on-devices)并确认其渲染无误。 ::: 你可以在流程编辑工具的预览区域中,查看流程在不同屏幕类型上的显示效果。这有助于确保你的流程在各种设备和屏幕尺寸上都能完美呈现。 使用预览区域下方的预览控件,你可以: - 选择预览流程所用的设备。 - 在横向和纵向预览模式之间切换。 - 在浅色和深色模式之间切换。 - 在不同语言区域之间切换。 :::tip - 务必预览不同语言环境,因为不同语言的文字长度各异,界面布局可能因此有所不同。 - 预览自定义变量时,请为其设置初始值。例如,添加 `name` 变量后,可以将初始值设为 `Jane Doe` 进行预览。 ::: --- # File: builder-save-publish --- --- title: "保存与发布流程" description: "将流程保存为草稿并发布给用户" --- [流程编辑器](adapty-flow-builder)将保存与发布两个操作分开处理。草稿用于在 Adapty 看板中保留你的工作进度,而发布则会通过 SDK 将当前版本提供给用户。本文介绍这两项操作及其适用场景。 ## 将流程保存为草稿 \{#save-a-flow-as-a-draft\} :::warning 无效的[远程配置](customize-flow-with-remote-config)会导致草稿无法保存。 ::: 付费墙编辑工具每分钟自动保存一次进度。 如需手动保存草稿,请点击付费墙编辑工具右上角的 **Save draft**,或按 **Cmd/Ctrl + S**。 草稿仅在看板内可见,不会影响用户在应用中看到的内容,即使该流程已分配至某个[版位](placements)也是如此。 ## 发布流程 \{#publish-a-flow\} 发布操作会通过 SDK 将当前版本的流程提供给用户。发布后,新版本将替换同一流程之前已发布的任何版本。 :::note 如需将流程添加到[版位](placements),请先发布它。草稿状态的流程无法添加。 ::: 要发布流程,请点击付费墙编辑工具右上角的 **Publish to Live**。 接下来发生的情况取决于该流程是否已分配到某个版位: - **流程已关联版位**:用户下次请求该版位时即可看到新版本。 - **流程未关联版位**:将流程添加到[版位](create-placement),以开始向用户展示。 :::tip 当每个操作、页面和产品元素均配置完成后,流程即可发布。常见问题请参阅[问题排查](#troubleshooting)。 ::: :::warning [自定义字体](using-custom-fonts-in-flow-builder)不会随流程一起打包——你必须将每个字体文件添加到应用包中。如果缺少字体文件,用户将看到系统默认字体。 若要在已发布的流程中更改字体且不影响旧版本:请复制该流程,在副本中更改字体,然后将副本通过[目标受众](add-audience-paywall-ab-test)定向到包含该字体的应用版本。 ::: ## 流程状态 \{#flow-status\} 每个流程在流程列表中都会显示一个状态,反映该流程在保存和发布生命周期中所处的阶段。 | 状态 | 含义 | | :----- | :------ | | **Draft** | 该流程从未发布过。目前只有草稿,用户尚不可见。需要先发布草稿,才能将其添加到[版位](placements)。 | | **Dirty** | 该流程已发布,但存在已保存但尚未发布的编辑内容。在再次发布之前,用户看到的仍是上一个已发布版本。 | | **Publishing** | 正在发布中。 | | **Failed** | 上次发布尝试失败。如果存在已发布版本,用户仍将看到该版本。 | | **Published** | 最新保存的版本已上线,没有未发布的编辑内容。 | | **Archived** | 该流程已删除。 | ## 故障排查 \{#troubleshooting\} 如果存在以下任何问题,您将无法发布或预览流程。 - 交互配置不完整。常见情况包括: - **打开 URL** 操作没有设置目标 URL。 - **跳转到页面** 操作没有设置目标页面——如果在设置操作后目标页面被删除,也会出现此问题。 - **条件操作** 没有设置运算符或值。 - **设置变量** 操作没有指定变量/值。 - **购买** 操作没有关联产品(应用内购买)或没有设置 Web 付费墙 URL(网页支付)。 - **自定义** 操作没有设置 Action ID。 - **显示弹窗** 操作的标题或消息为空。 - **显示**或**隐藏**操作没有选择任何元素。 - **页面中没有任何元素**。 - 产品元素**没有关联产品**——如果删除了所引用的产品,可能会出现此问题。 - **远程配置** JSON 无效会导致整个交付流程中断——您甚至无法保存草稿。 在发布前,请通过 [Adapty 应用](paywall-device-compatibility-preview)预览你的流程,提前发现问题。如果流程在预览中加载失败,请查看错误信息了解详情。 --- # File: flow-metrics --- --- title: "流程数据指标" description: "跟踪和分析流程性能指标,提升订阅收入。" --- Adapty 会收集一系列数据图表,帮助你衡量流程的表现。与付费墙数据图表不同,流程数据图表包含完成率追踪,让你能清楚看到用户在各个屏幕上的流失情况。除浏览量每隔几分钟更新一次外,所有数据图表均实时更新。本文将介绍可用的数据图表、其定义及计算方式。 :::important 流程收入的计算范围包含流程展示后发生的所有交易。 ::: 流量数据图表显示在流列表中,帮助你全面了解所有流的表现。这个汇总视图展示了每个流的聚合数据图表,方便你对比各流的效果,找出需要改进的地方。 如需对某个流进行更深入的分析,请进入流详情数据图表页面。在那里,你可以找到针对所选流的完整数据图表,深入了解其表现情况。 ## 数据图表控件 \{#metrics-controls\} 系统会根据所选时间段显示数据图表,并按左侧列参数以三级缩进进行组织。 对于已发布的流程,数据图表涵盖从流程发布日期到当前日期的时间段。草稿和已归档的流程也会显示在数据图表表格中,但如果没有可用数据,则不会展示任何数据图表。 ### 数据图表查看选项 \{#view-options-for-metrics-data\} 流程页面提供了两种数据图表查看方式: - 基于版位的视图:数据图表按与流程关联的[版位](placements)分组显示。使用此视图可对比同一流程在不同版位的表现。 - 基于目标受众的视图:数据图表按流程的[目标受众](audience)分组显示。使用此视图可评估不同目标受众细分的专项数据图表。 流程页面顶部的下拉菜单可用于切换所需的查看方式。 ### 按安装日期筛选数据图表 \{#filter-metrics-by-install-date\} 勾选 **Filter metrics by install date** 复选框后,您可以按用户安装应用的时间来分析数据,而不是按交易或页面访问发生的时间。这对于衡量特定同期群的用户获取效果非常有用。 ### 时间范围 \{#time-ranges\} 您可以使用时间范围来分析数据图表,专注于特定时段,例如按天、周、月或自定义日期范围进行查看。 ### 筛选与分组 \{#filters-and-groups\} Adapty 提供了多种工具,帮助你根据需要对数据图表进行筛选和自定义分析。数据图表页面支持多种时间范围、分组方式和筛选条件。 - 筛选条件:归因(来源、广告组、广告集、创意素材、广告系列)、国家、商店。 - 分组方式:流程(默认)、国家或商店。下拉菜单中仅显示有对应数据的分组选项——例如,如果所有流程浏览记录均来自同一个国家,则不会提供按国家分组的选项。 您可以在[此文档](controls-filters-grouping-compare-proceeds)中找到有关可用控件、过滤器、分组选项及其使用方法的更多信息。 ### 单项数据图表 \{#single-metric-chart\} 数据图表区域以简单的柱状图展示数据,帮助你快速了解: - 每项数据图表的具体数值。 - 特定时间段的数据。 图表旁边会显示汇总合计,让你一眼掌握全貌。 点击箭头图标可展开图表。 ### 总数据图表摘要 \{#total-metrics-summary\} 在单一数据图表旁边,有一个总数据图表摘要部分。该部分显示所选数据图表在特定时间点的累计值。你可以通过下拉菜单切换显示的数据图表。 ## 数据图表定义 \{#metrics-definitions\} ### 浏览量与独立浏览量 \{#views--unique-views\} **浏览量**统计用户启动流程的次数(即到达第一屏的次数)。如果同一用户启动了两次,则计为 2 次浏览量,但只算 1 次独立浏览量。该数据图表可帮助你了解流程被展示的频率。 ### 完成次数与唯一完成次数 \{#completions--unique-completions\} **完成次数**统计用户到达流程最后一屏的总次数。如果同一用户完成了两次,则计为两次完成,但只计为一次唯一完成。 ### 唯一完成率 \{#unique-completions-rate\} 唯一完成次数除以唯一查看次数所得的比率。使用此数据图表可了解用户在流程中的推进情况,并找出用户流失的节点。 :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: ### 收入 \{#revenue\} **Revenue** 显示归因于该流程的购买和续订所产生的 USD 总收入。这是扣除 App Store / Play Store 佣金等任何费用之前的金额。 ### 收益 \{#proceeds\} [**收益**](analytics-cohorts#revenue-vs-proceeds) 是指扣除 App Store / Play Store 佣金后、税前你实际到手的金额。 :::important 如果你的应用已加入佣金减免计划,请务必通知 Adapty。为确保计算准确,请在[应用设置](general)中填写你的 [Small Business Program](app-store-small-business-program) 和[费率减免计划](google-reduced-service-fee)状态。 ::: ### 净收益 \{#net-proceeds\} 扣除应用商店佣金和税费后的最终收益。 ### ARPPU ARPPU 是每付费用户平均收益,计算方式为总收益除以唯一付费用户数。例如:$15,000 收益 / 1,000 名付费用户 = $15 ARPPU。 ### ARPU ARPU 是每位查看流程的用户所带来的平均收入,计算方式为总收入除以独立访客数量。 ### ARPAS ARPAS 是每位活跃订阅者的平均收入,计算方式为总收入除以已激活试用或订阅的用户数量。例如:5,000 美元收入 / 1,000 位订阅者 = 5 美元 ARPAS。 ### 购买转化率与唯一购买转化率 \{#cr-purchases--unique-cr-purchases\} **购买转化率**表示在所有流程浏览次数中,最终完成购买的比例。例如,100 次浏览中有 10 次购买,转化率即为 10%。 **唯一购买转化率**衡量的是浏览过该流程的独立用户中,最终完成购买的比例——无论每位用户浏览了多少次,均只计算一次。 ### 试用转化率与独立用户试用转化率 \{#cr-trials--unique-cr-trials\} **试用转化率**表示在所有流程浏览次数中,有多少比例最终开启了试用。例如,100 次浏览中有 10 次开启试用,转化率即为 10%。 **独立用户试用转化率**衡量的是查看过流程的独立用户中,有多少比例开启了试用——每位用户无论浏览多少次,均只计一次。 ### 购买量 \{#purchases\} **购买量**统计流程中的所有交易,续订除外,具体包括: - 直接新购。 - 在该流程中激活的试用期转化。 - 方案变更(升级、降级、跨级)。 - 流程中的订阅恢复,例如订阅在无自动续订的情况下到期后被重新激活。 此数据图表能全面呈现流程中新交易的活跃情况。 ### 试用 \{#trials\} **试用**统计通过你的流程开始免费试用期的用户数量。使用此数据图表可以追踪试用优惠在用户付费决策前的吸引力表现。 ### 已取消的试用 \{#trials-cancelled\} **Trials cancelled** 显示了在试用期内关闭自动续订的用户数量。这一数据反映了有多少用户在体验你的服务后,决定不继续转为付费订阅。 ### 退款 \{#refunds\} **退款**统计退款处理的购买和订阅数量,不区分退款原因。 ### 退款率 \{#refund-rate\} **退款率**表示首次购买中被退款的百分比。例如:1,000 次首次购买中有 5 次退款,退款率即为 0.5%。续订不计入此计算。 --- # File: fallback-flows --- --- title: "备用流程" description: "在 Adapty 中设置本地备用流程,确保设备离线时流程仍可正常展示。" --- 为了保持流畅的用户体验,为你的[流程](adapty-flow-builder)设置**备用版本**非常重要。 当您的应用请求流程时,Adapty SDK 会联系我们的服务器获取其配置。如果设备无法访问 Adapty(网络问题、服务器故障),SDK 将回退到本地数据: - 如果用户之前已经看过该流程,SDK 会使用缓存的副本。 - 如果没有缓存,SDK 会加载打包在应用内的备用配置文件。 Adapty 会自动生成这些备用文件。流程的备用包与付费墙共享——每个平台使用一个 JSON 文件,其中包含两者的备用变体。SDK 会读取它所需的对应部分。 :::important 流程备用方案包含在 **Adapty SDK 4.0+** 版本中。如果在下载对话框中选择了更早的 SDK 版本,文件将只包含付费墙和用户引导的变体,不包含流程内容。在依赖流程备用方案之前,请确保你的应用使用的是支持流程的 SDK 版本。 ::: ## 开始之前 \{#before-you-start\} 1. 在流程编辑器中创建一个[流程](adapty-flow-builder)。 2. 为该流程[创建一个版位](create-placement)。 ## 下载备用付费墙文件 \{#download-the-fallback-file\} 1. 打开 **[Placements](https://app.adapty.io/placements)** 页面。 2. 点击右上角的 **Fallbacks** 按钮。 3. 从下拉菜单中选择目标平台。 4. 选择与应用中已集成版本匹配的 SDK 版本。选择 **Adapty SDK v4.0.0 and higher**(或更高版本选项),以获取包含 flows 的资源包。 浏览器将按平台下载一个 JSON 文件,例如 `ios_4_0_0_fallback.json`。 <details> <summary>示例 flow 备用条目(点击展开)</summary> ```json "PLACEMENT_ID": { "data": [ { "developer_id": "PLACEMENT_ID", "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "flow_id": "daf25858-3fa2-4981-8500-9c8a30e5b7e6", "flow_name": "FLOW_NAME", "flow_version_id": "FLOW_VERSION_ID", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "audience_name": "All Users", "ab_test_name": "", "cross_placement_info": null, "weight": 100, "variations": [ { "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "paywall_id": "PAYWALL_ID", "paywall_name": "PAYWALL_NAME", "ab_test_name": "", "products": [], "revision": 1, "custom_payload": null, "weight": 100 } ], "remote_configs": [] } ], "meta": { "placement": { "developer_id": "PLACEMENT_ID", "is_tracking_purchases": true, "audience_name": "All Users", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "revision": 0, "ab_test_name": "" } } } ``` 确切的结构可能因 SDK 版本而异。请始终使用 Adapty 为您的 SDK 版本生成的文件,而不是手动编写。 </details> ## 下载后 \{#after-the-download\} 将文件添加到您的应用代码中,然后按照对应平台的设置指南进行操作。加载付费墙备用文件的 API,在您的应用升级到支持 flow 的 SDK 版本后,同样适用于加载 flow 备用文件: - [iOS](ios-use-fallback-paywalls) - [Android](android-use-fallback-paywalls) - [React Native](react-native-use-fallback-paywalls) - [Capacitor](capacitor-use-fallback-paywalls) ## 限制 \{#limitations\} 备用流程是硬编码并本地存储的,因此不具备实时流程的完整动态能力: - **每个版位仅一个变体。** 如果某个版位有多个流程(不同目标受众或 A/B 测试变体),备用文件将使用权重最高或受众范围最广的那个变体。 - **不支持 A/B 测试。** 线上流程的 A/B 测试由服务器解析;备用方案始终只提供单个选定的变体。 - **不支持远程更新。** 更新备用文件需要发布新版本应用。如需运行时更新,请改用线上流程通过远程配置推送。 - **仅支持默认语言。** 备用方案使用 `en` 语言环境,不包含本地化变体。 --- # File: create-product --- --- title: "创建产品" description: "在 Adapty 中创建新订阅产品的分步指南,助您更好地管理收益。" --- 在 Adapty 中创建产品的方式取决于产品是否已经存在于应用商店中: - **[如果产品尚未在 App Store 和/或 Google Play 中创建,请在 Adapty 中创建并立即推送到应用商店](#create-product-and-push-to-store)**。 - **[如果产品已经存在于 App Store 和/或 Google Play 中,请在 Adapty 中创建并关联现有的应用商店产品。](#create-product-and-connect-existing-store-products)** :::tip 你也可以使用 [开发者 CLI](developer-cli-reference#adapty-products-create) 以编程方式创建产品。 ::: ## 创建产品并推送至应用商店 \{#create-product-and-push-to-store\} :::warning 开始之前,请确认你已完成所需应用商店的集成配置: - [App Store](initial_ios) - [Google Play](initial-android) 如果你是很早之前配置的 App Store 集成,请确认你已[添加 App Store Connect API 密钥](app-store-connection-configuration#step-6-add-app-store-connect-api-key)。 ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 要向应用添加新产品: 1. 在 Adapty 主菜单中进入 **[Products](https://app.adapty.io/products)**。 <img src="/assets/shared/img/products-tab.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击右上角的 **Create product**。Adapty 支持所有类型的产品:订阅、非消耗型商品(包括永久授权)以及消耗型商品。 3. 选择 **Create a new product and push to stores**。 <img src="/assets/shared/img/push-to-stores.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 填写以下信息: - **Product name**:输入产品名称,该名称将显示在 Adapty 看板中。名称主要供你自己参考,请选择最便于在 Adapty 看板中使用的名称。 - **Access Level**:选择该产品所属的[访问等级](access-level)。访问等级用于确定购买产品后解锁的功能。请注意,此列表仅包含已创建的访问等级。Adapty 默认创建了 `premium` 访问等级,你也可以[添加更多访问等级](access-level)。 - **Subscription duration**:从列表中选择订阅时长。 - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**:订阅时长。 - **Lifetime**:对于永久解锁应用高级功能的产品,请使用 Lifetime(永久)周期。 - **Non-Subscriptions**:对于非订阅类产品(即没有时长的产品),请使用 Non-Subscriptions。这类产品可用于解锁额外功能、消耗型商品等。 - **Consumables**:消耗型商品可多次购买,在应用使用过程中会被消耗,例如游戏货币和道具。请注意,消耗型商品不影响访问等级。如需通过一次性购买授予访问等级,请改用 **Non-Subscriptions**。 - **Price (USD)**:产品的美元定价。该价格将作为基准价,自动计算并设置各国/地区的价格。你可以在之后[为不同国家和地区自定义价格](edit-product#set-country-specific-prices)。 <img src="/assets/shared/img/create-product-push.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Save & Continue**。 6. 如果你计划在 App Store 上架,请填写对应的产品信息: - **Product ID**:为该产品创建一个永久唯一的 ID。 - **Product group**:选择你在 App Store Connect 中已创建的产品组,或点击 **Create new Product Group** 并设置名称。Adapty 创建完成后,你可以从下拉菜单中选择它。 - **Screenshot**:上传一张应用内购买的截图,清晰展示所提供的商品或服务。该截图仅用于 App Store 审核,不会在 App Store 上公开显示。截图尺寸和格式要求请参阅[此处](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/)。 <img src="/assets/shared/img/push-app-store.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击 **Push data to App Store**。 :::warning 如果这是您该应用的第一个产品,您必须在 App Store Connect 中手动提交审核。之后无需再次操作。审核完成后,Adapty 中的产品状态将自动更新。 ::: 8. 如果计划在 Google Play 发布,请配置 Google Play 的产品信息: - **Base Product ID**:为该产品创建一个永久唯一的 ID。 - **Subscription**:从下拉列表中选择您已在 Google Play Console 中创建的订阅组,或点击 **Create new Product Group** 并设置其名称和 ID。Adapty 创建完成后,您即可从下拉列表中选择它。 :::note Grace Period 和 Account Hold Period 将按照 Play Store 规则自动设置为默认值。您可以稍后在 Google Play Console 中进行修改。 ::: <img src="/assets/shared/img/push-google-play.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. 点击 **Push data to Play Store**。 10. 对于 iOS,通过从下拉菜单中选择 **Free duration** 来配置新用户优惠(免费试用)。在初始设置阶段,您可以添加一个免费试用的新用户优惠。主产品经商店审核通过后,您可以通过关联商店控制台中已有的 ID 来[添加更多优惠](offers)(例如促销活动、赢回优惠)。 <img src="/assets/shared/img/intro.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 新用户优惠不会自动与 Google Play 同步。与 App Store 不同,Google Play 没有单独的"新用户优惠"类型——免费试用和折扣优惠都以**优惠**的形式配置在基础方案上。[在 Google Play Console 中创建优惠并将其关联到你的 Adapty 产品](google-play-offers)。 ::: 11. 最后,点击 **Save** 确认创建产品。 ## 创建产品并关联已有应用商店产品 \{#create-product-and-connect-existing-store-products\} :::warning 开始之前,请确保你已完成以下操作: - 配置了所需应用商店的集成: - [App Store](initial_ios) - [Google Play](initial-android) - 在所需应用商店中创建了产品: - [App Store](app-store-products) - [Google Play](android-products) **如果你尚未创建任何产品**,建议参考[推送至应用商店](#create-product-and-push-to-store)指南,同时在 Adapty 和应用商店中创建产品。 ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> 要在应用中添加新产品: 1. 从 Adapty 主菜单进入 **[Products](https://app.adapty.io/products)**。 <img src="/assets/shared/img/products-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击右上角的 **Create product**。Adapty 支持所有类型的产品:订阅、非消耗型商品(包括永久授权)和消耗型商品。 3. 选择 **Connect an existing store product**。 <img src="/assets/shared/img/existing-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 填写以下信息: - **Product name**:输入产品名称,该名称将在 Adapty 看板中显示。此名称主要供你自己参考,可以选择任何方便在 Adapty 看板中使用的名称。 - **Access Level ID**:选择该产品所属的[访问等级](access-level)。访问等级用于确定购买产品后可解锁的功能。请注意,此列表仅显示已创建的访问等级。`premium` 访问等级在 Adapty 中默认创建,您也可以[添加更多访问等级](access-level)。 - **订阅时长**:从列表中选择订阅的时长。 - **每周/每月/2个月/3个月/6个月/每年**:订阅的具体时长。 - **永久授权**:适用于永久解锁应用高级功能的产品。 - **非订阅**:对于非订阅类产品(即没有时长的产品),请使用非订阅类型。可用于解锁附加功能、消耗型商品等。 - **消耗型商品**:消耗型商品可多次购买,在应用使用过程中会被消耗掉,常见示例包括游戏内货币和道具。请注意,消耗型商品不会影响访问等级。如需通过一次性购买授予访问等级,请使用**非订阅**类型。 - **价格(USD)**:产品的美元定价。如果您的产品已在商店上架,此处的值不会影响其实际售价,您可以从列表中选择任意值。之后,您可以直接在 Adapty 看板中[为不同地区自定义价格](edit-product#set-country-specific-prices)。 <img src="/assets/shared/img/product-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Continue**。 6. 配置每个应用商店的产品信息: - **App Store:** - **App Store Product ID:** 该唯一标识符用于在设备上访问您的产品。请从列表中选择。如果列表中未显示,请在 App Store Connect 中检查其配置,确保配置正确且归属于此应用。 - **Play Store:** - **Google Play Product ID:** 这是 Play Store 中的产品标识符。请从列表中选择。如果列表中未显示,请在 Google Play Console 中检查其配置,确保配置正确且归属于此应用。 - **Base Plan ID:** 该 ID 用于定义产品在 Play Store 中的基础方案。在 Play Store 上添加订阅的 Product ID 时,必须提供 Base Plan ID。基础方案定义了订阅的核心信息,包括账单周期、续订类型(自动续订或预付费)以及对应价格。请注意,在 Adapty 中,同一订阅与不同基础方案的每种组合均被视为独立产品。 - **Legacy fallback product**:备用产品仅适用于使用旧版 Adapty SDK(2.5 及以下版本)的应用。通过在 Google Play Console 中将产品标记为向后兼容,Adapty 可以识别旧版 SDK 是否可以购买该产品。此字段请按以下格式填写:`<subscription_id>:<base_plan_id>`。 - **Stripe**: - **Stripe Product ID**:这是 Stripe 中产品的唯一标识符。 - **Stripe Price ID**:在 Stripe 中,价格对象不仅包含金额,还涵盖税务行为、阶梯定价和订阅周期。由于一个产品可以对应多个价格,请在 Adapty 中创建产品时指定正确的价格 ID。 - **Paddle**: - **Paddle Product ID**:这是 Paddle 中产品的唯一标识符。 - **Paddle Price ID**:在 Paddle 中,价格对象不仅包含金额,还涵盖税务行为、阶梯定价和订阅周期。由于一个产品可以对应多个价格,请在 Adapty 中创建产品时指定正确的价格 ID。 7. **可选:** 点击 **Add custom store** 可添加任意自定义商店中的产品。在 **Manage custom store info** 窗口中,你可以选择已有的自定义商店,或新建一个并与产品关联。请注意,Adapty 仅追踪来自 App Store、Google Play 和 Stripe 的交易。对于自定义商店,你需要通过 Adapty 服务端 API 的 Set transaction 方法手动提交交易记录。 8. 点击 **Save product** 完成产品创建。产品状态同步最多需要五分钟,请等待表格中的状态更新。 9. 如需为产品创建优惠,可[创建优惠](create-offer)。点击 **Yes, add offers** 添加优惠,或点击 **No, thanks** 跳过。 :::note 新用户优惠仅在将产品推送到应用商店时才会在 Adapty 中创建。通过导入方式添加的产品或此前已创建的产品,其新用户优惠不会同步至 Adapty 也不会显示,但在应用中仍可正常使用。 ::: ## 后续步骤 \{#next-steps\} 恭喜!您已将产品添加到 Adapty。接下来做什么? - 如果您还没有配置新用户优惠/促销活动,可以[立即配置](offers)。 - 如果您不想配置或已经完成配置,请继续[设置付费墙](quickstart-paywalls)以启用应用内购买。 - 如果您想对商店产品进行任何调整(例如设置区域定价或配置宽限期),请在 App Store Connect 或 Google Play Console 中操作。 - 了解如何在之后[编辑产品](edit-product)。 --- # File: edit-product --- --- title: "编辑产品" description: "在 Adapty 中修改和管理您的订阅产品,以更好地追踪收入。" --- 在 Adapty 中,您可以编辑产品的名称、访问等级、地区定价以及关联的应用商店 ID,并查看审计日志以追踪定价变更。订阅时长在创建产品后不可编辑,如需更改,您需要创建新产品。 :::warning 虽然您可以编辑任何产品,但务必确保对已在正式付费墙中使用的产品进行更改时,不会导致分析数据出现偏差。 **不建议编辑访问等级、App Store 产品 ID 和 Play Store 产品 ID**,因为这可能会影响分析数据的清晰度。只有在出现错误(例如产品 ID 拼写有误)时才建议进行编辑。 如果您不再使用某个产品并希望用另一个产品替换它,我们强烈建议您创建一个新产品,并相应更新付费墙和 A/B 测试。 ::: ## 编辑产品 \{#edit-product\} 要编辑产品: 1. 从 Adapty 主菜单进入 **[Products](https://app.adapty.io/products)**。 2. 点击表格中的产品行,或点击产品旁边的三点菜单并选择 **Edit**。 3. 在打开的 **Edit** 窗口中进行所需更改。有关此窗口中各选项的详细说明,请参阅[创建产品](create-product)章节。 4. 点击 **Save**。 :::warning 在 App Store Connect 或 Google Play Console 中所做的更改不会同步回 Adapty。Adapty 中显示的价格是在创建产品时设置的,即使你在商店中修改了价格,这里也不会自动更新。 这不影响你的收入分析——Adapty 直接从各商店拉取收入数据。看板中的价格字段仅供参考。 ::: :::note 如果你修改了访问等级,该更改仅对新订阅生效。现有订阅者的访问等级保持不变,并将在下次订阅续期时自动更新。 ::: <img src={require('./img/edit-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 设置国家/地区特定价格 \{#set-country-specific-prices\} 您可以直接在 Adapty 看板中为不同地区设置不同的价格,这些国家/地区特定价格将自动应用到 App Store Connect 和/或 Google Play Console 中的产品。 要设置国家/地区特定价格: 1. [打开产品进行编辑](#edit-product)。 2. 点击 **Download**,以正确格式导出当前应用商店价格,或创建一个新的 CSV 文件。 <img src={require('./img/download-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: '块级元素', /* for alignment */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 CSV 文件中更新价格。请遵循[格式要求](#csv-file-format)。如果某个国家/地区的价格保持不变或未包含在文件中,则不会有任何变化。上传 CSV 时,Adapty 会比较价格并仅更新有差异的价格。 4. 在 **Edit** 窗口中,点击 **Upload** 并选择 CSV 文件。 <img src={require('./img/upload-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 如果您希望更改也对现有订阅者生效,请选择 **Apply to existing subscribers**。 6. 检查将要应用的更改,然后点击 **Save changes**。 <img src={require('./img/country-level-price.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### CSV 文件格式 \{#csv-file-format\} :::tip 如果您在同一应用中有类似产品,或希望在不同应用中设置相同的价格,可以复用同一个 CSV 文件。 ::: 编辑 CSV 中价格的最简便方法是[下载包含当前价格的文件并直接编辑](#set-country-specific-prices)。 但如果您自行创建文件,文件中必须包含以下列: - `region_name` - `region_code` - `app_store_currency` - `app_store_requested_price` - `play_store_currency` - `play_store_requested_price` 示例: ``` region_name,region_code,app_store_currency,app_store_requested_price,play_store_currency,play_store_requested_price United States,US,,8.99,,8.99 United Arab Emirates,AE,USD,8.99,AED,39.99 Germany,DE,USD,8.99,USD,8.99 ``` ## 查看审计日志 \{#view-audit-log\} Adapty 会记录每个产品的所有定价变更,以便您追踪更改人员及时间。要查看审计日志: 1. 从 Adapty 主菜单进入 **[Products](https://app.adapty.io/products)**。 2. 点击产品旁边的三点菜单,然后选择 **Audit log**。 审计日志表格显示每次定价变更的日期、团队成员姓名与角色,以及更改次数。 要下载某次事件的详细 CSV 说明,请点击该行的下载图标。 <img src={require('./img/audit-log.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: delete-product --- --- title: "删除产品" description: "了解如何在 Adapty 中删除订阅产品,同时不影响应用的收入流。" --- 您只能删除未在付费墙中使用的产品。 要删除产品,请执行以下步骤: 1. 从 Adapty 主菜单进入 **[Products](https://app.adapty.io/products)**。 2. 点击产品旁边的 **3-dot** 按钮,然后选择 **Delete**。 <img src="/assets/shared/img/delete-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 输入您要删除的产品名称。 <img src="/assets/shared/img/b945add-delete_product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Delete forever**。 --- # File: add-product-to-paywall --- --- title: "向付费墙添加产品" description: "了解如何在 Adapty 中向付费墙添加和管理产品。" --- 要使产品在应用程序用户的[付费墙](paywalls)中可见且可选择,请按照以下步骤操作: 1. 在[配置付费墙](create-paywall)时,点击 **Products** 标题下方的 **Add product**。 2. 从打开的下拉列表中,选择将向客户展示的产品。该列表仅包含之前已创建的产品。产品的排列顺序会在 SDK 端保留,因此在配置付费墙时,请务必考虑所需的排列顺序。此外,您还可以根据需要为产品指定优惠。 <img src="/assets/shared/img/0479b51-ad_product_to_paywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 根据付费墙的状态,点击 **Create as draft** 或 **Save and publish**。 请注意,付费墙创建后,不建议对付费墙中的产品进行编辑、添加或删除操作,因为这可能会影响付费墙的数据图表。 --- # File: virtual-currencies --- --- title: "虚拟货币" description: "在 Adapty 中定义应用内货币,将其与产品关联以自动发放积分,并跟踪每位用户的余额。" --- <CustomDocCardList ids={['virtual-currency-quickstart', 'create-virtual-currency', 'virtual-currency-balance']} /> 当用户购买产品或续订订阅时,向其授予虚拟货币——AI 代币、积分或金币。只需定义一种货币,将其关联到需要发放奖励的产品,Adapty 即可自动为每位用户记账并维护其余额。您的应用通过[服务端 API](getting-started-with-server-side-api) 读取和消耗该余额——例如,按次扣减生成所需的代币。 ## 工作原理 \{#how-it-works\} 1. [创建虚拟货币](create-virtual-currency):打开 **Products** 并点击 **Virtual currency** 标签页。点击 **New virtual currency**。为货币定义代码、名称以及可选的描述信息。 2. **将货币关联到产品**:将货币映射到一次性购买或订阅产品,并设置每次购买可获得的积分数量。 3. **自动发放积分**:当用户购买关联的一次性产品或续订关联的订阅时,Adapty 会自动将配置的积分添加到其余额中。 4. **查看和消费余额**:您的应用通过服务端 API 读取每位用户的余额并进行积分的发放或消费操作。 5. **追踪每一次变动**:每次余额变动都会记录在[用户的画像](virtual-currency-balance)中。 完整的操作流程(从货币设置到通过 API 使用积分),请参阅[虚拟货币快速入门](virtual-currency-quickstart)。 ## 使用场景 \{#use-cases\} | 应用类型 | 如何使用虚拟货币 | |----------|-------------------------------| | **AI 应用**(图像、视频或文本生成) | 每次生成消耗积分。订阅中包含每月积分额度,并将积分包作为一次性商品出售。 | | **短剧和视频应用** | 消耗金币解锁剧集。为订阅用户提供周期性金币额度,并将金币包作为一次性商品出售。 | | **语言学习和教育** | 通过服务端 API 为免费用户提供有限的爱心或生命值额度。付费方案中提供更大额度,或完全跳过余额校验。 | | **手游** | 同时运行软货币和硬货币:通过服务端 API 因游戏玩法奖励金币,出售宝石换取现金,并在单个原子事务中实现相互兑换。 | | **生产力应用** | 用积分计量高成本操作(如 OCR 或导出)。免费用户获得少量额度,订阅后获得更多额度,批量工作时可购买积分包。 | :::tip 对于订阅配额,请开启[积分到期](create-virtual-currency#link-products)功能。每次续订时未使用的积分自动清零,这样配额就始终是用户保持订阅的动力,重度用户也会选择购买积分包,而不是慢慢消耗囤积的积分。 ::: ## 限制 \{#limitations\} - **跨设备访问需要身份识别**:余额归属于某个用户画像。匿名用户的余额仅保留在其获得余额的设备上,因此请识别用户身份,以便在所有设备上共享余额。详见[余额、用户画像与设备](virtual-currency-balance#balances-profiles-and-devices)。 - **仅支持服务端操作**:目前尚无 SDK 方法可读取或消耗余额,因此应用需要一个后端服务,通过服务端 API 读取余额并进行充值或消耗操作。 - **每个应用最多支持 20 种货币**:一个应用最多可创建 20 种虚拟货币。 --- # File: virtual-currency-quickstart --- --- title: "虚拟货币快速入门" description: "端到端配置虚拟货币:创建代币货币、通过订阅授予代币,并通过服务端 API 进行消耗。" --- :::link 主要文章:[虚拟货币](virtual-currencies) ::: 本快速入门将端到端配置代币货币:订阅每月为用户授予 1000 个代币,应用内的付费操作消耗代币。Adapty 负责维护所有余额,你的后端只需读取和消耗代币即可。 1. 按照[创建虚拟货币](create-virtual-currency)指南创建一个 `TOKENS` 货币。将其与您的 Pro 订阅关联,并将 **Credit per cycle** 设置为 1000。如需出售额外的代币,也可以关联一次性代币包产品。 2. 在开始生成之前,调用[列出虚拟货币余额](api-adapty/operations/listVirtualCurrencyBalances)接口读取用户余额,示例如下: ```bash title="Read balances" curl https://api.adapty.io/api/v2/server-side-api/vc/balances/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" ``` 响应中列出了该用户拥有的所有货币,例如 1000 个 `TOKENS`: ```json title="Response" { "data": [ { "code": "TOKENS", "name": "Tokens", "balance": 1000, "held": 0, "available": 1000 } ] } ``` 3. 当用户执行生成操作时,通过调用 [创建虚拟货币交易](api-adapty/operations/createVirtualCurrencyTransaction) 并传入负数 `amount` 来扣除代币。在本示例中,生成一张图片需消耗 100 个代币: ```bash title="Spend tokens" curl -X POST https://api.adapty.io/api/v2/server-side-api/vc/transactions/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" \ -H "Content-Type: application/json" \ -d '{"items": [{"currency_code": "TOKENS", "amount": -100}]}' ``` 该事务是原子性的,并返回更新后的余额。如果用户余额不足以覆盖费用,请求将返回 `insufficient_balance`,且不会发生任何变更。请在请求中添加 `Idempotency-Key` 标头以安全地重试请求。 4. 要运行赢回优惠活动,可通过同一端点传入正数 `amount` 来为用户充值代币。以这种方式授予的额度永不过期。 5. 在用户的[用户画像](virtual-currency-balance)或[交易记录](api-adapty/operations/listVirtualCurrencyTransactions)中查看每一次变更,以便随时审计经济体系。 --- # File: create-virtual-currency --- --- title: "创建虚拟货币" description: "在 Adapty 中创建虚拟货币,将其关联到产品,使购买行为可授予积分,并设置积分是否过期。" --- :::link 主要文章:[虚拟货币](virtual-currencies) ::: 要出售虚拟货币积分,需先定义一种虚拟货币,再将其关联到你的产品。 整个流程分为两步——本文均有详细说明。关联产品后,每次购买或续订都会自动授予积分。 ## 创建虚拟货币 \{#create-a-virtual-currency\} 在 Adapty 看板中,打开 **Products** > [**Virtual currency**](https://app.adapty.io/virtual-currency)。 创建虚拟货币的步骤如下: 1. 点击 **New virtual currency**,侧边栏将在 **General** 步骤处打开。 2. 填写货币详情: - **Code**:货币在应用内的唯一**永久**标识符,例如 `COINS`。用于在服务端 API 中标识该货币,并追踪每位用户的余额。 使用拉丁字母、数字和下划线,最多 32 个字符。小写字母会自动转换为大写。 - **Name**:显示名称,例如 `Gold`。可以在之后修改。 - **Description**:关于该货币的可选备注。 3. 点击 **Continue** 进入 **Link Products** 步骤。 :::important 货币的 **Code** 一旦创建就无法更改。但 **Name** 可以在之后编辑。 ::: 当你引入一种新货币时,每个用户画像的初始余额为 0。 ## 关联产品 \{#link-products\} 将产品与虚拟货币关联,这样购买或续订即可获得该货币的积分。您可以将一个产品关联到多种货币,也可以将一种货币关联到多个产品。您可以在 **Link Products** 步骤中立即关联产品,也可以在后续编辑货币时再添加。 在 **Link Products** 步骤中,**Associated products** 部分列出了可授予此货币的产品。如需添加,请点击 **Add associated products**,然后选择一个产品。 每次授予都会累加到用户的余额中,而不是替换原有余额,因此来自不同产品的点数会不断累积。点数设置取决于产品类型: - **订阅产品**有三个积分设置: - **Credit per cycle**(必填):每次续订时授予的积分,包括将试用期转为付费的那次续订。 - **Credit on trial start**(可选):试用期开始时单独授予的一次性积分。 - **Credits expire at the end of each billing cycle**(开关):开启后,Adapty 会在每次续订时先将未使用的积分重置为 0,再授予新周期的积分;关闭后,积分跨周期累积,永不过期。 - **一次性购买产品**:设置 **Credit amount**,即每次购买授予的积分数量。一次性积分永不过期。 点击 **Save** 创建货币及其产品链接。 产品链接仅对未来的交易生效。新链接的产品将从下一次购买或续订时开始授予积分。Adapty 不会自动向当前订阅者发放货币——他们需要先续订订阅。移除链接后,未来的发放将停止,但已授予的积分不受影响。 当过期开关开启时,到期积分将遵循订阅生命周期: - 在账单重试或宽限期内,Adapty 不会授予新的积分。 - 取消订阅后,积分在付费周期结束前保持有效。 - 订阅到期时,Adapty 会将剩余积分重置为 0。 ## 后续步骤 \{#next-steps\} 创建货币并关联产品后,购买操作将自动发放积分。接下来你可以: - 在 [Virtual currency balance](virtual-currency-balance) 页面追踪每位用户的余额。 - 通过服务端 API 在运行时发放、消耗和读取余额:[Create virtual currency transaction](api-adapty/operations/createVirtualCurrencyTransaction) 和 [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances)。如需完整示例,请参阅 [Virtual currency quickstart](virtual-currency-quickstart)。 --- # File: virtual-currency-balance --- --- title: "虚拟货币余额" description: "在用户画像中查看虚拟货币余额,并在事件历史记录中回顾每次余额变动。" --- :::link 主要文章:[虚拟货币](virtual-currencies) ::: [Profiles/CRM](profiles-crm) 中的每个用户画像都会显示该用户的虚拟货币余额,以及每次变动的历史记录。 ## 在用户画像中查看余额 \{#view-balances-in-a-user-profile\} 打开用户的[用户画像](https://app.adapty.io/profiles/users)。**Virtual currency** 卡片列出了该用户持有的所有货币。 每行显示: - 货币**代码**,例如 `COINS`。 - 当前**余额**,以整数表示。 余额会随着用户赚取、消费或获得积分而更新。余额不能低于 0:如果某笔交易试图消费超过用户持有量,则会以 `insufficient_balance` 失败,且不会产生任何变动。没有余额记录的用户画像将显示空白卡片。 如需在自己的代码中读取余额,请调用服务端 API 中的 [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances)。 ## 在事件历史中查看余额变更 \{#review-balance-changes-in-the-event-history\} 每次余额变更都会显示在用户画像的事件历史中,最新记录排在最前面。每条记录会注明变更内容及受影响的货币。 历史记录中包含三种虚拟货币事件: - **Virtual currency credited**:通过购买、续订或试用开始获得的积分。 - **Virtual currency transaction**:通过服务端 API 调用对余额进行了充值或扣减。 - **Virtual currency expired**:在计费周期结束时或订阅终止时,到期积分被重置。 每条记录会列出所有发生变更的货币: - **Virtual currency**:货币名称和代码,例如 `Gold Coins (COINS)`。 - **Amount**:带符号的变动金额——正数表示增加,负数表示扣减。 - **Balance after**:变动生效后的货币余额。 一个事件可以同时更改多种货币——例如,一笔[交易](api-adapty/operations/createVirtualCurrencyTransaction)可以同时扣减一种货币并增加另一种货币,从而实现两者之间的转换。 ## 余额、用户画像与设备 \{#balances-profiles-and-devices\} 余额始终归属于某一个[用户画像](profiles-crm),且不会转移到其他用户画像。Adapty 不会像[在用户账户之间共享访问等级](profiles-crm#sharing-paid-access-between-user-accounts)那样,在用户画像之间共享或转移余额。 实际上,这意味着: - **已识别用户**:客户用户 ID 始终解析到同一个用户画像,因此用户在每台登录设备上看到的余额都相同。 - **匿名用户**:匿名用户画像可以获得和消费积分,但仅存在于单一设备上。当同一用户在另一台设备上打开应用且未登录时,Adapty 会创建一个余额为零的新匿名用户画像。 因此,若要跨设备运行虚拟货币系统,需要识别您的用户。您可以通过 SDK 在应用代码中识别用户(请参阅快速入门中的[识别用户](ios-quickstart-identify)),也可以通过[服务端 API](getting-started-with-server-side-api) 从后端进行识别。 晚期识别用户也有其自身的隐患:如果用户在匿名状态下积累了积分,随后使用已属于另一个用户画像的 customer user ID 登录,设备会切换到该用户画像及其余额,匿名状态下获得的积分将留在旧用户画像上,无法访问。如果 customer user ID 是新的,它会绑定到当前用户画像,用户可以保留其余额。为了安全起见,请在用户获得或购买积分之前完成身份识别。 在[服务端 API](getting-started-with-server-side-api) 调用中,使用 `adapty-profile-id` 或 `adapty-customer-user-id` 请求头来标识用户画像——两者都会解析到同一个用户画像。对于匿名用户画像,请使用 `adapty-profile-id`。 --- # File: app-store-offers --- --- title: "App Store 中的优惠" description: "设置并管理 App Store 优惠,提升用户留存率。" --- :::info 请先完成[商店产品](quickstart-products)的配置,再按照本指南操作。 ::: App Store 中的优惠是针对自动续费订阅推出的特别活动,包括折扣和套装优惠,可帮助吸引新用户、提升转化率。 App Store 提供四种优惠类型,Adapty 全部支持: - **[新用户优惠](#introductory-offers)(面向新用户)**: - 免费或折扣订阅期 - 仅限新用户(从未激活过新用户优惠或持有订阅的用户) - 无需在 Adapty 中将其关联到产品。Adapty 会自动为符合条件的用户在购买时应用优惠。 - **[促销活动](#promotional-offers)和[赢回优惠](#win-back-offers)**: - Adapty 会在购买时自动应用这些优惠,但你需要先在产品和付费墙中配置相应优惠。 - 促销活动包括免费订阅期、百分比折扣和固定价格折扣,所有用户均可参与。 - 赢回优惠包括免费订阅期或百分比折扣,仅限已流失用户参与。 - **优惠码**:详情请参阅[在 iOS 中兑换优惠码](making-purchases#redeem-offer-codes-in-ios)。 :::important 要使用 App Store 优惠,请将您的[订阅密钥](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers)上传至 Adapty 看板。 ::: ## 新用户优惠 \{#introductory-offers\} 如果用户符合条件,Adapty 会在 iOS 上自动应用新用户优惠。 要为你销售的产品启用新用户优惠,只需在 App Store Connect 中创建即可: 1. 在 App Store Connect 中打开你的应用,切换到 **Monetization > Subscriptions**。 2. 选择一个订阅组,然后导航到所需的订阅。该订阅必须已配置时长。 3. 点击 **View all Subscription Pricing**,切换到 **Introductory offers** 标签页,然后点击 **Set up introductory offer**。 4. 选择新用户优惠适用的国家和地区。 5. 选择新用户优惠的开始和结束日期。如果新用户优惠没有具体的结束日期,请选择 **No end date**。点击 **Next**。 6. 选择新用户优惠类型。根据所选类型,还需设置优惠时长和价格。详情请参阅 [Apple 官方文档](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions)。 7. 确认选择后,点击 **Confirm**。 完成此设置后,您无需在 Adapty 中进行任何额外操作。符合条件的用户购买该产品时,优惠将自动激活。请确保只向符合优惠条件的用户展示包含该产品的付费墙。 ## 促销活动 Adapty 会自动为符合条件的用户应用促销活动。请先在 App Store Connect 中设置优惠,然后在 Adapty 中将其添加到产品和付费墙: 1. 在 App Store Connect 中打开您的应用,从左侧菜单切换到 **Monetization > Subscriptions**。 2. 选择订阅组,并导航到所需订阅。该订阅必须已配置时长。 3. 点击 **View all Subscription Pricing**,切换到 **Promotional offers** 标签页,然后点击 **Set up promotional offer**。 4. 设置促销活动的详细信息。这些值创建后无法更改且会被复用,请谨慎填写。 - **Promotional offer reference name**:促销活动名称,用户不可见。 - **Promotional offer identifier**:促销活动识别码,用于在 Adapty 中添加该优惠。 5. 选择促销活动类型。类型决定用户是享受折扣价格还是获得免费期。如需折扣,请选择 **Pay as you go** 或 **Pay up front**;如需免费订阅期,请选择 **Free**。然后设置优惠时长和价格。详情请参阅 [Apple 文档](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-promotional-offers-for-auto-renewable-subscriptions)。 6. 如有需要,为不同国家和地区设置不同价格,然后点击 **Next**。 7. 确认您的选择,然后点击 **Confirm**。 8. [将促销活动添加](create-offer)到 Adapty。 ## 赢回优惠 \{#win-back-offers\} :::important 在创建赢回优惠之前,您的订阅必须先通过 App Review 审核。 ::: Adapty 会自动为符合条件的用户应用赢回优惠。请先在 App Store Connect 中设置优惠,然后在 Adapty 中将其添加到产品和付费墙: 1. 在 App Store Connect 中打开你的应用,从左侧菜单切换到 **Monetization > Subscriptions**。 2. 选择一个订阅组,并导航到所需的订阅。该订阅必须已配置时长。 3. 点击 **View all Subscription Pricing**,切换到 **Win-back offers** 标签页,然后点击 **Create offer**。 4. 填写赢回优惠的详细信息。这些值在创建后无法更改。 - **Reference name**:优惠名称,用户不可见。 - **Offer identifier**:优惠标识码,用于在 Adapty 中添加该优惠。 5. 配置优惠类型、时长和价格。详情请参阅 [Apple 文档](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-win-back-offers)。 6. 确认选择后点击 **Confirm**。 7. 在 Adapty 中[添加该优惠](create-offer)。 ## 后续步骤 \{#next-steps\} 添加优惠后,继续完成以下设置: - 如果你还有 **Google Play 应用**,请设置 [Google Play 优惠](google-play-offers)。 - 如果你有**促销活动或赢回优惠**,请[将其添加到 Adapty](create-offer)。 - 如果你只有**新用户优惠**,且没有促销活动或赢回优惠,那么你已经完成设置。[Adapty 如何处理优惠](create-offer#how-adapty-works-with-offers) 这一部分内容也可供参考。 --- # File: google-play-offers --- --- title: "Google Play 中的优惠活动" description: "配置 Google Play 优惠活动以提升应用变现能力和用户留存率。" --- 在 Google Play 中,任何类型的优惠活动(免费试用或折扣付款)均以**优惠**的形式添加。要创建优惠活动,您必须先创建一个订阅并添加自动续订基础方案。 优惠活动始终针对订阅中的基础方案创建。在下方截图中,您可以看到一个名为 `premium_access`(1) 的订阅,其中包含两个基础方案:`1-month`(2) 和 `1-year`(3)。 <img src="/assets/shared/img/c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 要在 Google Play Console 中创建优惠活动: 1. 点击 **Add offer** 并从列表中选择基础方案。 <img src="/assets/shared/img/75a5d69-eb0bc9a-001931-November-03-eQdthUMx.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 输入优惠活动 ID。该 ID 后续将用于分析和 Adapty 看板,请为其设置一个有意义的名称。 <img src="/assets/shared/img/ff282c2-c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 选择资格标准: 1. **New customer acquisition**:该优惠活动仅对新订阅者开放,且这些用户此前未使用过该优惠。这是最常见的选项,建议默认使用。 2. **Upgrade**:该优惠活动面向从其他订阅升级的用户。当您希望向现有订阅者推广更高价位的方案时使用,例如从订阅青铜等级升级至黄金等级的用户。 3. **Developer determined**:您可以通过应用代码控制哪些用户可以使用该优惠活动。在生产环境中使用时请谨慎,以避免潜在欺诈行为:用户可能反复激活免费或折扣订阅。此类优惠活动的一个典型使用场景是赢回已流失的订阅者。 <img src="/assets/shared/img/ee302dc-a506e5a-001934-November-03-TVBLOz2L.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 为您的优惠活动最多添加两个定价阶段。共有三种可用阶段类型: 1. **Free trial**:订阅可在配置的时间内(最少 3 天)免费使用。这是最常见的优惠类型。 2. **Single payment**:如果用户预付费用,订阅价格更低。例如,通常月度方案售价 $9.99,但使用此优惠类型后,前三个月合计 $19.99,享受 30% 的折扣。 3. **Discounted recurring payment**:订阅在前 `n` 个周期内享受优惠价格。例如,通常月度方案售价 $9.99,但使用此优惠类型后,前三个月每月仅需 $4.99,享受 50% 的折扣。 一个优惠活动可以包含两个阶段。在这种情况下,第一个阶段必须是免费试用(Free trial),第二个阶段为单次付款(Single payment)或折扣周期性付款(Discounted recurring payment)。这两个阶段将按此顺序依次生效。 <img src="/assets/shared/img/d6267f3-a48f79e-001936-November-03-A13wutRh.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,使用 Adapty 付费墙编辑工具创建的付费墙仅会显示多阶段 Google 订阅优惠的第一个阶段。但请放心,当用户购买产品时,所有优惠阶段将按照 Google Play 中的配置依次生效。 ::: 5. 激活优惠活动以在应用中使用。 <img src="/assets/shared/img/d3fc09b-f149ba6-001937-November-03-MO9Gz3ap.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 继续[将优惠活动添加到 Adapty](create-offer)。 :::note 不同基础方案的优惠活动 ID 可以相同。 ::: ## 后续步骤 \{#next-steps\} 添加优惠活动后,请继续完成以下设置: - 如果您**同时在 App Store 拥有应用**,请参阅 [App Store 指南](app-store-offers)。 - 如果您**仅在 Google Play 拥有应用**,请参照[此指南](create-offer)将优惠活动添加到 Adapty。 --- # File: create-offer --- --- title: "将优惠添加至 Adapty" description: "使用 Adapty 的工具创建和管理特殊订阅优惠。" --- Adapty 允许你为新用户、现有用户或流失用户提供试用或折扣优惠。 在 App Store Connect 或 Google Play Console 中完成设置后,你需要通过以下两个步骤将其添加到 Adapty: 1. [在 Adapty 中使用商店的优惠 ID 将优惠添加到产品。](#1-create-offer) 2. [在流程或付费墙中展示优惠。](#2-display-offer) :::warning 新用户优惠(App Store)会在用户符合条件时自动生效,无需在 Adapty 中手动添加到产品。 本指南介绍如何配置促销活动(App Store)、赢回优惠(App Store)以及所有 Google Play 优惠。 ::: ## 0. 开始之前 \{#0-before-you-start\} 在 Adapty 中设置优惠之前,请确保以下事项: 1. 您已在商店中创建了所需的所有优惠: - [App Store](app-store-offers) - [Google Play](google-play-offers) 2. 您已在 Adapty 中创建了[产品](create-product)并添加了其 ID。 3. 对于 App Store:您已上传[用于促销活动的应用内购买密钥](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers)。 ## 1. 在 Adapty 中为产品添加优惠 \{#1-add-offer-to-product-in-adapty\} 无论是 Play Store 和 App Store 的促销活动,还是 App Store 的赢回优惠,在应用商店完成配置后,将其添加到 Adapty 非常简单: 1. 在 Adapty 主菜单中打开 [**Products**](https://app.adapty.io/products),找到要添加优惠的产品。 2. 找到目标产品后,在 **Actions** 列中点击产品旁边的 **3-dot** 按钮,然后选择 **Edit**。 3. 在 **Edit product** 窗口中,点击 **+** 并选择 **Add offers**。 4. 点击 **Add offer**。 5. 然后填写产品的优惠详情。 以下是优惠的相关字段: - **Offer name**:为优惠命名,便于在 Adapty 中识别。使用任何方便你的名称即可。 - **App Store Offer type**:选择你要添加的 App Store 优惠类型:促销活动或赢回优惠。(新用户优惠无需手动添加——如果有,系统会自动应用。) - **App Store Offer ID**:这是你[在 App Store 中设置的](app-store-products)优惠唯一 ID。 - **Play Store Offer ID**:同样,这是你[在 Play Store 中设置的](android-products)优惠唯一 ID。 :::tip 如果 **App Store Offer ID** 或 **Play Store Offer ID** 字段未激活,请切换到 **Products** 标签页并选择一个产品 ID。 ::: 6. (可选)如有需要,点击 **Add offer** 继续添加优惠。 7. 点击 **Save**,将优惠添加到产品中。 ## 2. 展示优惠 \{#2-display-offer\} 将优惠关联到产品后,需要在用户看到该产品的地方展示它——可以在流程中,也可以在付费墙中。 ### 在流程中添加优惠 \{#add-offer-to-flow\} 在 [流程编辑工具](adapty-flow-builder) 中,优惠通过产品元素绑定到具体产品上。请先添加产品元素并为其分配产品——详见[设置购买](paywall-product-block)。 绑定优惠的步骤: 1. 在画布上,选择要显示优惠的产品卡片。 2. 在右侧面板的 **Product** 下,选择对应产品,然后在 **Select offer (optional)** 下拉菜单中选择优惠。 ### 将优惠添加到付费墙 \{#add-offer-to-paywall\} :::info 你无法向处于 **live** 状态的付费墙添加优惠。如果想为已有付费墙添加优惠,请先[复制](duplicate-paywalls)它,然后在新付费墙中配置产品。 ::: 要让优惠在应用的[付费墙](paywalls)中对用户可见且可供选择,请按以下步骤操作: 1. 创建或编辑付费墙时,在 **General** 标签页中,添加您刚才为其创建了优惠的产品。 2. 从 **Offer** 列表中为该产品选择您之前创建的优惠。该列表仅对已添加优惠的产品可用。 3. 如有需要,可以继续添加更多产品和优惠,但每个产品只能添加一个优惠。 ## Adapty 如何处理优惠活动 \{#how-adapty-works-with-offers\} 请注意以下关于 Adapty 中优惠活动的工作方式: - 当用户符合某项优惠的条件时,Adapty 会在用户购买时自动应用您配置的优惠。 - 如果某个产品在 App Store 中同时配置了新用户优惠和促销活动,符合条件的用户将优先享受新用户优惠。新用户优惠期结束后,如果用户仍符合促销活动的条件,且您在 Adapty 中配置了该促销活动,则在用户再次尝试购买该产品时,促销活动将自动生效。 - 如果您希望更精细地控制优惠的应用方式,或在某些情况下需要不附带优惠地销售产品,可以通过以下几种方式实现: - 在 App Store 或 Google Play Console 中配置资格条件 - 在 App Store 或 Google Play Console 中创建一个不含优惠的独立产品 - 在 Adapty 中创建一个不含优惠的独立产品,将包含两种产品版本的付费墙添加到某个[版位](placements),并使用目标受众[市场细分](segments)来控制向不同用户展示哪个付费墙。例如,您可以根据**订阅产品**或**付费访问等级**创建市场细分,或使用[自定义属性](profiles-crm)来实现自己的业务逻辑。 --- # File: create-access-level --- --- title: "创建访问等级" description: "在 Adapty 中创建并分配访问等级,以实现更好的用户细分。" --- 访问等级让您无需硬编码特定产品 ID,即可控制应用用户在移动应用中的操作权限。每个产品定义了用户获得某一访问等级的时长。因此,每当用户完成购买时,Adapty 会为其授予特定时段(订阅)或永久(永久授权购买)的应用访问权限。 当您在 Adapty 看板中创建应用时,系统会自动生成 `premium` 访问等级。该访问等级作为默认访问等级,无法被删除。 :::tip 您也可以通过 [Developer CLI](developer-cli-reference#adapty-access-levels-create) 以编程方式创建访问等级。 ::: 创建新访问等级的步骤: 1. 在 Adapty 主菜单中进入 **[Products](https://app.adapty.io/access-levels)**,然后选择 **Access levels** 标签页。 <img src="/assets/shared/img/access-level-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击 **Create access level**。 <img src="/assets/shared/img/b8646ca-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Create access level** 窗口中,为其分配一个 ID。该 ID 将作为移动应用内部的标识符,在用户购买后用于开启额外功能的访问权限。此外,该标识符有助于在应用中区分不同的访问等级。请确保其清晰易懂,以便于您的使用。 4. 点击 **Create access level** 确认创建访问等级。 --- # File: assigning-access-level-to-a-product --- --- title: "为产品分配访问等级" description: "为产品分配访问等级,以优化订阅管理。" --- 每个[产品](product)都需要关联一个访问等级,以确保用户在购买后能够获得相应的专属内容。Adapty 会自动确定订阅时长,并将其作为访问等级的到期日期。对于永久授权产品,若用户完成购买,访问等级将永久有效,不设任何到期日期。 将访问等级关联至产品: 1. 在[配置产品](create-product)时,从 **Access Level ID** 列表中选择访问等级。 <img src="/assets/shared/img/access-level-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击 **Save**。 --- # File: give-access-level-to-specific-customer --- --- title: "为特定用户授予访问等级" description: "使用 Adapty 的高级工具为用户分配特定的访问等级。" --- 您可以直接在 Adapty 看板中手动调整特定用户的访问等级。这在客户支持场景中尤为实用。例如,您可以为某位用户额外延长一周的高级功能使用时间,以感谢其留下精彩评价。 ## 在 Adapty 看板中为特定用户授予访问等级 \{#give-access-level-to-a-specific-customer-in-the-adapty-dashboard\} 1. 从 Adapty 主菜单进入 **[Profiles and Segments](https://app.adapty.io/placements)**。 <img src="/assets/shared/img/profiles-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击您想要授予访问权限的用户。 3. 点击 **Add access level**。 <img src="/assets/shared/img/add-access-level.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 选择要授予的访问等级以及该访问等级对此用户的到期时间。 5. 点击 **Apply**。 ## 通过 API 为特定用户授予访问等级 \{#give-access-level-to-a-specific-customer-via-api\} 您也可以选择通过 Adapty API 从服务器端为用户授予访问等级。如果您为推荐用户或其他与产品相关的事件设置了奖励机制,这将非常方便。详细信息请参阅[通过服务端 API 授予访问等级](api-adapty/operations/grantAccessLevel)页面。 --- # File: local-access-levels --- --- title: "本地访问等级" description: "在临时服务中断的情况下管理访问等级。" --- :::important 请注意以下几点: - 本地访问等级从 Adapty SDK 3.12 版本开始支持。 - 出于安全考虑,Android 上的本地访问等级默认处于禁用状态。如有需要,请在 SDK 激活时启用:[Android](sdk-installation-android#enable-local-access-levels)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter#enable-local-access-levels-android)。 ::: 您配置的每个产品都关联了一个[**访问等级**](access-level)。当用户完成购买后,Adapty SDK 会将访问等级分配给该用户的[用户画像](profiles-crm),因此您需要使用此访问等级来判断用户是否可以访问应用内的付费内容。 Adapty SDK 非常可靠,其服务器不可用的情况极为罕见。即使发生这种情况,您的用户也不会察觉到任何影响。 如果用户完成了购买,但 Adapty 无法收到响应,SDK 将切换为直接在应用商店验证购买。因此,访问等级会在应用本地授予,无需进行任何额外配置即可启用此功能。SDK 会在后台自动处理这一切,用户将像正常情况一样访问其已付费的内容。 关于本地访问等级的工作方式,请注意以下几点: - 当用户重新联网后,交易信息将自动推送至 Adapty 服务器,服务器随后会将交易应用到用户画像,并将更新后的用户画像返回给 SDK。 - 在数据推送完成之前,更新后的数据不会显示在 Adapty 数据分析中。 - 本地访问等级仅在 Adapty 服务器不可用时生效,否则 SDK 将使用已缓存的数据。 - 本地访问等级不适用于消耗型商品,但如果消耗型商品在 Adapty 看板中被分配了订阅类型(月付、年付、周付等),则不受此限制。 --- # File: choose-meaningful-placements --- --- title: "选择有意义的版位" description: "使用 Adapty 优化流程和付费墙版位,提升用户参与度和收益。" --- 在[创建版位](create-placement)时,务必考虑应用的逻辑流程以及您希望为用户打造的体验。大多数应用拥有不超过 5 个[版位](placements)即可,同时不影响进行实验的能力。以下是一个版位结构示例: <img src="/assets/shared/img/placement-flows.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **用户引导流程:** 这是用户与你的应用首次互动的阶段。通过在此处结合流程、用户引导和付费墙版位,是向用户展示应用价值的绝佳机会。超过 80% 的订阅在用户引导旅程中完成激活,因此专注于在此阶段推销最高利润的订阅至关重要。借助 Adapty,你可以轻松为不同目标受众设置不同的[流程](adapty-flow-builder)、[用户引导](onboardings)和[付费墙](paywalls),并通过运行 A/B 测试找到最适合你应用的方案。例如,你可以针对美国用户运行 A/B 测试,以 50% 的概率展示价格更高的订阅。 2. **应用设置:** 如果用户在用户引导旅程中未完成订阅,你可以在应用内创建流程或付费墙版位,例如放在应用设置中,或在用户完成某个特定目标操作后触发。由于应用内的用户往往会更审慎地考虑是否订阅,这里的产品价格可以比用户引导阶段略低一些。 3. **促销活动:** 如果用户多次看到流程或付费墙后仍未订阅,可能意味着价格对他们来说偏高,或者他们对订阅本身有所顾虑。此时,你可以向他们展示一个特别优惠,提供最实惠的订阅方案,甚至是永久授权产品。这有助于吸引对价格敏感或对订阅持观望态度的用户完成购买。 大多数应用都有相似的逻辑和版位设置,遵循用户旅程,并在关键节点展示流程、付费墙、用户引导或 A/B 测试,以提升转化率和营收。你可以在每个版位中进行配置,从而灵活试验并优化变现策略。 --- # File: create-placement --- --- title: "创建版位" description: "在 Adapty 中创建和管理版位,以改善流程和付费墙的效果。" --- [版位](placements)是移动应用中的特定位置,用于展示流程、付费墙、用户引导或 A/B 测试。例如,订阅选择界面可能出现在启动流程中,而消耗型商品(如金币)则可能在游戏玩家金币耗尽时弹出。 你可以在不同版位向不同用户群体展示相同或不同的流程、付费墙、用户引导或 A/B 测试——在 Adapty 中,这些用户群体称为"目标受众"。 请阅读[选择有意义的版位](choose-meaningful-placements)部分,了解如何选择合适的版位。 :::tip 您也可以使用 [Developer CLI](developer-cli-reference#adapty-placements-create) 以编程方式创建版位。 ::: :::info 虽然版位的创建流程对于流程、付费墙和用户引导来说大致相同,但您无法创建一个同时服务于多种类型的版位——每种版位类型处理的数据图表各不相同。 ::: ## 创建并配置版位 \{#create-and-configure-a-placement\} 1. 从 Adapty 主菜单进入 **[Placements](https://app.adapty.io/placements)**。根据你想创建的版位类型,切换到 **Flows**、**Paywalls** 或 **Onboardings** 标签页。 2. 点击 **Create placement**。 <img src="/assets/shared/img/create-placement-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 输入**版位名称**。这是在 Adapty 看板中使用的内部标识符,之后可以随时修改。 4. 输入**版位 ID**。你将在 Adapty SDK 中使用该 ID 来调用版位的[流程](adapty-flow-builder)、[付费墙](paywalls)、[用户引导](onboardings)和 [A/B 测试](ab-tests)。该 ID 是每个版位的唯一标识,创建后无法修改。 接下来,为版位分配流程、付费墙、用户引导或 A/B 测试。Adapty 支持[目标受众](audience)——基于[市场细分](segments)的用户群体——因此你可以向不同用户群体展示不同内容。如果不需要定向投放,默认的 *所有用户* 目标受众可覆盖所有人。 :::note 在开始之前,请确保你已创建好想要运行的流程、付费墙、用户引导或 A/B 测试,以及要指定的目标受众。 ::: 1. 在 **Placements/ Your placement** 窗口中,为默认的 *All users* 目标受众添加流程、付费墙、用户引导或 A/B 测试。点击 **Run flow**、**Run paywall** 或 **Run A/B test** 按钮(按钮标签取决于版位类型),然后从下拉列表中选择所需的流程、付费墙、用户引导或 A/B 测试。 2. 如果你希望在版位中使用多个目标受众,为不同用户群体提供个性化内容,请点击 **Add audience** 按钮,并从列表中选择所需的市场细分。 <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 接下来,为该目标受众添加要展示的流程、付费墙、用户引导或 A/B 测试。 4. 根据需要添加更多目标受众。 5. 如果有多个目标受众,请检查它们的优先级顺序是否正确。 6. 点击 **Save and publish button**。 版位保存并发布后,你就拥有了所需的一切——在应用代码中使用 **Placement ID** 来获取并展示它。 ## 后续步骤 \{#next-steps\} 在应用中展示付费墙:[iOS](ios-present-paywalls) | [Android](android-present-paywalls) | [React Native](react-native-present-paywalls) | [Flutter](flutter-present-paywalls) | [Unity](unity-present-paywalls) | [Kotlin Multiplatform](kmp-present-paywalls) | [Capacitor](capacitor-present-paywalls) 在应用中展示用户引导:[iOS](ios-present-onboardings) | [Android](android-present-onboardings) | [React Native](react-native-present-onboardings) | [Flutter](flutter-present-onboardings) | [Unity](unity-present-onboardings) | [Kotlin Multiplatform](kmp-present-onboardings) | [Capacitor](capacitor-present-onboardings) --- # File: edit-placement --- --- title: "编辑版位" description: "了解如何在 Adapty 中编辑版位,以优化流程、付费墙可见性和用户参与度。" --- [版位](placements)是指移动应用中的特定位置,可在该位置展示流程、付费墙、用户引导或 A/B 测试。例如,订阅选项可能出现在启动流程中,而消耗型商品(如金币)则可以在游戏中用户金币耗尽时弹出。 你可以灵活地在多个版位或用户群体(在 Adapty 中称为目标受众)中展示相同或不同的流程、付费墙、用户引导或 A/B 测试。 如需编辑现有版位: 1. 从 Adapty 主菜单进入 **[Placements](https://app.adapty.io/placements)**。根据要编辑的版位类型,切换到 **Flows**、**Paywalls** 或 **Onboardings** 标签页。 2. 点击要编辑的版位。 3. 点击右上角的 **Edit placement**。 4. 进行所需修改。关于此窗口中各选项的详细说明,请参阅[创建版位](create-placement)部分。 5. 点击 **Save and publish** 按钮确认更改。 --- # File: export-placements --- --- title: "导出版位" description: "了解如何在 Adapty 中导出版位,以优化流程、付费墙可见性和用户互动。" --- 当你同时管理多个流程、付费墙和用户引导时,追踪哪些内容展示给哪些用户至关重要。你可以将所有[版位](placements)配置导出为 CSV 文件,查看每个目标受众对应哪个流程/付费墙/用户引导,并在修改配置或运行实验后复查当前设置。 :::tip 如果你更习惯用服务端 API,可以[通过服务端 API 导出版位信息](api-export-analytics/operations/retrievePlacementInfo)。 ::: 导出流程、付费墙或用户引导的版位配置: 1. 前往主菜单中的 **[Placements](https://app.adapty.io/placements)**。切换到 **Flows**、**Paywalls** 或 **Onboardings** 标签页——每种类型的版位需分别导出。 2. 点击 **Export to CSV**。 <img src="/assets/shared/img/export-placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 导出的 CSV 文件包含以下版位信息: - 版位 ID - 版位名称 - 目标受众名称 - 市场细分名称 - 跨版位 A/B 测试名称 - A/B 测试名称 - 流程名称、付费墙名称或用户引导名称(取决于导出时所在的标签页) :::note 流程版位不支持跨版位 A/B 测试,因此该列在流程导出中将为空。 ::: --- # File: delete-placement --- --- title: "删除版位" description: "了解如何在 Adapty 中删除版位,同时不影响您的流程或付费墙效果。" --- [版位](placements)是指您移动应用中的特定位置,可在该位置展示流程、付费墙、用户引导或 A/B 测试。 :::danger 尽管你可以删除任何版位,但请务必确认不要删除移动应用中正在使用的版位。删除一个活跃的流程或付费墙版位后,如果你已[配置了备用付费墙](fallback-paywalls),该备用付费墙将永久显示,且你将无法在已发布的应用版本中将其替换为动态流程或付费墙。 ::: 要删除已有版位: 1. 从 Adapty 主菜单进入 **[Placements](https://app.adapty.io/placements)**。根据要删除的版位类型,切换到 **Flows**、**Paywalls** 或 **Onboardings** 标签页。 2. 点击版位旁边的 **3-dot** 按钮,选择 **Delete** 选项。 <img src="/assets/shared/img/delete-placement.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在弹出的 **Delete placement** 窗口中,输入你即将删除的版位名称。 <img src="/assets/shared/img/8177c51-delete_placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Delete forever** 按钮确认删除。 --- # File: add-audience-paywall-ab-test --- --- title: "为版位添加目标受众与流程、付费墙或 A/B 测试" description: "在 Adapty 中对不同目标受众细分的流程和付费墙运行 A/B 测试。" --- Adapty 中的**目标受众**是由[市场细分](segments)定义的用户群体,可让你向特定用户展示流程、付费墙、用户引导和 A/B 测试。通过筛选条件构建市场细分,确保每个用户群体看到适合自己的内容。 将目标受众添加到[版位](placements)后,你可以将流程、付费墙、用户引导或 A/B 测试定向投放给特定用户群体。将目标受众与版位关联,能确保合适的用户在其使用旅程中的恰当时机看到正确的内容。 打开您想要添加流程、付费墙、用户引导或 A/B 测试的版位,或在 [版位](https://app.adapty.io/placements) 菜单中新建一个。 :::note 在开始之前,请确保你已创建好想要运行的流程、付费墙、用户引导或 A/B 测试,以及要指定的目标受众。 ::: 1. 在 **Placements/ Your placement** 窗口中,为默认的 *All users* 目标受众添加流程、付费墙、用户引导或 A/B 测试。点击 **Run flow**、**Run paywall** 或 **Run A/B test** 按钮(按钮标签取决于版位类型),然后从下拉列表中选择所需的流程、付费墙、用户引导或 A/B 测试。 2. 如果你希望在版位中使用多个目标受众,为不同用户群体提供个性化内容,请点击 **Add audience** 按钮,并从列表中选择所需的市场细分。 <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 接下来,为该目标受众添加要展示的流程、付费墙、用户引导或 A/B 测试。 4. 根据需要添加更多目标受众。 5. 如果有多个目标受众,请检查它们的优先级顺序是否正确。 6. 点击 **Save and publish button**。 --- # File: change-audience-priority --- --- title: "在版位中更改目标受众优先级" description: "在 Adapty 中调整目标受众优先级,以向用户提供个性化优惠。" --- 当一个[版位](placements)中存在不同的用户目标受众时,一个用户可能同时属于多个目标受众。例如,如果您定义了"初学者"、"跑步者"以及"所有用户"这样的通用目标受众,那么当一个用户同时符合多个类别时,确定优先考虑哪个目标受众就至关重要。 <img src="/assets/shared/img/afee54f-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在这种情况下,我们依赖目标受众优先级。目标受众优先级是一个数字顺序,其中 #1 优先级最高。它指导检查目标受众的顺序。简单来说,目标受众优先级帮助 Adapty 决定在选择要展示的付费墙、用户引导或 A/B 测试时,首先应用哪个目标受众。如果目标受众的优先级较低,可能符合条件的用户会被跳过,转而被导向另一个优先级更高的目标受众。 跨版位目标受众(即为[跨版位 A/B 测试](ab-tests#ab-test-types)创建的目标受众)始终优先于常规目标受众。 "所有用户"目标受众始终具有最低优先级,因为它是一个备用选项,包含所有不符合其他目标受众条件的用户。 要调整版位的目标受众优先级: 1. 在创建新版位或编辑现有版位时,点击 **Edit priority**。只有在版位中添加了至少三个目标受众("所有用户"加上其他两个)时,该按钮才可见。如果少于三个,顺序是显而易见的——"所有用户"目标受众排在最后。 <img src="/assets/shared/img/edit-priority.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在打开的 **Edit audience priorities** 窗口中,通过拖放方式重新排列目标受众以正确排序。 <img src="/assets/shared/img/reorder_audiences.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Save** 按钮。 --- # File: placement-metrics --- --- title: "版位数据图表" description: "在 Adapty 中分析版位数据图表,提升付费墙表现。" --- 借助 Adapty,你可以在应用中灵活创建和管理多个版位,每个版位都可以关联不同的付费墙或 A/B 测试。这种灵活性让你能够针对特定的市场细分人群,尝试不同的优惠或定价模型,从而优化应用的变现策略。 为了深入了解版位的表现及用户与您的优惠之间的互动情况,Adapty 会追踪与已展示付费墙相关的各类用户交互和交易行为。其强大的分析系统可捕获浏览量、独立浏览量、购买量、试用量、退款量、转化率和收入等数据图表。 所收集的数据图表会实时持续更新,并可通过 Adapty 友好的看板方便地访问和分析。您可以自由自定义分析时间范围、按不同参数应用筛选器,以及跨版位、用户市场细分或产品比较数据图表。 版位数据图表可在版位列表中查看,您可在此获取所有版位的整体表现概览。该高级视图为每个版位提供汇总数据图表,便于您比较其表现并识别趋势。 如需对每个版位进行更详细的分析,可导航至版位详细数据图表页面。在该页面上,您将看到所选版位的全面专项数据图表。这些数据图表能更深入地揭示特定版位的表现,帮助您评估其有效性并做出数据驱动的决策。 <img src="/assets/shared/img/3e711fc-CleanShot_2023-07-26_at_14.55.042x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 按安装日期筛选数据图表 \{#filter-metrics-by-install-date\} 付费墙、试用和购买的数据图表可以按两种不同的日期维度进行分组: - **事件日期** — 付费墙被查看、试用开始或购买发生的时间。 - **安装日期** — 用户首次打开应用的时间。 对于同一日期范围,两种视图呈现的数据可能差异显著。**按安装日期筛选数据图表** 复选框用于控制看板采用哪种分组方式: - **未勾选(默认)**:数据图表按事件日期分组。 - **已勾选**:数据图表按安装日期分组。 **示例。** 将日期范围设置为 4 月 1 日至 30 日,查看试用数据。 - **未勾选**:显示 4 月内*开始*的试用,无论这些用户何时安装应用。 - **已勾选**:显示 4 月内*安装*应用的用户所产生的试用,无论其试用何时开始。 使用安装日期视图可衡量特定同期群的用户获取效果;使用事件日期视图可衡量特定时段内付费墙或用户引导的活跃情况。 ### 数据图表控件 \{#metrics-controls\} 系统根据所选时间段展示数据图表,并按左侧列参数以四级缩进进行组织。 #### 数据图表的视图选项 \{#view-options-for-metrics-data\} 版位数据图表页面提供两种数据视图选项:基于付费墙的视图和基于目标受众的视图。 <img src="/assets/shared/img/9d26b32-Export-1690376094858.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在基于付费墙的视图中,数据图表按与付费墙关联的版位进行分组,便于用户按不同版位分析数据图表。 在基于目标受众的视图中,数据图表按付费墙的目标受众进行分组,用户可评估特定目标受众市场细分的数据图表。 #### 时间范围 \{#time-ranges\} 你可以从多种时间段中进行选择,以分析数据图表,聚焦于特定的天数、周数、月数或自定义日期范围。 <img src="/assets/shared/img/15d2c3e-CleanShot_2023-07-26_at_16.49.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 可用筛选器与分组 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: Adapty 提供了强大的工具,帮助你按需筛选和自定义数据图表分析。在 Adapty 的数据图表页面,你可以使用多种时间范围、分组选项和筛选功能。 - ✅ 筛选方式:目标受众、付费墙、付费墙分组、版位、国家、商店。 - ✅ 分组方式:市场细分、商店、产品 #### 单项数据图表 \{#single-metrics-chart\} 版位数据图表页面的核心组成部分之一是数据图表区域,它以可视化方式呈现所选数据图表,便于分析。 版位数据图表页面的图表区域包含一个水平条形图,直观展示所选数据图表的值。图表中的每个条形对应一个数据值,大小按比例呈现,一目了然。水平轴表示所分析的时间范围,垂直列显示数据图表的数值。所有数据图表值的总计显示在图表旁边。 <img src="/assets/shared/img/4623c5b-Export-1690375597411.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 此外,点击图表区域右上角的箭头图标可展开视图,在图表完整折线上显示所选数据图表。 #### 数据图表总计摘要 \{#total-metrics-summary\} 在单项数据图表旁边,还有一个数据图表总计摘要区域,显示特定时间点所选数据图表的累计值,您可以通过下拉菜单更改所显示的数据图表。 <img src="/assets/shared/img/0f647cf-CleanShot_2023-07-26_at_14.55.492x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 数据图表定义 \{#metrics-definitions\} 借助我们全面的定义,充分发挥版位数据图表的价值。从收入到转化率,获取有价值的洞察,为您的变现策略提供强力支撑,助力应用走向成功。 <img src="/assets/shared/img/771a0f0-Export-1690375049771.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: #### 收入 \{#revenue\} 该数据图表表示在特定版位内,来自购买和续订所产生的以美元计的总收入。请注意,收入计算不包括 Apple App Store 或 Google Play Store 的佣金,且为扣除任何费用前的金额。 #### 实际收益 \{#proceeds\} 该数据图表表示应用所有者在特定版位内,扣除 Apple App Store 或 Google Play Store 佣金后,实际从购买和续订中获得的以美元计的收入。它反映了直接贡献于应用收益的净收入。有关实际收益计算方式的更多信息,请参阅 Adapty [文档](analytics-cohorts#revenue-vs-proceeds)。 #### ARPPU \{#arppu\} ARPPU 即每付费用户平均收入,衡量特定版位内每位付费用户所产生的平均收入。计算方式为总收入除以独立付费用户数量。例如,若总收入为 15,000 美元,付费用户数为 1,000,则 ARPPU 为 15 美元。 #### ARPAS \{#arpas\} ARPAS 即每活跃订阅者平均收入,用于衡量特定版位内每位活跃订阅者所产生的平均收入。计算方式为总收入除以已激活试用或订阅的用户数量。例如,若总收入为 5,000 美元,订阅者数量为 1,000,则 ARPAS 为 5 美元。该数据图表有助于评估每位订阅者的平均变现潜力。 #### ARPU \{#arpu\} 仅适用于用户引导版位。ARPU 是查看用户引导的每位用户的平均收入,计算方式为总收入除以独立浏览用户数量。 #### 独立购买转化率 \{#unique-cr-to-purchases\} 独立购买转化率的计算方式为特定版位内的购买数量除以独立浏览量。它侧重于购买量与独立浏览量之比,从而洞察特定版位内将独立访客转化为付费用户的效果。 #### 购买转化率 \{#cr-to-purchases\} 购买转化率的计算方式为特定版位内的购买数量除以付费墙的总浏览次数。它表示特定版位内产生购买的浏览比例,从而洞察您的付费墙将用户转化为付费用户的效果。 #### 独立试用转化率 \{#unique-cr-to-trials\} 独立试用转化率的计算方式为特定版位内启动的试用数量除以独立浏览量。它衡量特定版位内导致试用激活的独立浏览比例,从而洞察您的付费墙将独立访客转化为试用用户的效果。 #### 购买量 \{#purchases\} 购买量代表特定版位内付费墙上各类交易的累计总数。该数据图表包含以下类型的交易(不含续订): - 在特定版位内直接进行的新购买。 - 最初在特定版位内激活的试用转化。 - 在特定版位内进行的订阅降级、升级和跨级操作。 - 在特定版位内的订阅恢复,例如在自动续订到期后重新激活订阅。 通过综合考量这些不同类型的交易,购买量数据图表可全面呈现特定版位内的整体获客和变现活动情况。 #### 试用量 \{#trials\} 试用量数据图表表示在特定版位内已激活的试用总数,反映了通过您的付费墙在这些版位内启动试用期的用户数量。该数据图表有助于追踪试用优惠的有效性,并提供关于用户参与度以及从试用转化为付费订阅的洞察。 #### 已取消试用量 \{#trials-canceled\} 已取消试用量数据图表表示特定版位内已关闭自动续订功能的试用数量。当用户手动取消订阅试用时即会产生此情况,表明用户决定在试用期结束后不继续订阅。追踪已取消试用量可提供关于用户行为的宝贵信息,帮助您了解特定版位内用户退出试用的比率。 #### 退款量 \{#refunds\} 退款量数据图表表示特定版位内退款的购买和订阅数量,包括因各种原因(如用户申请、支付问题或其他适用退款政策)而被撤销或退款的交易。 #### 退款率 \{#refund-rate\} 退款率的计算方式为特定版位内的退款数量除以首次购买数量(不含续订)。例如,若有 5 笔退款和 1,000 笔首次购买,则退款率为 0.5%。 #### 浏览量 \{#views\} 浏览量数据图表表示特定版位内用户浏览付费墙的总次数。用户每次访问该版位内的付费墙均计为一次独立浏览。追踪浏览量有助于了解用户与付费墙的互动程度,提供关于用户行为以及付费墙在应用特定区域内的版位和设计效果的洞察。 #### 独立浏览量 \{#unique-views\} 独立浏览量数据图表表示特定版位内用户浏览付费墙的独立实例数量。与将每次访问计为一次浏览的总浏览量不同,独立浏览量无论用户访问多少次,均只计算该用户对特定版位内付费墙的一次访问。追踪独立浏览量有助于更准确地衡量用户参与度以及付费墙在特定版位内的覆盖范围,因为它关注的是独立用户而非总访问次数。 #### 完成量与独立完成量 \{#completions--unique-completions\} 仅适用于用户引导版位。完成量统计用户完成用户引导版位的次数,即从第一屏浏览到最后一屏。若用户完成两次,则计为两次**完成量**,但只有一次**独立完成量**。 #### 独立完成率 \{#unique-completions-rate\} 仅适用于用户引导版位。独立完成量除以独立浏览量的结果。该数据图表有助于了解用户与用户引导版位的互动情况,并在发现用户忽略该引导时进行相应调整。 --- # File: create-paywall --- --- title: "创建付费墙" description: "了解如何使用 Adapty 的付费墙编辑工具创建高转化率付费墙。" --- [付费墙](paywalls)是 Adapty 中定义要提供哪些产品的配置。在 Adapty 中,付费墙是在应用中获取产品的唯一方式。 无论以何种方式展示,您都需要一个付费墙: - [**付费墙编辑工具**](adapty-paywall-builder):在无代码编辑器中设计页面。Adapty 负责渲染并处理购买逻辑。 - **自定义付费墙**:自行实现 UI,并使用付费墙配置获取产品。 创建后,将付费墙分配到[版位](placements)——版位控制用户看到哪个付费墙。已上线的付费墙产品是固定的,因此其数据图表始终反映相同的产品组合,让您可以比较不同产品和定价方案之间的表现。 :::tip 您也可以使用 [Developer CLI](developer-cli-reference#adapty-paywalls-create) 以编程方式创建付费墙。 ::: <details> <summary>开始创建付费墙之前(点击展开)</summary> 1. [至少创建一个产品](create-product)。 2. (可选)[创建优惠](create-offer)。 </details> ## 创建付费墙 \{#create-paywall\} 在 Adapty 看板中创建新付费墙: 1. 在 Adapty 主菜单中进入 [**Paywalls**](https://app.adapty.io/paywalls)。此页面显示所有付费墙及其数据图表的概览。 2. 点击 **Create paywall**。 3. 在 **Paywalls / New paywall** 页面,输入 **Paywall name** 以在 Adapty 看板中标识此付费墙。 4. 点击 **Add product**。 5. 选择要向用户展示的产品。 :::note - 列表中的产品顺序将在 SDK 中保持不变,请按您期望的顺序排列产品。 - 付费墙在生产环境中展示后,您将无法更改其产品,因为这可能会影响付费墙的数据图表。 ::: 6. 如果您为产品提供免费试用或其他优惠,请在此处添加,否则它们将不可用。从 **Offer** 列表中为该产品选择您[之前创建的](create-offer)优惠。该列表仅对有优惠的产品可用。 7. 点击 **Create as a draft** 确认创建付费墙。 您的付费墙现已创建成功! <img src="/assets/shared/img/create-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '900px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 后续步骤 \{#next-steps\} 创建第一个付费墙后: 1. 将其添加到[版位](placements)。版位 ID 将是唯一需要硬编码的实体,您将使用它们来获取要销售的产品。 2. 后续使用付费墙的方式取决于您的实现方案: - 如果您想使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请在无代码编辑器中设计付费墙。Adapty 将负责渲染付费墙并处理购买逻辑,您只需在应用代码中展示付费墙即可。 - 如果您使用自定义付费墙,请参阅适用于您平台的 Adapty 应用内购买实现指南: - [iOS](ios-implement-paywalls-manually) - [Android](android-implement-paywalls-manually) - [React Native](react-native-implement-paywalls-manually) - [Flutter](flutter-implement-paywalls-manually) - [Unity](unity-implement-paywalls-manually) - [Kotlin Multiplatform](kmp-implement-paywalls-manually) --- # File: customize-paywall-with-remote-config --- --- title: "使用远程配置设计付费墙" description: "在 Adapty 中使用远程配置自定义付费墙,实现更精准的用户定向。" --- :::important 本指南介绍经典付费墙的远程配置。如需了解 Flow Builder,请参阅[使用远程配置自定义流程](customize-flow-with-remote-config)。 ::: 付费墙远程配置是一个强大的工具,提供灵活的配置选项。它允许使用自定义 JSON 数据来精确定制你的付费墙。你可以通过它定义标题、图片、字体、颜色等各种参数。 <details> <summary>开始自定义付费墙之前(点击展开)</summary> 1. [创建产品](create-product)。 2. [创建付费墙并将产品添加到其中](create-paywall)。 </details> 要使用远程配置自定义付费墙,请按以下步骤操作: 1. 打开 Adapty 主菜单中的 [**Paywalls**](https://app.adapty.io/paywalls) 部分。 2. 点击付费墙将其打开。 <img src="/assets/shared/img/remote-config.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 切换到 **Remote config** 选项卡。 <img src="/assets/shared/img/remote-config-3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 远程配置有 2 种视图: - [表格视图](customize-paywall-with-remote-config#table-view-of-the-remote-config) - [JSON 视图](customize-paywall-with-remote-config#json-view-of-the-remote-config) **表格**视图和 **JSON** 视图包含相同的配置元素。两者只是使用偏好上的差异,唯一的区别在于表格视图提供了右键菜单,在修正本地化错误时非常实用。 如需切换视图,随时点击 **Table** 或 **JSON** 标签即可。 无论您选择哪种视图来自定义付费墙,之后都可以通过 SDK 使用 `AdaptyPaywall` 的 `remoteConfig` 或 `remoteConfigString` 属性访问这些数据,并对付费墙进行相应调整。您也可以通过[服务端 API](api-adapty/operations/updatePaywall) 以编程方式更新远程配置的值,从而无需手动在看板上操作即可动态修改付费墙配置。以下是一些远程配置的使用示例。 <Tabs groupId="current-os" queryString> <TabItem value="Titles" label="标题" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or others texts ``` </TabItem> <TabItem value="Images" label="图片" default> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your paywall ``` </TabItem> <TabItem value="Fonts" label="字体" default> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="颜色" default> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML" default> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed on the paywall ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="软/硬付费墙" default> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disalow skipping paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="翻译" default> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> 你可以自由组合各种选项,打造自己的方案。这样就能测试不同的标题、文字、图片、字体、颜色等内容。 ### 远程配置的 JSON 视图 \{#json-view-of-the-remote-config\} 在远程配置的 **JSON** 视图中,您可以输入任意 JSON 格式的数据: <img src="/assets/shared/img/3356ff5-remote_config_JSON.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 远程配置的表格视图 \{#table-view-of-the-remote-config\} 如果你不太习惯直接编写代码,但又需要修改 JSON 中的某些值,Adapty 为你提供了**表格**视图。 <img src="/assets/shared/img/4c27b2f-remote_config_table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 这是您 JSON 的表格版副本,便于阅读和理解。颜色编码有助于区分不同的数据类型。 要添加键,请点击 **Add row** 按钮。我们会自动检查值与类型的映射关系,如果您的修改可能导致无效的 JSON,系统会显示警告。 <img src="/assets/shared/img/ef682d8-add_raw.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 其他行选项主要适用于[付费墙本地化](add-remote-config-locale): <img src="/assets/shared/img/17bcf80-remote_config_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 现在是时候[创建版位](create-placement)并将付费墙添加到其中了。完成后,你可以在移动应用中<InlineTooltip tooltip="展示你的远程配置付费墙">[iOS](present-remote-config-paywalls)、[Android](present-remote-config-paywalls-android)、[React Native](present-remote-config-paywalls-react-native)、[Flutter](present-remote-config-paywalls-flutter)以及[Unity](present-remote-config-paywalls-unity)</InlineTooltip>。 --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "在流程编辑工具中添加语言区域" description: "在 Adapty 的流程编辑工具中添加本地化内容,让全球用户都能看到自己语言的界面。" --- 为流程添加本地化支持后,它就能以多种语言呈现给用户。在流程编辑工具中,本地化按屏幕组织,每个屏幕都会显示翻译完成百分比,方便追踪进度。 :::tip 在添加其他语言之前,请先用默认语言区域完成流程的所有设置。 ::: ## 添加并配置本地化 \{#add-and-set-up-localization\} 1. 在左侧面板中,点击 Localizations,然后点击 **Add locale**,选择要添加的语言。 2. 每个已添加的语言区域将作为一列显示在本地化表格中,并预先填入默认语言的内容。 3. 如果只想查看尚未翻译的内容,可以在左侧面板中开启 **Missing only** 开关,表格将只显示未翻译的行。 ## 导出与导入以供外部翻译 \{#export-and-import-for-external-translation\} 您可以将本地化文件导出,发送给翻译人员,并在翻译完成后导入结果。 在顶部工具栏中,点击 **Import / Export**。 ### 导出文件格式 \{#export-file-format\} 导出会生成一个 `.tsv`(制表符分隔)文件,每行对应一个可翻译元素。各列说明如下: | 列名 | 说明 | |--------|-------------| | `Screen` | 该元素所属的屏幕(例如 `Welcome`、`Quiz`) | | `Element` | 该屏幕内自动生成的元素标识符。可在 **Interactions** > **Element ID** 中修改。 | | `Property` | 属性类型(例如 `content`) | | `[default_locale]` | 默认语言代码(例如 `en`) | | `[locale]` | 每个已添加的语言对应一列(例如 `fr`、`es`) | 示例: :::note 对于未翻译的行,将对应语言列留空——Adapty 会将其视为缺失内容。 ::: ### 导入文件要求 \{#import-file-requirements\} - **格式**:`.tsv`(制表符分隔值) - **标题行**:必须包含 `Screen`、`Element`、`Property` 列,以及至少一个语言区域列 - **语言区域列名**:必须与流程中已添加的语言区域代码一致。若文件中包含流程里不存在的语言区域代码,将会报错。 - **部分导入**:可以只包含部分行;文件中未包含的行将保留其当前值 ## 手动翻译 \{#translate-manually\} 你也可以直接在本地化表格的任意单元格中输入翻译内容。 要管理某一行,请打开其右侧的上下文菜单(**⋮**): - **Reset to default**:将该行的翻译恢复为默认语言的值。 ## 预览本地化内容 \{#preview-the-localization\} 要检查翻译效果,请在流程编辑工具中切换当前语言区域,并逐屏查看。 --- # File: add-remote-config-locale --- --- title: "使用远程配置本地化付费墙" description: "为 Adapty 付费墙添加远程配置语言区域以实现个性化。" --- 在文化多样的世界中,针对不同语言对付费墙进行适配至关重要。本地化功能让您能够为特定地区的用户打造定制化体验。您可以为每个付费墙添加多种语言版本,确保您的产品与当地受众产生共鸣。 如果您没有使用 Adapty 付费墙编辑工具设计付费墙,您仍然可以本地化自定义付费墙,并在不重新部署应用的情况下管理本地化内容: 1. 您在 Adapty 看板中创建包含变量的远程配置。变量可以代表文本、媒体或其他内容类型。 2. 您为每个语言区域设置变量值。 3. 您在应用代码中处理这些变量。 4. 当您获取包含产品的付费墙并发送语言区域时,您将获得对应的变量值。 这样,本地化内容不会被硬编码到应用代码中,您随时可以进行调整。 无论是表格视图还是 JSON 格式,您都可以轻松调整每种语言的设置。例如,翻译字符串键、切换布尔值(例如,英语为 `TRUE`,意大利语为 `FALSE`),甚至替换背景图片。 ## 为远程配置付费墙设置本地化 \{#set-up-localization-for-remote-configured-paywalls\} 1. 前往 Adapty 中的 [**Paywalls**](https://app.adapty.io/paywalls) 板块。 2. 点击付费墙将其打开。 3. 切换到 **Remote config** 标签页。 <img src="/assets/shared/img/switch_to_remote_config.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Locales** 并选择您想要支持的语言。保存更改以将这些语言区域添加到付费墙。 <img src="/assets/shared/img/add_locale.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 现在,您可以手动翻译内容、使用 AI,或导出本地化文件供外部翻译人员使用。 ## 使用 AI 翻译付费墙 \{#translate-paywalls-with-ai\} AI 驱动的翻译是本地化付费墙的快捷高效方式。 您可以翻译 **String** 和 **List** 类型的值。默认情况下,所有行都已选中(以紫色高亮显示)。已翻译的行标记为绿色,默认不会包含在新的翻译中。未选中或未翻译的行显示为灰色。 <img src="/assets/shared/img/localization-table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/localization-json.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. 选择要翻译的行。建议取消勾选包含 ID、URL 和变量的行,以防止 AI 对其进行翻译。 2. 选择翻译目标语言。 <img src="/assets/shared/img/localization-table-language.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **AI Translate** 应用翻译。所选行将被翻译并添加到付费墙中,已翻译的行将标记为绿色。 ## 导出本地化文件供外部翻译 \{#exporting-localization-files-for-external-translation\} 虽然 AI 驱动的本地化正成为一种流行趋势,但您可能更倾向于使用更可靠的方式,例如专业的人工翻译或经验丰富的翻译机构。如果是这种情况,您可以导出本地化文件分享给翻译人员,然后将翻译结果重新导入 Adapty。 通过 **Export** 按钮导出时,会为每种语言创建单独的 `.json` 文件,并打包为一个压缩包。如果您只需要一个文件,可以直接从特定语言的菜单中导出。 <img src="/assets/shared/img/localization-single-export.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 收到翻译文件后,使用 **Import** 按钮一次性或逐个上传。Adapty 将自动验证文件,确保其符合正确格式。 ### 导入文件格式 \{#import-file-format\} 为确保导入成功,导入文件必须满足以下要求: - **文件名和扩展名:** 文件名必须与其所代表的语言区域一致,并以 `.json` 为扩展名。您可以在 Adapty 看板中验证并复制语言区域名称。如果名称无法识别,导入将失败。 <img src="/assets/shared/img/locale-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **有效的 JSON:** 文件必须是有效的 JSON 格式。否则,导入将失败。 ## 手动本地化 \{#manual-localization\} 有时,您可能需要微调翻译内容、为特定语言区域添加不同的图片,或直接调整远程配置。 1. 选择要翻译的元素并输入新值。您可以更新 **String** 和 **List** 类型的值,或替换更适合该语言区域的图片。 <img src="/assets/shared/img/032b429-remote_config_localization.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 利用英语语言区域中的上下文菜单高效解决本地化问题: - **Copy this value to all locales**:将所选行的英语值覆盖到所有非英语语言区域,替换其中已做的更改。 - **Revert all row changes to original values**:放弃当前会话中所做的所有更改,将值恢复到上次保存的状态。 <img src="/assets/shared/img/d7e70f1-remote_confi_loc_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在为付费墙添加语言区域后,请确保在应用代码中正确实现语言区域代码。请参阅 <InlineTooltip tooltip="the guides on how to use localizations and locale codes in your app">[iOS](localizations-and-locale-codes)、[Android](android-localizations-and-locale-codes)</InlineTooltip> --- # File: web-paywall-configuration --- --- title: "网页付费墙配置" --- 在 **Web paywall** 页面点击 **Create web paywall** 后,您将被重定向到一个独立页面,用于设置网页付费墙的设计和支付方式。 ## 设置支付方式 \{#set-up-a-payment-method\} 首先,你需要连接一个处理购买的支付提供商。可用选项如下: - Stripe - Paddle - Paypal - Solidgate :::important 为确保 Adapty 中网页付费墙分析数据的准确追踪,你需要在 Adapty 中[添加产品](product),并填写对应的 Stripe/Paddle/其他支付提供商的产品 ID。 ::: 设置支付提供商的步骤: 1. 在网页付费墙列表页面,点击 **Settings**,然后切换到 **Integrations** 标签页。 2. 选择一个支付提供商,并按照页面上的集成说明进行操作。 3. ⚠️ 如果您选择 Stripe,请确保使用**测试模式(Test Mode)**环境中的密钥,尽管界面显示的是 **Sandbox**。否则您的网页付费墙将无法正常工作。Stripe 的 **Sandboxes** 目前尚不支持。 <img src="/assets/shared/img/web-paywall-configuration-stripe.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 设置 Apple Pay 域名验证 \{#set-up-apple-pay-domain-verification\} 在 **Settings > Domains** 中,选择用于域名验证的主要支付服务商。然后,向相应服务商验证你的付费墙域名: **Stripe**: 1. 前往 [Payment method domain settings](https://dashboard.stripe.com/settings/payment_method_domains),点击 **Add a new domain**。 2. 添加 `app.funnelfox.com` 以及你的个人付费墙子域名(格式类似 `paywalls-....fnlfx.com`)。如需查找你的子域名,请前往 **Settings > Domains**,复制 **Hosted subdomain** 的值。 **Paddle**: 1. 在 Paddle 控制台中,前往 **Checkout > Website approval**,点击 **Add a new domain**。 2. 添加 `app.funnelfox.com` 以及你的个人付费墙子域名(格式类似 `paywalls-....fnlfx.com`)。要查找你的子域名,请前往 **Settings > Domains**,复制 **Hosted subdomain** 的值。 Paddle 的审批流程为人工审核,你需要等待域名状态从 `Pending` 变为 `Approved`。 **FunnelFox Billing**: 请按照 [FunnelFox Billing 集成说明](https://funnelfox.com/docs/billing/integration-billing-funnelfox)进行操作。 **SolidGate**: 1. 在 Solidgate 看板中,前往 **Developers > Apple Pay Domains**。 2. 点击 **+ Add new domain**,粘贴您的项目域名(来自 FunnelFox 的 **Settings > Domains**)。如有自定义域名,也一并添加。 3. 若要在预览模式下使用 Apple Pay,还需添加 `http://app.funnelfox.com/`。 ## 创建并配置网页付费墙 \{#create-and-configure-a-web-paywall\} 1. 在网页付费墙列表页面,点击 **Create a paywall**。 2. 输入付费墙名称,然后点击 **Create**。 <img src="/assets/shared/img/web-paywall-configuration-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 系统将自动跳转到一个基础模板,其中包含两个订阅选项和 Apple Pay 购买按钮。 The first screen lists the subscription plans. The second and third screens are checkout screens. Each screen corresponds to one plan you offer. If you have only one plan, delete the extra screen. If you have more, you need to duplicate the checkout screens. The last screen users see after a successful purchase is where you need to clearly indicate that they can return to your app. <img src="/assets/shared/img/web-paywall-configuration-10.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 设置方案列表:添加或删除方案和价格。屏幕上显示的所有价格和方案均不会动态添加,因此需要手动配置。 <img src="/assets/shared/img/web-paywall-configuration-8.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 为每个方案添加或配置结账页面。建议在每个结账页面添加总金额,让用户在点击购买按钮之前了解需要支付的费用。 6. 在结账页面中,Apple Pay 按钮已默认存在。若要使其正常工作,请在每个页面上配置以下内容: 1. **Product type**:选择是否要添加试用期或折扣。 2. **Trial period**:输入试用期时长。 3. **Product**:从您的支付提供商中选择产品。 :::important 请确保该产品已添加到 Adapty。否则,购买结果将被设置为默认值。 ::: 4. **Subscription discount**:可选,从您的支付提供商中选择优惠券。 <img src="/assets/shared/img/web-paywall-configuration-6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 现在,您需要将方案与结账页面关联。在方案选择页面,点击 **Continue** 按钮,然后为每个方案选择目标页面。 <img src="/assets/shared/img/web-paywall-configuration-9.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 当付费墙准备好后,你需要获取其链接以在 Adapty 中激活该付费墙。获取方式取决于你是在测试还是在生产环境中发布: 1. **沙盒测试**:点击右上角的 **Preview**,复制链接。 2. **生产环境**:点击右上角的 **Publish**,然后点击 **Home**,从 **URL** 列中复制链接。 <img src="/assets/shared/img/web-paywall-configuration-11.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 就这些!使用此链接[继续完成设置](web-paywall#step-2-trigger-the-paywall)。 --- # File: fallback-paywalls --- --- title: "备用付费墙" description: "使用备用付费墙确保 Adapty 中流畅的用户体验。" --- 为了保持流畅的用户体验,请务必为你的[付费墙](paywalls)和[用户引导](onboardings)设置**备用版本**。 当应用加载付费墙时,Adapty SDK 会向服务器请求付费墙配置数据。但如果设备因网络问题或服务器故障无法连接到 Adapty,会发生什么呢? * 如果用户之前访问过该付费墙,且设备已缓存其数据,应用将**从缓存**加载付费墙数据。 * 如果设备未缓存付费墙,应用会查找本地存储的配置文件,从而在不报错的情况下展示付费墙。 Adapty 会自动生成备用配置文件供你下载使用。每个文件包含*所有*版位的平台专属配置。 ## 快速开始 \{#get-started\} 1. 从 Adapty [下载备用配置文件](/local-fallback-paywalls)。 2. 使用 Adapty SDK 配置备用付费墙: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) ## 限制说明 \{#limitations\} 备用付费墙是硬编码并本地存储的,因此不具备常规 Adapty 付费墙的动态能力。 * 备用付费墙不支持[国际化](paywall-localization)。Adapty 生成配置文件时,默认使用 `en` 语言区域。 * 每个版位只能有一个备用付费墙。如果你的配置中针对不同[目标受众](audience)设置了不同的付费墙,Adapty 会使用面向"所有用户"的配置。 * 备用付费墙不支持 [A/B 测试](ab-tests)。如果付费墙参与了 A/B 测试,其备用配置文件将包含权重最高的实验变体。 * 备用付费墙不支持[远程管理](customize-paywall-with-remote-config)。如需更新配置文件,必须在 App Store / Google Play 上发布新版本的应用。 --- # File: local-fallback-paywalls --- --- title: "下载备用付费墙" description: "在 Adapty 中使用本地备用付费墙,确保订阅流程顺畅无阻。" --- Adapty 会自动为你的[备用付费墙](/fallback-paywalls)生成 JSON 配置文件,每个平台对应一个文件。这些文件同时包含用户引导的备用数据。 如果某个版位下有多个付费墙或用户引导,备用版本将包含权重最高或受众范围最广的那个变体。每当你修改付费墙或用户引导时,Adapty 都会更新这些文件。 请按照以下步骤下载备用配置: 1. 打开 **[Placements](https://app.adapty.io/placements)** 页面。 2. 点击 **Fallbacks** 按钮。 3. 从下拉菜单中选择目标平台(*iOS* 或 *Android*)。 4. 选择你的 SDK 版本以开始下载。 <img src="/assets/shared/img/9c63367-placements.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 下载后的操作 \{#after-the-download\} 请根据你的具体平台参考相应的配置指南: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) --- # File: paywall-metrics --- --- title: "付费墙数据图表" description: "跟踪和分析付费墙性能指标,以提升订阅收入。" --- Adapty 收集一系列数据图表,帮助您更好地衡量付费墙的表现。所有数据图表均实时更新,但浏览量除外(每隔几分钟更新一次)。除浏览量外,所有数据图表均归因于付费墙内的产品。本文档概述了可用的数据图表、其定义及计算方式。 付费墙数据图表在付费墙列表中即可查看,让你一览所有付费墙的整体表现。这个汇总视图展示了每个付费墙的聚合指标,帮助你评估各付费墙的效果并找出可优化的方向。 如需对某个付费墙进行更深入的分析,可以进入该付费墙的详细数据图表页面。在这里,你将看到所选付费墙的全面专项指标,从而获得更深层的性能洞察。 ### 按安装日期筛选数据图表 \{#filter-metrics-by-install-date\} 付费墙、试用和购买的数据图表可以按两种不同的日期维度进行分组: - **事件日期** — 付费墙被查看、试用开始或购买发生的时间。 - **安装日期** — 用户首次打开应用的时间。 对于同一日期范围,两种视图呈现的数据可能差异显著。**按安装日期筛选数据图表** 复选框用于控制看板采用哪种分组方式: - **未勾选(默认)**:数据图表按事件日期分组。 - **已勾选**:数据图表按安装日期分组。 **示例。** 将日期范围设置为 4 月 1 日至 30 日,查看试用数据。 - **未勾选**:显示 4 月内*开始*的试用,无论这些用户何时安装应用。 - **已勾选**:显示 4 月内*安装*应用的用户所产生的试用,无论其试用何时开始。 使用安装日期视图可衡量特定同期群的用户获取效果;使用事件日期视图可衡量特定时段内付费墙或用户引导的活跃情况。 ### 数据图表控件 \{#metrics-controls\} 系统根据所选时间段显示数据图表,并按左侧列参数以三级缩进方式进行组织。 对于正在运行的付费墙,数据图表涵盖从付费墙启动日期至当前日期的时间段。对于已停用的付费墙,数据图表涵盖从启动日期到所选时间段结束日期的完整时间段。草稿和已归档的付费墙也会显示在数据图表表格中,但如果这些付费墙没有可用数据,则仅列出条目而不显示任何数据图表。 #### 数据图表的查看选项 \{#view-options-for-metrics-data\} 付费墙页面提供两种数据图表查看方式:按版位查看和按目标受众查看。 在按版位查看模式下,数据图表按与该付费墙关联的版位进行分组,方便用户按不同版位分析数据。 在目标受众视图中,数据图表按付费墙的目标受众分组,用户可以查看不同目标受众细分下的具体数据。您可以通过付费墙详情页顶部的下拉选项切换视图。 #### 时间范围 \{#time-ranges\} 您可以从多种时间周期中选择来分析数据图表,支持按天、周、月或自定义日期范围进行聚焦查看。 #### 可用筛选条件与分组 \{#available-filters-and-grouping\} :::link 主要文章:[Analytics controls](controls-filters-grouping-compare-proceeds) ::: Adapty 提供了强大的工具,帮助你根据需求对数据图表分析进行过滤和自定义。在 Adapty 的数据图表页面,你可以使用多种时间范围、分组选项和过滤条件。 - 过滤条件:目标受众、国家、付费墙、付费墙状态、付费墙分组、版位、国家、商店、产品及产品商店。 - 分组方式:产品和商店。 #### 单项数据图表 \{#single-metrics-chart\} 付费墙数据图表页面的核心组成部分之一是数据图表区域,它以可视化方式呈现所选数据图表,便于分析。 付费墙数据图表页面的图表区域包含一个水平条形图,直观地展示所选数据图表的具体数值。图表中的每条横条对应一个数据图表值,并按比例缩放,让你一眼就能看懂数据。水平轴表示分析的时间范围,垂直列显示各数据图表的数值。图表旁边还会显示所有数据图表值的总和。 此外,点击数据图表区域右上角的箭头图标可展开视图,以完整折线图的形式展示所选数据图表。 #### 数据汇总 \{#total-metrics-summary\} 单项数据图表旁边会显示数据汇总区域,展示特定时间点所选数据图表的累计值,你可以通过下拉菜单切换要显示的数据图表。 ### 数据图表定义 \{#metrics-definitions\} :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: #### 收入 \{#revenue\} 该数据图表表示通过购买和续订产生的 USD 总金额。请注意,收入计算不包含 App Store / Play Store 的佣金,且为扣除任何费用之前的金额。 #### 实际到账金额 \{#proceeds\} 该数据图表表示应用所有者在扣除适用的 App Store / Play Store 佣金后,从购买和续订中实际收到的 USD 金额。 :::important 如果您的应用已加入佣金减免计划,请告知 Adapty。为确保计算结果准确,请在[应用设置](general)中指定您的 [Small Business Program](app-store-small-business-program) 和 [Reduced Service Fee program](google-reduced-service-fee) 状态。 ::: 它反映了直接贡献于应用收益的净收入。有关收益计算方式的更多信息,请参阅 Adapty [文档](analytics-cohorts#revenue-vs-proceeds)。 #### ARPPU ARPPU 是每位付费用户的平均收入,计算方式为总收入除以唯一付费用户数。例如:$15000 收入 / 1000 位付费用户 = $15 ARPPU。 #### ARPAS \{#arpas\} 每位活跃订阅者的平均收入,用于衡量每位活跃订阅者产生的平均收入。计算方式为总收入除以已激活试用或订阅的订阅者数量。例如,若总收入为 $5,000,订阅者数量为 1,000,则 ARPAS 为 $5。该指标有助于评估每位订阅者的平均变现潜力。 #### 独立访客购买转化率(CR)\{#unique-conversion-rate-cr-to-purchases\} 独立访客购买转化率的计算方式是:购买次数除以独立访客浏览次数。例如,若有 10 次购买和 100 次独立访客浏览,则独立访客购买转化率为 10%。该指标关注购买次数与独立访客浏览次数的比率,有助于了解将独立访客转化为付费用户的效果。 #### 购买转化率(CR)\{#cr-to-purchases\} 购买转化率的计算方式为购买次数除以总浏览量。例如,若有 10 次购买和 100 次浏览,则购买转化率为 10%。此数据图表表示最终产生购买的浏览量占比,有助于了解付费墙将用户转化为付费客户的效果。 #### 独立用户试用转化率(CR) \{#unique-cr-to-trials\} 独立用户试用转化率的计算方式为已开始的试用次数除以独立浏览量。例如,若有 30 次试用开始和 100 次独立浏览量,则独立用户试用转化率为 30%。此数据图表衡量最终激活试用的独立浏览量占比,有助于了解付费墙将独立访客转化为试用用户的效果。 #### 购买 \{#purchases\} 购买代表付费墙上各类交易的累计总量。此数据图表包含以下交易类型(不含续订): - 直接在付费墙上完成的新购买。 - 最初在付费墙上激活的试用转化。 - 在付费墙上进行的订阅降级、升级和跨级操作。 - 在付费墙上恢复的订阅,例如在无自动续订的情况下到期后重新激活订阅。 通过综合考虑这些不同类型的交易,购买数据图表提供了付费墙上整体获客和变现活动的全面视图。 #### 试用 \{#trials\} 试用数据图表表示已激活的试用总次数,反映通过付费墙发起试用期的用户数量。此数据图表有助于跟踪试用产品的效果,并可提供有关用户参与度以及从试用转化为付费订阅的洞察。 #### 已取消试用 \{#trials-canceled\} 已取消试用数据图表表示已关闭自动续订功能的试用数量。当用户手动取消订阅试用时,即表明其决定在试用期结束后不继续订阅。跟踪已取消试用可提供有关用户行为的宝贵信息,帮助您了解用户退出试用的比率。 #### 退款 \{#refunds\} 退款数据图表表示已退款的购买和订阅数量。这包括因各种原因被撤销或退款的交易,例如客户申请、付款问题或其他适用的退款政策。 #### 退款率 \{#refund-rate\} 退款率的计算方式为:退款数量除以首次购买数量(不含续订)。例如,若有 5 笔退款和 1000 笔首次购买,则退款率为 0.5%。 #### 浏览量 \{#views\} 浏览量数据图表表示用户查看付费墙的总次数。每次用户访问付费墙时,均计为一次单独的浏览。例如,若一个用户访问付费墙两次,则记录为两次浏览。跟踪浏览量有助于了解用户对付费墙的参与程度和互动情况,并为付费墙版位及设计的有效性提供洞察。 #### 独立浏览量 \{#unique-views\} 独立浏览量数据图表表示用户查看付费墙的独立实例数量。与总浏览量将每次访问计为单独一次不同,独立浏览量对每位用户访问付费墙仅计数一次,无论其访问多少次。例如,若一个用户访问付费墙两次,则记录为一次独立浏览。跟踪独立浏览量有助于更准确地衡量用户参与度和付费墙的覆盖范围,因为它侧重于个别用户而非总访问次数。 :::warning 请务必使用 `.logShowFlow()`(iOS SDK v4+)/ `.logShowPaywall()` 方法向 Adapty 上报付费墙的展示事件。否则,付费墙的展示次数将不会计入数据图表,转化率数据也会失去参考价值。 ::: --- # File: migrate-paywalls --- --- title: "在应用之间迁移付费墙" description: "了解如何在 Adapty 中从其他应用迁移付费墙。" --- 使用 Adapty,您无需为每个应用从头构建新的付费墙。如果您管理多个应用,可以将任何通过编辑工具创建的付费墙的付费墙编辑工具配置从一个应用迁移到另一个应用。 迁移允许您复制所有视觉配置: - 付费墙及所有付费墙元素的布局设置 - 媒体资源 - 本地化 迁移仅适用于编辑工具配置,不会复制产品或远程配置。 :::note 如果您迁移的付费墙编辑工具配置包含自定义字体,请在设备上进行测试,因为这些字体可能显示不正确。 ::: ## 迁移付费墙 \{#migrate-paywall\} :::important 您只能迁移在**新版** Adapty 付费墙编辑工具中创建的付费墙。若要迁移**旧版**付费墙编辑工具创建的付费墙,必须先将其迁移至新版付费墙编辑工具。 ::: 要迁移付费墙编辑工具配置: 1. **对于新付费墙**:开始[创建付费墙](create-paywall)并添加产品。然后,点击 **Build no-code paywall** 以打开模板库。 **对于已有付费墙**:前往 **Builder & Generator** 标签页的 **Layout settings** 部分,点击 **Change template**。 2. 在编辑付费墙模板时,点击 **Copy a design from your apps** 框内的 **Choose paywall**。 <img src="/assets/shared/img/migrate-paywall-builder.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 选择您想要复制配置的应用和付费墙。 <img src="/assets/shared/img/migrate-app.png" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Copy Selected Paywall**。 迁移完成后,您可以进行任何所需的编辑,这些更改不会影响原始付费墙。 --- # File: duplicate-paywalls --- --- title: "复制付费墙" description: "了解如何在 Adapty 中管理重复的付费墙并优化付费墙性能。" --- 如果您需要对 Adapty 中的现有付费墙进行少量修改,尤其是当该付费墙已在您的移动应用中使用,且您不希望影响分析数据时,您可以直接复制它。您可以根据需要使用这些副本来替换部分或全部版位中的原始付费墙。 复制操作会创建一个包含付费墙所有详细信息的副本,例如名称、产品以及任何促销活动。新付费墙的名称将添加"Copy"后缀,以便您轻松与原始付费墙区分。 在 Adapty 看板中复制付费墙的步骤如下: 1. 在 Adapty 主菜单中打开 [**Paywalls**](https://app.adapty.io/paywalls) 板块。Adapty 看板的付费墙列表页面提供了您账户中所有付费墙的概览。 2. 点击付费墙旁边的 **3-dot** 按钮,然后选择 **Duplicate** 选项。 <img src="/assets/shared/img/duplicate.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 调整新付费墙的设置,然后点击 **Save** 按钮。 4. 如果原始付费墙当前已在某个版位中使用,Adapty 将提示您是否要在版位中将原始付费墙替换为其副本。如果您选择 **Create and replace original**,新付费墙将立即变为 **Live** 状态。或者,您也可以将它们创建为 **Draft** 状态的新付费墙,稍后再将其添加到版位中。 --- # File: archive-paywalls --- --- title: "归档付费墙" description: "了解如何在 Adapty 中归档过时的付费墙,同时不丢失数据。" --- 随着您深入使用 Adapty 并不断调整付费墙设置,可能会积累一些不再符合当前策略或活动的付费墙。这些处于 `Inactive`(未激活)状态的付费墙会使您的工作区变得杂乱,让您难以找到最重要的付费墙。为解决这一问题,Adapty 推出了归档不必要付费墙的功能。 归档可确保这些付费墙被安全保存而不会被永久删除,日后如有需要随时可以访问。此外,已归档的付费墙可以从默认视图中过滤掉,从而整理您的工作区,简化用户界面。在本指南中,我们将带您了解如何在 Adapty 中高效地归档付费墙,让您对付费墙管理流程拥有更强的掌控力。 温馨提示:当前在至少一个版位中处于激活状态的付费墙无法被归档。如果您希望归档此类付费墙,请事先将其从所有版位中移除。 :::note 如果付费墙正在用于未归档的 A/B 测试中,则无法将其归档。这样用户可以查看已完成的 A/B 测试的详细数据图表,而关联的付费墙也是该数据的一部分。 ::: **归档付费墙的步骤:** 1. 在 Adapty 主菜单中打开 [**Paywalls**](https://app.adapty.io/paywalls) 部分。 2. 点击付费墙旁边的 **3-dot** 按钮,然后选择 **Archive** 选项。 <img src="/assets/shared/img/archive-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Archive paywall** 窗口中,输入您希望归档的付费墙名称,然后点击 **Archive** 按钮。 --- # File: restore-paywall --- --- title: "从存档中恢复付费墙" description: "在 Adapty 中恢复付费墙,以确保为用户提供不间断的订阅服务。" --- 存档付费墙的功能对于简化付费墙管理流程非常有帮助。它允许您隐藏不再需要的付费墙,从而减少工作区中的混乱。此外,恢复已存档付费墙的选项提供了灵活性,使您能够在需要时将其重新纳入策略。 已存档的付费墙可能会在默认视图中被过滤掉。要查看它们,请在 **State** 筛选器中选择 **Archived**。 **将付费墙从存档中恢复** 1. 在 Adapty 主菜单中打开 [**Paywalls**](https://app.adapty.io/paywalls) 部分。 2. 确保已存档的付费墙显示在列表中。如果没有,请更新右侧的筛选器。 <img src="/assets/shared/img/paywall-filter.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击已存档付费墙旁边的 **3-dot** 按钮,然后选择 **Back to active**。 <img src="/assets/shared/img/restore-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: profiles-crm --- --- title: "用户画像/CRM" description: "在 Adapty 中管理用户画像和 CRM 数据,以增强目标受众细分能力。" --- 用户画像是面向你的用户的 CRM 系统。通过用户画像,你可以: 1. 通过用户画像 ID、Customer User ID、邮箱或交易 ID 查找特定用户。 2. 查看用户的事件时间线,包括账单问题、宽限期及其他[事件](events)。 3. 分析用户属性,如订阅状态、总收入/实际收益等。 4. 为用户授予订阅。 :::note 事件feed中的事件到达看板时会有延迟。新的用户画像和属性变更可能不会立即显示。 ::: <img src="/assets/shared/img/profiles.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::link 要了解 Adapty 如何创建和关联用户画像,请参阅[用户画像的工作原理](how-profiles-work)。 ::: ## 查找用户 \{#finding-users\} 在用户画像列表中,你可以通过以下方式搜索特定用户: - **Profile ID**:Adapty 对该用户的内部标识符(也称为 Adapty ID)。 - **Customer user ID**:你的应用为该用户设置的标识符(如已设置)。 - **Email**:用户的电子邮件地址(如已作为自定义属性传入)。 - **Transaction ID**:购买时产生的应用商店交易 ID。 点击任意一行即可打开该用户的完整用户画像。 ## 订阅状态 \{#subscription-state\} 在用户画像列表中,您可以按订阅状态对用户进行筛选和排序。状态值如下: | 用户**状态** | 描述 | | :--------------------- | :----------------------------------------------------------- | | Subscribed | 用户拥有有效订阅,且自动续订已开启。 | | Auto-renew off | 用户已关闭自动续订,但在订阅期结束前仍可使用高级功能。 | | Subscription cancelled | 用户已取消订阅,且订阅已完全终止。 | | Billing issue | 用户在订阅或试用期到期后因账单问题无法完成扣款。 | | Grace period | 用户当前处于宽限期,原因是在订阅或试用期到期后尝试扣款时发生了账单问题。 | | Active trial | 用户拥有有效订阅,当前处于试用期。 | | Trial cancelled | 用户已取消试用,且没有有效订阅。 | | Never subscribed | 用户从未订阅或开始试用,仍为免费用户。 | ## 用户属性 \{#user-attributes\} <img src="/assets/shared/img/ce8df4d-CleanShot_2023-06-26_at_20.32.232x.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 您可以通过 SDK 向 Adapty 发送额外的用户属性。 默认情况下,Adapty 会设置: | 属性 | 描述 | | ------------------ | ------------------------------------------------------------ | | Customer user ID | 您的系统中终端用户的标识符。 | | Adapty ID | Adapty 内部的终端用户标识符,即 Profile ID。 | | IDFA | 广告主标识符,由 Apple 分配给用户设备。在 iOS 14+ 上需要 App Tracking Transparency (ATT) 权限。Android 不可用。 | | Country | 终端用户所在国家/地区。 | | OS | 终端用户使用的操作系统。 | | Device | 终端用户可见的设备型号名称。 | | Install date | 用户在 Adapty 中首次被记录的日期:<ul><li>用户的创建日期。</li><li>如果用户在您集成 Adapty 之前已安装应用,安装日期以其首次交易日期为准。</li><li>如适用,以历史数据导入时提供的日期为准。</li></ul> | | Created at | 用户的创建日期。 | 请至少发送您的内部用户 ID 或用户邮箱。这样您就可以在用户画像列表中通过这些标识符找到对应用户。 安装 SDK 后,Adapty 会自动从支付队列中收集用户事件,并在用户画像中显示。上表中的属性均会自动收集——您无需手动发送。 ### 自定义属性 \{#custom-attributes\} 在用户画像的 **Attributes** 部分,您可以查看通过 SDK 或 API 设置的自定义属性。您也可以使用 **Add attribute** 按钮手动添加属性。 <img src="/assets/shared/img/378c1fb-add_attribute.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## 授予订阅 \{#granting-a-subscription\} 在用户画像中,你可以延长活跃订阅的有效期,或授予用户某个访问等级的永久授权——无需用户实际购买。 <img src="/assets/shared/img/b1d74fd-edit_paid_access_level.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 以下场景中此功能最为实用: - 在账单或支持问题后对用户进行补偿。 - 执行手动促销活动或 Beta 测试计划。 - 无需真实购买即可测试订阅流程。 要授予访问权限,请打开用户的用户画像,进入 **Access levels** 部分,然后点击 **Edit**。设置到期日期并保存。到期日期必须是未来的时间,且一旦设置后不能缩短。调整活跃订阅的到期日期不会影响正在进行中的付款。 :::note 授予访问权限不会创建 App Store 或 Google Play 购买事件。用户的事件记录和分析数据将与真实购买流程有所不同。 ::: 您也可以使用 [Grant access level](api-adapty/operations/grantAccessLevel) API 方法以编程方式授予访问权限。 ## 在用户账户之间共享付费访问权限 \{#sharing-paid-access-between-user-accounts\} :::link 主要文章:[在用户账户之间共享付费访问权限](sharing-paid-access-between-user-accounts) ::: ### 访问等级共享历史 \{#access-sharing-history\} 当访问等级被共享或转让时,用户的用户画像会显示一个指向关联用户画像的链接——即共享访问的用户画像或接收访问的用户画像。要查看关联的用户画像,请在用户的 **Profile** 中,点击访问等级旁边的链接。 <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note 虚拟货币余额不像访问等级那样在用户画像之间共享或转移。每个余额仅归属于单个用户画像——详见[余额、用户画像与设备](virtual-currency-balance#balances-profiles-and-devices)。 ::: ## 后续步骤 \{#next-steps\} - 要了解 Adapty 如何创建和关联用户画像,请参阅[用户画像的工作原理](how-profiles-work)。 - 要配置访问共享策略,请参阅[在用户账户之间共享付费访问等级](sharing-paid-access-between-user-accounts)。 - 要以编程方式授予访问权限,请参阅 [授予访问等级](api-adapty/operations/grantAccessLevel) API 方法。 --- # File: how-profiles-work --- --- title: "用户画像的工作原理" description: "了解 Adapty 如何创建、跟踪和关联用户画像,包括匿名用户画像、已识别用户及父/继承关系。" --- 您应用中的每位用户都会获得一个 Adapty 用户画像,用于追踪其购买记录、事件和订阅状态。了解用户画像的创建和关联方式,有助于您避免集成错误、防止数据碎片化,并正确解读 [用户画像](profiles-crm) 页面中的数据。 ## 创建用户画像 \{#profile-creation\} Adapty 会在用户首次打开应用时自动创建用户画像。 **未设置 Customer User ID 时**,用户画像为匿名状态。每当以下情况发生时,系统会创建新的匿名用户画像: - 用户重新安装应用 - 用户退出应用(当应用调用 `Adapty.logout()` 时) 购买记录与应用安装绑定,而非与持久的用户身份关联。 **设置了 Customer User ID 时**,用户画像可在重装和多设备间持久保留。使用 Customer User ID 可以让你: 1. 跨应用重装和多设备追踪用户。 2. 在 [**Profiles**](profiles-crm) 页面通过 customer user ID 查找用户。 3. 在[服务端 API](getting-started-with-server-side-api) 中使用 customer user ID。 4. Adapty 会将 customer user ID 发送给所有集成渠道。 设置 customer user ID 的时机不同,用户画像的行为也会有所差异: - **在 SDK 激活时**:Adapty 会使用该 customer user ID 对应的现有用户画像(针对已有用户),或创建新的用户画像(针对首次使用的用户)。 - **在 SDK 激活后**:Adapty 在激活时创建一个匿名用户画像。当你稍后识别用户身份时,Adapty 会将 customer user ID 关联到该匿名用户画像(针对首次使用的用户),或切换到已有该 ID 的用户画像(针对已有用户)。 **如何选择:** - **应用启动时即可获取 Customer user ID**(例如从上次会话中已保存)——在初始化 SDK 时将其传入 `activate()`。 - **用户在应用启动后登录**——在身份验证完成后调用 `identify()`。若该 ID 是新 ID,Adapty 会将其关联到当前用户画像;若该 ID 已存在,则切换到对应的已有用户画像。 - **用户可在登录前购买**——在登录完成后调用 `identify()`。若该 Customer user ID 在 Adapty 中已存在,请在调用后重新获取用户画像,以同步当前的访问等级。 有关实现细节,请参阅 [用户识别](identifying-users) SDK 指南。 :::note 如果某个回访用户此前在没有 customer user ID 的情况下使用过你的应用,当你在 SDK 初始化时开始识别用户后,这些匿名用户画像不会自动合并。若需要为此类用户保留完整历史记录,请在登录后改用 `identify()`。 ::: ## 父画像与继承画像 \{#parent-and-inheritor-profiles\} 当同一个商店端订阅与多个 Adapty 用户画像关联时,Adapty 会将这些画像视为一条链:一个**父**画像,以及一个或多个从同一购买中共享访问权限的**继承**画像。 出现这种情况的原因如下: - [用户账户之间共享付费访问权限](sharing-paid-access-between-user-accounts)已启用,且某用户在一台设备上登录,而该设备上此前已有另一个用户画像完成了购买。 - 用户在未设置 `customer_user_id` 的情况下重新安装应用,新用户画像继承了上一次安装的购买记录。 - 不同的已识别用户在同一台设备上恢复了购买。 - 应用在 Apple Team ID 之间进行了迁移,新应用继承了旧 Team ID 下的购买记录。 **父级用户画像的选择方式。** **父级**是 **第一个记录购买行为的用户画像** — 由 Adapty 中的购买收据顺序决定,而非用户画像的创建顺序。例如:你安装应用后未进行任何购买,然后重新安装并购买了订阅。第二个用户画像成为父级,因为它完成了购买。第一个用户画像成为继承方,并通过共享获得访问权限。 **事件的分配方式:** - **事务性事件**(购买、续订、取消、账单问题、宽限期、退款):仅出现在发起购买的**父用户画像**上。所有订阅续订和更新都将继续显示在该用户画像上。 - **`access_level_updated` 事件**:每当访问等级状态发生变化时,**父用户画像和继承用户画像都会**收到该事件。这样可以确保所有关联的用户画像都能及时获知当前的访问状态。 父级用户画像显示完整的交易历史记录。继承者用户画像仅在 **Access level** 部分显示其访问等级更新内容以及指向父级用户画像的链接。 <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> **跨用户画像追踪同一订阅。** 每个继承者用户画像都有独立的 `profile_id`,因此 `profile_id` 在整个链路中并不稳定。若要在多个用户画像之间识别同一笔订阅——例如在核对 webhook 事件或将看板中的用户画像与同一底层用户进行匹配时——请改用渠道侧的标识符。 | 字段 | 用途 | | --- | --- | | `store_original_transaction_id` | 跨用户画像识别订阅链。每个 Apple 订阅唯一。 | | `profiles_sharing_access_level`(webhook 字段) | 启用共享时,当前享有该订阅访问等级的所有用户画像。 | | `profile_id` | **不**适合跨用户画像追踪——每个继承者都有自己的 `profile_id`。 | ## 没有用户画像的交易 \{#transactions-without-profiles\} 在 Adapty 中,有些交易没有关联任何用户画像——它们会出现在数据分析和导出结果中,但不会显示在用户画像列表里。这类情况发生在**服务器间(S2S)商店通知**中,即这些通知对应的用户从未通过 Adapty SDK 连接过你的应用。已知来源包括: - App Store S2S 通知(包括退款事件) - Google Play S2S 通知 - Stripe 和 Paddle Webhook 事件 这些交易: - **出现在分析数据图表中**(计入整体数据指标) - **出现在导出数据中**(S3、GCS、BigQuery),其中 `profile_id` 设置为 `null` - **不出现在用户画像列表中** — 因为没有关联的用户画像 如果你在分析或导出数据中看到的事件数量多于用户画像界面中能找到的数量,差异部分很可能就是这些没有关联用户画像的交易记录。要在导出数据中找到它们,可以筛选 `profile_id IS NULL` 的行。 ## 在用户账户之间共享付费访问权限 \{#sharing-paid-access-between-user-accounts\} :::link 主要文章:[在用户账户之间共享付费访问权限](sharing-paid-access-between-user-accounts) ::: 要设置您的访问等级共享策略,请在 [**General**](general) 设置页面上选择一个共享选项。您可以为[沙盒环境](test-purchases-in-sandbox)单独设置策略。 **已启用(默认)** 已识别用户(即设置了 [Customer User ID](identifying-users#set-customer-user-id-on-configuration) 的用户)在设备登录相同 Apple/Google ID 的情况下,可以共享 Adapty 提供的同一[访问等级](access-level)。这在用户重新安装应用并使用不同邮箱登录时非常有用——他们仍然可以访问之前的购买内容。使用此选项时,多个已识别用户可以共享同一访问等级。 尽管访问等级是共享的,但所有过去和未来的交易都会作为事件记录在原始 Customer User ID 下,以保持数据一致性并完整保留交易历史——包括试用期、订阅购买、续订等,均关联到同一用户画像。 **将访问权限转移给新用户** 已识别用户可以继续访问 Adapty 提供的[访问等级](access-level),即使他们使用不同的 [Customer User ID](identifying-users#set-customer-user-id-on-configuration) 登录或重新安装应用,只要设备登录的是相同的 Apple/Google ID 即可。 与上一选项不同,Adapty 会在已识别用户之间转移购买记录。这确保购买内容始终可用,但同一时间只有一个用户能拥有访问权限。例如,如果 UserA 购买了订阅,而 UserB 在同一设备上登录并恢复了交易,则 UserB 将获得该订阅的访问权限,UserA 的访问权限将被撤销。 如果其中一个用户(无论新用户还是旧用户)未被识别,Adapty 中这些用户画像之间的访问等级仍会共享。 尽管访问等级会被转移,但所有过去和未来的交易都会作为事件记录在原始 Customer User ID 下,以保持数据一致性并完整保留交易历史——包括试用期、订阅购买、续订等,均关联到同一用户画像。 切换到**将访问权限转移给新用户**后,用户画像之间的访问等级不会立即转移。每个特定访问等级的转移流程仅在 Adapty 收到来自商店的事件时触发,例如订阅续订、恢复购买或验证交易时。 **已禁用** 第一个获得访问等级的已识别用户画像将永久保留该访问等级。如果你的业务逻辑要求购买记录必须绑定到单个 Customer User ID,这是最佳选项。 请注意,访问等级在匿名用户之间仍会共享。 你可以通过[删除所有者的用户画像](https://adapty.io/docs/zh/api-adapty/operations/deleteProfile)来"解绑"购买记录。删除后,访问等级将归属于第一个声明它的用户画像,无论是匿名用户还是已识别用户。 禁用共享仅影响新用户。已在用户之间共享的订阅在禁用此选项后仍会继续共享。 :::warning Apple 和 Google 要求在用户之间共享或转移应用内购买,因为这些购买是依赖 Apple/Google ID 进行关联的。如果不启用共享,用户在重新安装应用后可能无法恢复购买。 禁用共享可能导致用户登录后无法重新获得访问权限。 我们建议仅在用户**必须先登录**才能进行购买的情况下禁用共享。否则,已识别用户可能在购买订阅后登录另一个账号,从而永久失去访问权限。 ::: ### 应该选择哪个设置?\{#which-setting-should-i-choose\} | 我的应用…… | 推荐选项 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | 没有登录系统,仅使用 Adapty 的匿名用户画像 ID。 | 使用默认选项,因为对于所有三个选项,匿名用户画像 ID 之间的访问等级始终是共享的。 | | 有可选登录系统,允许用户在创建账号之前进行购买。 | 选择**将访问权限转移给新用户**,确保未登录账号就完成购买的用户之后仍能恢复交易。 | | 要求用户在购买前创建账号,但允许购买记录关联到多个 Customer User ID。 | 选择**将访问权限转移给新用户**,确保同一时间只有一个 Customer User ID 拥有访问权限,同时允许用户使用不同 Customer User ID 登录而不丢失已付费的访问权限。 | | 要求用户在购买前创建账号,并严格规定购买记录只能绑定到单个 Customer User ID。 | 选择**已禁用**,确保交易记录永远不会在账号之间转移。 | ## 事件时间戳显示为未来日期(Apple/iOS)\{#event-timestamps-with-future-dates-appleios\} 此行为仅在 Apple App Store 中存在,Google Play 的通知系统不会提前发送事件。 用户画像和集成中的事件时间戳可能显示为未来日期,这是因为 Apple 会提前发送续订事件。 - **发生原因**:Apple 这样做是为了确保订阅在到期前自动续订,防止用户服务中断。更多详情请参阅 Apple 开发者论坛:[Server Notifications for Subscriptions](https://developer.apple.com/forums/tags/app-store-server-notifications)。 - **受影响的事件类型**:通常,此情况适用于订阅续订和试用转付费转化。这些事件可能带有未来时间戳,因为 Apple 会提前通知系统。 - **其他事件类型**:额外的应用内购买和订阅计划变更会以实际时间戳记录,因为这些事件无法提前预测。 - **对分析和事件流的影响**:这些事件只有在其时间戳过后才会出现在 **Analytics** 和 **Event Feed** 中。带有未来时间戳的事件不会显示在这两个板块中。 - **对集成的影响**:Adapty 会在收到事件后立即将其发送至集成。如果事件带有未来时间戳,Adapty 会将该事件连同未来时间戳原样发送至您的集成。 ## 后续步骤 \{#next-steps\} - 若要使用用户画像看板查找和管理用户,请参阅 [用户画像](profiles-crm)。 - 若要在您的应用中设置用户身份识别,请参阅 [识别用户](identifying-users) SDK 指南。 - 若要配置访问共享策略,请参阅 [在用户账户之间共享付费访问权限](sharing-paid-access-between-user-accounts)。 --- # File: sharing-paid-access-between-user-accounts --- --- title: "在用户账户之间共享付费访问权限" description: "在不同用户账户之间共享付费访问权限,以适应拥有多台设备或多个应用账户的用户" --- 当用户完成购买后,Adapty 会为其当前的[用户画像](identifying-users)分配新的[访问等级](access-level),该等级授权购买者访问付费内容。 如果用户重新安装应用或登录新的应用内账号,买家的用户画像可能会发生变化。为确保访问不中断,Adapty 会自动在原始用户画像与后续用户画像之间共享用户的访问等级。 这种方式适用于大多数应用。但如果您的业务逻辑有特殊需求,也可以选择限制性更强的付费访问共享策略。 打开 [General Settings](https://app.adapty.io/settings/general) 页面,设置访问等级共享策略。为方便测试,你可以仅针对[沙盒环境](#sharing-paid-access-on-sandbox)更改此设置。 <Details> :::important 如果你的应用不需要用户登录,可以忽略此设置。关联到同一应用商店账户的匿名用户画像*始终*共享其访问等级。 ::: <summary>应该选择哪种访问共享策略?(点击展开)</summary> | 我的应用…… | 最佳选项 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | 没有身份验证功能,仅使用 Adapty 的匿名用户画像 ID。 | 使用 **Enabled (default)** 设置。 | | 可以对用户进行身份验证,但允许用户在没有账户的情况下进行购买。 | 启用 **Transfer access to new user** 设置。用户将能够注册并认领匿名购买记录。 | | 要求用户在购买前创建账户,但可以将单个产品关联到多个 Customer User ID。 | 启用 **Transfer access to new user** 设置。多个账户将能够依次访问该产品。 | | 要求用户在购买前创建账户,并严格规定购买记录只能绑定到单个 Customer User ID。 | **禁用**访问等级共享。 | </Details> <img src="/assets/shared/img/sharing-paid-access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 已启用(默认) \{#enabled-default\} 此设置最适合**没有内置身份验证**的应用程序。购买完成后,与同一应用商店账户关联的所有用户画像将自动*继承*该访问等级。 * 如果用户使用新的凭证登录您的应用,他们仍可访问已购内容。 * 如果用户在恢复出厂设置后重新安装应用,他们仍可访问已购内容。 * 如果用户使用相同的应用商店账号在其他设备上安装该应用,购买内容将在所有设备上可用。即使每个应用实例拥有各自独立的客户用户画像。 ## 将访问权限转移给新用户 \{#transfer-access-to-new-user\} 此设置最适合允许**有无账号均可购买**的应用,或希望强制执行**每位用户仅限一台设备**策略的应用。 Adapty 每次限制一个客户 ID 持有购买访问权限。设备所有者可以重新安装应用、登录或退出账号,但无法同时从多个客户 ID 访问同一产品。 启用此设置后,匿名用户画像(例如,用户登出后激活的用户画像)始终继承上一个活跃 customer ID 的访问等级。这样可以防止用户之后失去访问权限。 :::warning 当你关闭默认设置并启用 **Transfer access to new user** 后,Adapty 不会立即更新现有 customer 用户画像的访问等级。 切换操作会在用户触发新的商店事件时发生:例如,续订订阅或恢复购买。 ::: :::important 只有当 SDK 传播交易时新用户画像已设置 [Customer User ID](identifying-users#set-customer-user-id-on-configuration),Adapty 才会撤销旧用户画像。如果 `restorePurchases` 在匿名用户画像上运行,旧的 Customer User ID 和新的匿名用户画像都会获得访问等级。旧用户画像会在您识别该匿名用户画像后才被撤销。 为避免此问题,请按顺序调用 SDK 方法:`activate` → `identify` → `restorePurchases`。 ::: ## 禁用付费访问共享 \{#disable-paid-access-sharing\} 此设置**仅适用于**具有**强制身份验证**或独立访问管理实现的应用程序。在其他情况下,用户可能无法访问其已购买的内容,您的应用程序将面临**无法通过商店强制审核**的风险。 如果您禁用付费访问共享,Adapty 会将产品绑定到购买时处于活跃状态的[客户 ID](identifying-users#set-customer-user-id-on-configuration),且不会与任何其他用户画像共享该访问等级。此策略实现了严格的一对一产品分配。 :::warning 禁用付费访问共享后,客户 ID 将无法继承付费访问权限。如果某个客户 ID 过去已继承了付费访问权限,则无法自动撤销。 ::: :::important 在紧急情况下,您可能需要[删除用户画像](api-adapty/operations/deleteProfile),以便下一个可用的用户画像(无论是已识别的还是匿名的)能够获得其访问等级。 ::: ## 实用参考 \{#practical-reference\} 选定合并模式后,以下规则说明了你可以期望的结果:哪些用户画像能看到该访问等级、旧用户画像何时失去访问权限,以及会触发哪些 webhook 事件。 | 模式 | 多个用户画像共享同一次购买? | 转移时吊销旧用户画像? | 何时吊销旧用户画像 | 第二个用户画像认领订阅时触发的 Webhook 事件 | | --- | --- | --- | --- | --- | | **已启用(默认)** | 是——每个通过恢复购买或登录的用户画像均可继承访问等级 | 从不 | 不适用 | 每个继承访问权限的新用户画像均触发 `access_level_updated`(`is_active=true`) | | **将访问权限转移给新用户** | 否——独占,但可在用户画像之间转移 | 是 | 新的已识别设备传播该交易时立即吊销(`restorePurchases`、identify 或下一次商店侧事件) | 新用户画像:`access_level_updated`(`is_active=true`)。旧用户画像:`access_level_updated`(`is_active=false`) | | **已禁用** | 否——每次购买永久绑定唯一一个 Customer User ID | 不适用——访问权限永不转移 | 不适用 | 第二个用户画像不触发任何事件。SDK 对该用户画像不显示任何访问权限 | ## 在沙盒环境中共享付费访问权限 \{#sharing-paid-access-on-sandbox\} 您可以专门为沙盒环境设置共享付费访问权限策略。在沙盒环境中测试购买时,请注意以下行为: * Apple 会将您过去的购买记录存储在账户的购买历史中,Adapty SDK 也可以访问这些记录。 * 如果您重新安装应用,Adapty 检测到该产品已被购买,当前用户画像将继承相应的访问等级。 * 如果 Apple 检测到该产品已有购买记录,即使当前用户画像没有所需的访问等级,也不会允许您重复购买同一产品。 此行为**与您的共享付费权限设置无关**。如果您的应用未显示付费墙,您就无法购买该产品。唯一的解决方案是**清除您账号的购买记录**。请参阅[沙盒测试指南](test-purchases-in-sandbox)获取详细说明。 :::warning 沙盒环境中的 Apple 订阅每隔几分钟就会自动续订。这种快速续订可能导致 Adapty 识别的[父级](how-profiles-work#parent-and-inheritor-profiles)用户画像发生切换——这种链式模式在正式环境中很少出现。请在与生产环境一致的模式下进行测试,并在得出结论之前,使用真实 Apple ID 验证实际行为。 ::: ## 付费访问共享在分析中的表现 \{#paid-access-sharing-in-analytics\} * Adapty 按实际发生的交易进行记录。单笔交易可能关联多个用户画像,但不会被重复计算。 * 如果两个或多个用户画像共享同一访问等级,该购买将归因于[父级用户画像](how-profiles-work#parent-and-inheritor-profiles)。 * 访问等级的继承不影响安装量统计。如需了解 Adapty 如何统计安装量,可在设置页面选择两种可用的[安装定义](installs#counting-modes)之一。 --- # File: segments --- --- title: "市场细分" description: "在 Adapty 中创建和管理用户市场细分,以实现更精准的定向投放。" --- **市场细分**是一组过滤条件,用于将具有共同属性的用户归为一类。通过市场细分,可以更精准地定向投放付费墙和 A/B 测试。 :::note 事件feed中的事件到达看板时会有延迟。新的用户画像和属性变更可能不会立即显示。 ::: 创建市场细分后,您可以[将其作为**目标受众**用于版位和 A/B 测试](audience),从而控制用户看到哪个付费墙(单个或多个)。示例: - 向非订阅用户展示标准付费墙,向曾经取消订阅或试用的用户提供折扣优惠。 - 向不同国家的用户展示不同的付费墙。 - 根据 Apple Search Ads 归因数据定向用户。 - 确保使用旧版应用的用户继续看到现有付费墙,而新版本用户看到更新后的付费墙。 - [在分析中](controls-filters-grouping-compare-proceeds#filter-and-group-data),按市场细分筛选数据,查看特定用户群体的表现;按市场细分分组,可在**所有用户**中对比各组表现或贡献占比。 <img src="/assets/shared/img/3244407-Segments.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 创建 \{#creation\} 要创建市场细分,请输入名称并选择定义其过滤条件的属性。当您选择多个属性时,用户必须满足所有条件。Adapty 在属性之间应用 AND 逻辑。 <img src="/assets/shared/img/1af9744-new_cohort.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 可用属性 \{#available-attributes\} :::note 虽然许多用户属性会自动设置(如 **Country** 或 **Calculated total revenue USD**),但 **Age**、**App user ID**、**Attribution** 数据、**Gender** 和 **Custom attributes** 不会自动定义。如需将这些属性用于市场细分,您必须[设置用户属性](setting-user-attributes)或[传递归因数据](attribution-integration)。 ::: :::tip 对于基于日期的属性,您可以使用以下方式进行筛选: - **固定日期**:从日历中选择具体日期(例如,向黑色星期五至网络星期一期间安装应用的用户展示特别优惠) - **相对范围**:设置动态时间窗口,例如"最近 7 天"或"最近 3 个月"(例如,重新触达 30 天以上未活跃的用户,或定向最近安装的用户) 相对范围会自动更新,非常适合持续性活动。固定日期则更适合有时间限制的促销活动。 ::: | 属性 | 筛选依据 | |---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Age** | 用户年龄。请注意,年龄在 Adapty 首次获取时计算,之后不会更新。 | | **App User ID** | 用户在您应用中的标识符([customer_user_id](profiles-crm#user-attributes))。您可以按其是否存在进行筛选,例如仅向未登录的用户显示付费墙。 | | **App version (current)** | 用户设备上当前安装的应用版本,即 Adapty 最后一次收到事件数据时的版本——**随用户升级而更新**,始终反映其正在运行的版本。当您需要向所有运行特定版本的用户推送内容(包括从旧版本升级至该版本的用户)时,请使用此条件。在创建市场细分时,点击 **App version** 旁的铅笔图标并添加新版本,即可立即使用。<br/> 使用 **version > X.X** 条件,无需逐一列举每个版本,即可衡量所有高于或低于特定版本的应用版本对转化的影响。<br/><br/> **格式:** 版本字符串须遵循 [SemVer](https://semver.org/) 格式。任意部分不允许前导零——`26.03.4` 不会匹配,而 `26.3.4` 会匹配。无效版本将被静默排除在市场细分之外。 | | **App version (on install)** | 用户设备上安装时的应用版本,即 Adapty 首次收到事件数据时的版本——**固定为安装时的版本,即使用户升级后也不会更新**。如需按用户最初安装的版本进行定向,而非当前版本,请使用此条件。`App version (on install) = 1.5.7` 仅匹配首次安装版本为 1.5.7 的用户,从旧版本升级至 1.5.7 的用户会被静默排除——若需同时覆盖升级用户,请改用 **App version (current)**。<br/><br/> **格式:** 版本字符串须遵循 [SemVer](https://semver.org/) 格式。任意部分不允许前导零——`26.3.04` 不会匹配,而 `26.3.4` 会匹配。无效版本将被静默排除在市场细分之外。 | | **Attribution: Ad Group** | 归因广告组。 | | **Attribution: Ad Set** | 归因广告集。 | | **Attribution: Campaign** | 营销活动名称。 | | **Attribution: Creative** | 归因素材关键词。 | | **Attribution: Channel** | 营销渠道名称。 | | **Attribution: Source** | 归因来源。 | | **Attribution: Status** | 归因状态。可选值:<ul><li> **Organic** – 用户在没有任何付费营销影响的情况下安装了应用(例如在 App Store/Google Play 中直接搜索、口碑传播或自然社交媒体触达)。</li><li> **Non-organic** – 用户通过付费营销渠道获取(例如广告、网红营销、推荐计划)。</li><li> **Unknown** – 该用户没有可用的归因数据。</li></ul> | | **Calculated subscription state** | 用户的[当前订阅状态](profiles-crm#subscription-state),表明订阅是否有效、已取消,或是否存在未解决的扣款问题。 | | **Calculated total revenue USD** | 该用户产生的总收入。 | | **Country** | 客户所在国家/地区,根据其最近一次 IP 地址确定。Adapty 最多每周刷新一次 IP 信号,因此若用户更换了位置或使用了 VPN,可能存在偏差。如需按用户的 App Store / Play Store 账户所在国家/地区进行定向,请使用 **Country from store account**。 | | **Country from store account** | 与用户 iOS 或 Android 商店账户关联的国家/地区。请注意,Adapty 仅在运行 iOS 13 及以上版本的设备上收集商店国家/地区信息。 | | **Creation date** | 用户画像的创建日期(即应用首次安装在用户设备上的日期)。 | | **Device** | 基于元数据的设备类型。例如"Samsung Galaxy"或"iPhone 13"。 | | **Gender** | 用户性别。请注意,该值由您自行设置。 | | **Installation date** | 用户安装应用的日期。 | | **Language** | 用户设备的语言。<Callout type="warning">Adapty 以 2 位 `ISO 639-1` 代码存储语言。请勿使用 `zh-Hant-TW` 或 `pt-BR` 等扩展区域设置,它们可能出现在下拉列表中,但不会匹配任何用户。</Callout> <Callout type="tip">如需进一步缩小语言定向范围,可将 **Language** 与 **Country** 组合使用。例如,**简体中文(`zh`)** + **Country = TW、HK、MO** 可定向繁体中文用户。</Callout> | | **Last seen** | 用户最后一次打开应用的日期。 | | **OS** | 用户设备的操作系统版本。 | | **Paid access level** | 授予用户的访问等级。 | | **Platform** | 用户设备平台。可选值:`iOS`、`macOS`、`iPadOS`、`visionOS`、`Android`。<br/> 如果用户从多个平台(例如 iOS 和 Android)访问您的应用,系统会使用该设备的最新数据分别评估每个平台的市场细分归属。这样即使是同一用户画像,也可以实现针对不同平台的定向。 | | **Subscription expiration date** | 订阅的到期日期,或其是否存在。永久授权商品显示为 `none`;若用户有用户画像但从未有过试用、订阅或永久授权购买,则该字段为空。 | | **Subscription product** | 客户当前有效订阅的最新产品 ID。 | | **[Custom attributes](profiles-crm#custom-attributes)** | 自定义属性,根据应用或业务特有的属性创建高度精准的市场细分。 | ## 自定义属性 \{#custom-attributes\} 通过定义自定义属性,你可以基于应用或业务特有的属性构建更精准的市场细分。 :::note - 你可以在移动端 SDK 或 Adapty 看板中设置自定义属性。有关 SDK 设置方法,请参阅[此处](setting-user-attributes#custom-user-attributes)的说明。 - 如果某个自定义属性已被用于市场细分,修改该属性后,相关用户在[数据分析](controls-filters-grouping-compare-proceeds#filter-and-group-data)中可能不再符合该细分条件。数据将反映修改前的旧值。 ::: ### 如何配置自定义属性 \{#how-to-configure-a-custom-attribute\} 在 Adapty 看板中,从属性下拉菜单中选择 **Create custom attributes**。 <img src="/assets/shared/img/883d3b2-CleanShot_2023-03-16_at_17.20.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | 字段 | 说明 | | ------ |--------------------------------------------------------------------------------------------------------------------------------------| | **Name** | 自定义属性的标签,仅在 Adapty 看板中显示。 | | **Key** | 属性的唯一标识符,必须与 SDK 中使用的键名一致。 | | **Type** | 可选类型:<ul><li>String:需要预定义一组可选值。</li><li>Number:仅接受数字值。</li></ul> | | **Values** | 选择 `String` 时,输入可选值列表;选择 `Number` 时,该属性仅接受数字输入。数字属性支持小数,并可与比较运算符配合使用。 | 填写完必填字段后,您就可以在市场细分、[A/B 测试](ab-tests)等功能中使用自定义属性了。 每个用户画像最多可设置 30 个自定义属性。 ## 总数量和随机样本 \{#total-number-and-random-sample\} 创建市场细分后,Adapty 会显示符合该市场细分条件的用户总数。 Adapty 还会显示 40 名符合条件的随机用户样本。使用它来测试您的市场细分,确保配置正确。 <img src="/assets/shared/img/segment-random-set.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 复制市场细分 \{#duplicate-segments\} 如果你需要创建一个与现有市场细分相似的新细分,直接复制即可,无需从头构建。对于同时运行多个营销活动或 A/B 测试、且用户群体存在重叠的团队来说,这能节省不少时间。 复制市场细分会创建一个包含所有筛选条件和描述的副本。新细分的名称会自动添加"(copy)"后缀,便于与原始细分区分。新细分与原始细分相互独立,修改其中一个不会影响另一个。 在 Adapty 看板中复制市场细分的步骤如下: 1. 在 Adapty 主菜单中打开 **Profiles & Segments** 部分,切换到 [**Segments**](https://app.adapty.io/segments) 标签页。 2. 点击市场细分旁边的 **3-dot** 按钮,选择 **Duplicate**。 <img src="/assets/shared/img/duplicate-segment.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 打开新的市场细分,根据需要调整其筛选条件。 ## 删除市场细分 \{#delete-segments\} 当某个市场细分不再需要时,你可以将其永久删除。 如果该市场细分正被以下任一项用作目标受众,Adapty 将阻止删除操作: - **版位**:至少有一个未删除的版位将该市场细分用作其目标受众。 - **A/B 测试(进行中或已完成)**:至少有一个未删除的 A/B 测试将该市场细分用作其目标受众。 对于市场细分的删除,Adapty 将 **Live** 和 **Completed** 状态的 A/B 测试均视为活跃状态。已完成的测试仍会使用该目标受众向匹配用户展示测试结束后的付费墙或用户引导,且该测试的历史数据图表也限定在该市场细分的范围内。只有在 A/B 测试本身被删除后,市场细分才会被释放。 :::warning 市场细分的删除是永久性的,无法恢复。 ::: 在 Adapty 看板中删除市场细分的步骤如下: 1. 前往 Adapty 主菜单中的 **Profiles & Segments**,切换到 [**Segments**](https://app.adapty.io/segments) 标签页。 2. 点击该市场细分旁边的 **3-dot** 按钮,选择 **Delete**。 3. 在确认输入框中输入市场细分名称,然后点击 **Delete forever**。 :::info 如果该市场细分正在被使用,对话框会列出引用它的版位和 A/B 测试。 要解除删除限制,请从列表中打开每个版位或 A/B 测试,然后将该市场细分从其目标受众中移除,或者直接删除对应的版位或 A/B 测试。待没有任何内容引用该市场细分后,即可将其删除。 ::: --- # File: event-feed --- --- title: "事件流" description: "通过 Adapty 的事件流监控和分析用户活动。" --- 事件流让你可以直观地追踪 Adapty 生成的[事件](events),并查看其导出到第三方集成(包括 webhook)的状态。 :::warning 事件流不显示以下内容: - **服务端 API v1 的交易记录**:使用 [服务端 API(第 1 版)](server-side-api-specs-legacy#requests) 创建的交易。请改用 [服务端 API(第 2 版)](api-adapty/operations/setTransaction) 以使其出现在事件流中。 - **没有用户画像的事件**:在 SDK 识别用户之前到达的交易(例如商店服务器通知)。若要将其包含在导出中,请在 [S3](s3-exports) 或 [Google Cloud Storage](google-cloud-storage) 集成中启用 **Include events without profile**。 ::: <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note AppsFlyer、Facebook Ads 和 Branch 的发送状态可能不准确,因为它们并不总是在发生错误时返回错误信息。 ::: 要查看发起该交易的用户画像,请点击事件详情中的 **View Profile** 按钮。 --- # File: ab-tests --- --- title: "A/B 测试" description: "通过 Adapty 的 A/B 测试优化订阅定价,提升转化率。" --- :::tip 你无需自行研究,就能获得可落地的 A/B 测试方案。[AI 增长顾问](autopilot) 会审查你的付费墙、对标竞品,并基于 Adapty 追踪的 20,000 款订阅应用的匿名数据为你生成建议。 ::: 通过在 Adapty 中运行 A/B 测试来提升应用收益。对比不同的用户流程、付费墙和用户引导,找出转化效果最佳的方案——无需修改代码。例如,你可以测试: - 订阅价格 - 付费墙的设计、文案和布局 - 试用期时长与订阅周期 - 用户引导界面设计 ## 前提条件 \{#prerequisites\} 在设置 A/B 测试之前,您需要准备: - **版位**:一个或多个用于展示流程、付费墙或用户引导的[版位](placements)。 - **流程**:至少两个[流程](adapty-flow-builder)。 - **付费墙**:至少两个[付费墙](paywalls)。 - **用户引导**:至少两个[用户引导](onboardings)。 :::warning 如果您没有使用 [Adapty Flow 编辑工具](adapty-flow-builder)或 [Adapty 付费墙编辑工具](adapty-paywall-builder),请通过 `.logShowFlow()`(iOS SDK v4+)/ `.logShowPaywall()` [向 Adapty 上报付费墙展示事件](present-remote-config-paywalls#track-paywall-view-events)。若未调用此方法,Adapty 将无法统计测试中的付费墙展示次数,转化数据也会不准确。 ::: ## A/B 测试类型 \{#ab-test-types\} Adapty 支持两种主要的 A/B 测试类型: - **常规测试**:在单个流程/付费墙/用户引导版位上运行。 - **跨版位测试**:跨多个付费墙版位运行,向同一用户在所有版位展示相同的实验变体。目前仅支持付费墙。 如需了解各类型的完整对比、使用场景及优先级规则,请参阅 [A/B 测试类型](ab-test-types)。 ## 后续步骤 \{#next-steps\} - [AI 增长顾问](autopilot) — 分析你的付费墙,获取市场洞察,并生成 A/B 测试方案 - [A/B 测试类型](ab-test-types) — 了解各种测试类型及其适用场景 - [创建、运行和停止 A/B 测试](run_stop_ab_tests) — 配置并运行你的第一个测试 - [A/B 测试结果与数据图表](results-and-metrics) — 解读 A/B 测试数据并选出胜出实验变体 --- # File: ab-test-types --- --- title: "A/B 测试类型" description: "了解 Adapty 中的 A/B 测试类型。" --- Adapty 提供两种 A/B 测试类型,分别适用于不同的测试场景: - **常规 A/B 测试:** 针对单个[流程](adapty-flow-builder)/[付费墙](paywalls)/[用户引导](onboardings)版位创建的 A/B 测试。 - **跨版位 A/B 测试:** 针对应用中多个付费墙版位创建的 A/B 测试。一旦 A/B 测试分配了<InlineTooltip tooltip="实验变体">A/B 测试的实验变体是用于测试的流程、付费墙或用户引导的不同版本。</InlineTooltip>,该实验变体将在所有选定的应用页面中保持一致展示。 :::warning 跨版位 A/B 测试仅支持 Adapty SDK v3.5.0 及以上版本,且仅适用于付费墙。 Flow A/B 测试需要 Adapty SDK v4.0.0 及以上版本。 用户引导 A/B 测试需要 Adapty SDK v3.8.0+(iOS、Android、React Native、Flutter)、v3.14.0+(Unity)或 v3.15.0+(Kotlin Multiplatform、Capacitor)。 使用旧版本的用户将跳过这些测试。 ::: 每个 flow/付费墙/用户引导都会分配一个权重,用于在测试期间分配流量。 例如,权重分别为 70% 和 30% 时,约 700 个用户会看到第一个付费墙,约 300 个用户会看到第二个付费墙。在跨版位测试中,权重按实验变体设置,而非按付费墙设置。 通过这种方式,你可以对比不同的流程和付费墙,为应用的变现策略做出数据驱动的决策。 ## 何时使用各类型 \{#when-to-use-each-type\} 每种 A/B 测试类型的适用场景如下: - **常规 A/B 测试**: - 你的应用中只有一个版位。 - 你只想在某一个版位上运行 A/B 测试,并仅追踪该版位的收益变化,即使应用中有多个版位也是如此。 - 你想对老用户(即至少看过一次 Adapty 付费墙的用户)运行 A/B 测试。 - **跨版位 A/B 测试**: - 你想在多个版位之间同步实验变体。例如,同时修改用户引导流程和应用设置页中的价格。 - 你想评估应用的整体收益表现。在所有版位上运行测试,比只测试单个版位更便于分析 A/B 测试数据。 - 你只想对新用户(即从未看过任何 Adapty 付费墙的用户)运行 A/B 测试。 - 你想在单个实验变体中使用多个付费墙: <img src="/assets/shared/img/ab-test-variants.png" alt="单个跨版位 A/B 测试实验变体中多个付费墙的示例" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 主要区别 \{#key-differences\} | 功能 | 普通 A/B 测试 | 跨版位 A/B 测试 | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | **测试对象** | 单个流程/付费墙/用户引导 | 同一实验变体下的一组付费墙 | | **实验变体一致性** | 每个版位独立决定实验变体 | 所有付费墙版位使用相同的实验变体 | | **目标受众设置** | 按流程/付费墙/用户引导版位分别定义 | 在所有付费墙版位间共享 | | **数据分析** | 分析单个流程/付费墙/用户引导版位 | 分析测试所涉及的所有版位的整体应用表现 | | **实验变体流量分配** | 按流程/付费墙/用户引导分别设置 | 按一组付费墙整体设置 | | **适用用户** | 所有用户 | 仅限新用户(从未看过 Adapty 付费墙的用户) | | **Adapty SDK 版本要求** | 流程:v4.0.0+;付费墙:不限版本;用户引导:v3.8.0+(iOS、Android、React Native、Flutter),v3.14.0+(Unity),v3.15.0+(KMP、Capacitor) | 3.5.0+ | | **最适合** | 在不考虑整体应用经济的情况下,测试单个流程/付费墙/用户引导版位的独立变更 | 在全应用范围内评估整体变现策略 | ## A/B 测试选择逻辑 \{#ab-test-selection-logic\} **跨版位 A/B 测试的优先级高于普通 A/B 测试。** 但跨版位测试仅面向**新用户**展示——即从未看过任何 Adapty 付费墙的用户(从未为其调用过 `getPaywall` SDK 方法)。这确保了跨版位结果的一致性。 下图展示了 Adapty 为某个版位选择 A/B 测试时所使用的逻辑: <img src="/assets/shared/img/ab-tests-scheme.webp" alt="Diagram showing the A/B test selection logic for a paywall placement" style={{ border: '1px solid #727272', /* border width and color */ width: '350px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在 **A/B Tests** 页面中,付费墙、用户引导、流程和跨版位测试分别显示在不同的标签页中。 <img src="ab-tests-tabs.webp" alt="A/B 测试列表页面,包含常规、用户引导和跨版位测试类型的标签" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 跨版位 A/B 测试的限制 \{#crossplacement-ab-test-limitations\} :::warning 跨版位 A/B 测试不能包含流程或用户引导类版位。 ::: 跨版位 A/B 测试保证每位用户在测试涉及的所有版位中看到相同的实验变体。这带来以下限制: * 只有新用户才能参与。新用户是指从未看过 Adapty 付费墙、且其应用从未调用过 `getPaywall` 的用户。Adapty 无法为其他用户保证一致的付费墙链路。 * 用户遇到的第一个版位决定了 Adapty 展示哪个付费墙。你无法更改用户的分配,也无法将同一用户加入多个跨版位 A/B 测试。 :::warning 用户一旦收到跨版位付费墙,即便你已停止测试,该用户在 90 天内仍会看到同一付费墙。如需调整此时长,请在 **General** 设置中修改 **[Cross-placement variation stickiness](general#9-cross-placement-variation-stickiness)**。 ::: ## 跨版位 A/B 测试优先级 \{#crossplacement-ab-test-priority\} * 跨版位 A/B 测试始终优先于常规 A/B 测试和用户引导 A/B 测试。如果新用户同时符合跨版位测试和同一版位的常规测试条件,系统将展示跨版位测试。 * 当多个面向相同目标受众的跨版位 A/B 测试共享同一版位时,Adapty 会根据测试的添加顺序自动分配优先级,最先添加的测试优先级最高,且无法手动调整。 * 针对较小目标受众群体的测试会自动优先于针对所有用户群体的测试。 :::note 在 Analytics 中,跨版位 A/B 测试会显示为多个子测试,每个版位对应一个。子测试的命名格式为 `<test-name> child-0`、`<test-name> child-1`,以此类推。编号与 A/B 测试详情页面上的版位顺序一致。如需查看特定版位的结果,请按 **Placement** 筛选。 ::: ## 下一步 \{#next-steps\} - [创建、运行和停止 A/B 测试](run_stop_ab_tests) — 设置并启动您的第一个测试 - [A/B 测试结果与数据图表](results-and-metrics) — 分析性能并选出优胜方案 --- # File: run_stop_ab_tests --- --- title: "创建、运行和停止 A/B 测试" description: "在 Adapty 中创建、运行和停止 A/B 测试的分步指南。" --- 本文介绍 Adapty 中 A/B 测试的完整生命周期:创建测试、运行测试,以及在准备好查看结果时停止测试。 ## 前提条件 \{#prerequisites\} 在设置 A/B 测试之前,你需要准备: - 至少两个已创建的[流程](adapty-flow-builder)/[付费墙](paywalls)/[用户引导](onboardings) - 在应用中配置好的[版位](placements) :::warning 如果你没有使用 [Adapty Flow builder](adapty-flow-builder) 或 [Adapty 付费墙编辑工具](adapty-paywall-builder),请通过 `.logShowPaywall()` [将付费墙展示事件发送至 Adapty](present-remote-config-paywalls#track-paywall-view-events)。若未调用该方法,Adapty 将无法统计测试中的付费墙展示次数,导致转化数据不准确。 ::: :::info Adapty 中的 A/B 测试分两步完成。你先创建测试并保存为草稿——不会立即上线。准备好后再单独运行。这样你可以在用户看到之前检查配置是否正确。 ::: ## 创建 A/B 测试 \{#create-an-ab-test\} 创建新的 A/B 测试时,至少需要包含两个[流程](adapty-flow-builder)/[付费墙](paywalls)/[用户引导](onboardings)。 创建新的 A/B 测试: 1. 从 Adapty 主菜单进入 [A/B tests](ab-tests)。 <img src="/assets/shared/img/go-to-abtests.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在右上角,点击 **Create A/B test**。 <img src="/assets/shared/img/create-abtest.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Create the A/B test** 窗口中,输入 **Test name**。此项为必填。请选择一个能清晰描述测试内容的名称,以便在查看结果时快速识别。 4. 填写 **Test goal**,说明您希望达成的目标(例如提升订阅量或降低流失率)。 5. 点击 **Select placement**,选择一个流程、付费墙或用户引导版位。 6. 在 **Variants** 表格中配置测试内容。每一行代表一个实验变体,每一列代表一个版位。在每个交叉处添加一个付费墙。 默认情况下,表格包含 2 个实验变体和 1 个版位。你最多可以添加 20 个实验变体。添加第二个版位后,测试将变为跨版位 A/B 测试。请注意,跨版位 A/B 测试仅适用于付费墙。 <img src="/assets/shared/img/abtest-variants.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 7. 保存测试。你有两种选择: 1. **Save as draft**:测试不会立即上线。你可以稍后从版位或 A/B 测试列表中启动它。在正式启动前,可以用这个选项检查配置是否正确。 2. **Run A/B test**:立即启动测试。点击该按钮后,测试将立刻上线。 保存为草稿后,继续参阅[运行 A/B 测试](#run-an-ab-test)。 ## 编辑 A/B 测试 \{#edit-an-ab-test\} 您只能编辑已保存为草稿的 A/B 测试。一旦测试生效,就无法更改。要更新正在运行的测试,请使用 **Modify** 选项——这会创建一个同名副本,您可以在其中进行更改。Adapty 会停止原始测试,原始版本和修改版本将分别出现在您的数据分析中。 ## 运行 A/B 测试 \{#run-an-ab-test\} 在 Adapty 中运行 A/B 测试,意味着将其分配到某个版位,从而开始向用户展示付费墙和用户引导。 1. 从 Adapty 主菜单进入 [A/B tests](ab-tests) 板块。 2. 确认你正在查看正确的列表——**Paywall**、**Flow**、**Onboardings** 和 **Crossplacement** A/B 测试分别显示在不同标签页中,可以切换查看。 3. 切换到 **Drafts** 标签页。只有草稿状态的测试才能启动。 4. 在您要启动的测试旁边,点击 **Run A/B test**。 5. **编辑 A/B 测试**窗口随即打开。请检查配置,并在此时完成最终调整。如果版位或目标受众尚未填写,请在此添加。 6. 确认配置无误后,点击 **Run A/B test** 开始运行。 测试启动后,你可以在 [A/B 测试结果与数据图表](results-and-metrics) 页面跟踪测试进度并查看性能数据。 ## 停止 A/B 测试 \{#stop-an-ab-test\} 停止 A/B 测试后,测试将结束,你可以查看结果。同时,你还需要决定测试结束后在相关版位中向用户展示哪个付费墙。 <img src="/assets/shared/img/stop-ab-test.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. 打开 [A/B tests](https://app.adapty.io/ab-tests) 页面,切换到 **Live** 标签页。 2. 在要停止的测试旁边,点击三点菜单,然后选择 **Stop A/B test**。 3. 在 **Stop the A/B test** 窗口中,决定测试结束后的处理方式。你有三个选项: | 选项 | 描述 | |----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 展示某个已测试的付费墙/用户引导 | 根据收入、最优概率(**P2BB**)和每千用户收入等测试结果,选择胜出的付费墙或用户引导。所选版位和目标受众将展示该付费墙或用户引导。 | | 选择不参与 A/B 测试的付费墙/用户引导 | 选择任意不属于当前 A/B 测试的付费墙或用户引导。当所有测试实验变体均未达到预期目标时,可使用此选项。 | | 不指定任何付费墙/用户引导 | A/B 测试结束后,所选版位和目标受众不会指定特定的付费墙或用户引导,而是根据目标受众优先级展示下一个可用的付费墙或用户引导。如果您希望由现有配置自动决定展示哪个付费墙或用户引导,而无需手动选择,这是一个不错的选择。 | :::note 停止 A/B 测试是不可逆操作——测试一旦停止便无法重新启动。请确保在决定停止之前已收集了足够的数据。 ::: 4. 点击 **Stop and complete this A/B test** 按钮。 A/B 测试结束后,它将不再处于活跃状态,其中的付费墙或用户引导也不再向新用户展示。 您仍可在 [A/B 测试数据图表页面](results-and-metrics#metrics-controls)查看测试结果与数据图表,了解测试期间参与用户的表现。随着新的购买或收入事件归因到这些用户,数据图表可能会持续更新。 --- # File: ab-test-no-paywall-variants --- --- title: "添加不含流程或付费墙的 A/B 测试实验变体" description: "运行一个 A/B 测试,其中一个实验变体跳过流程或付费墙,使用远程配置标志控制是否展示。" --- 你可以通过运行一个包含空实验变体的 A/B 测试来衡量流程或付费墙的影响。一个实验变体展示你的流程/付费墙,另一个什么都不显示。你的应用通过读取远程配置中的标志来决定是否渲染。 ## 工作原理 \{#how-it-works\} 该设置在同一版位中使用两个流程/付费墙: - **流程/付费墙 A**:你想要测试的流程或付费墙,其远程配置中 `show_paywall` 设置为 `true`。 - **流程/付费墙 B**:一个空的流程或付费墙,其远程配置中 `show_paywall` 设置为 `false`。 当 SDK 返回流程或付费墙时,你的应用会读取 `show_paywall` 标志。如果标志为 `true`,应用正常渲染;如果为 `false`,应用跳过渲染,用户无需看到任何内容即可继续。 ## 1. 在远程配置中添加 show_paywall 标志 \{#1-add-the-show_paywall-flag-in-remote-config\} 在同一个版位中需要两个流程或付费墙:流程/付费墙 A(需要测试的那个)和流程/付费墙 B(一个空的)。为每个流程/付费墙在其远程配置中添加一个 `show_paywall` 字段,这样你的应用就可以用同一个键名对两个实验变体进行分支处理。 为流程/付费墙 A 添加该标志: 1. 在 Adapty 主菜单中打开 [**Flows**](https://app.adapty.io/flows)/[**Paywalls**](https://app.adapty.io/paywalls) 部分,选择 Flow/Paywall A。 2. 打开 **Remote config** 部分。 3. 创建一个名为 `show_paywall`、值为 `true` 的字段。在 **JSON** 视图中,该条目如下所示: ```json showLineNumbers { "show_paywall": true } ``` 4. 保存更改。 对 Flow/Paywall B 重复上述步骤,但将 `show_paywall` 设置为 `false`。 有关远程配置的完整详情,请参阅[使用远程配置自定义流程](customize-flow-with-remote-config)或[使用远程配置设计付费墙](customize-paywall-with-remote-config)。 :::tip 在两个实验变体上都设置 `show_paywall`,可以让两组的代码路径保持一致,也便于后续扩展更多实验变体。 ::: ## 2. 设置 A/B 测试 \{#set-up-the-ab-test\} 1. 在版位上[创建 A/B 测试](run_stop_ab_tests),并将两个流程/付费墙作为实验变体添加进去。 2. 设置实验变体的流量权重,以便在看到流程/付费墙的用户与未看到的用户之间分配流量。 ## 3. 在应用中检查标志 \{#check-the-flag-in-your-app\} 从 SDK 返回的远程配置中读取 `show_paywall`。如果该标志为 `false`,则跳过渲染,让用户继续操作。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let showPaywall = config?.dictionary?["show_paywall"] as? Bool ?? true if showPaywall { // render the flow or paywall } } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android"> ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } } is AdaptyResult.Error -> { // handle the error } } } ``` </TabItem> <TabItem value="react-native" label="React Native"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const showPaywall = paywall.remoteConfig?.data?.["show_paywall"] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID"); final bool showPaywall = paywall.remoteConfig?.dictionary?['show_paywall'] as bool? ?? true; if (showPaywall) { // Render the paywall } } on AdaptyError catch (adaptyError) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity"> ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } var showPaywall = paywall.RemoteConfig?.Dictionary?["show_paywall"] as bool? ?? true; if (showPaywall) { // Render the paywall } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID" ).onSuccess { paywall -> val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } }.onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' }); const showPaywall = paywall.remoteConfig?.data?.['show_paywall'] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> </Tabs> 默认值 `true` 确保在缺少该标志时流程/付费墙仍保持可见,因此不含该标志的现有流程/付费墙不受影响。 :::important 如果你自行渲染付费墙(不使用 [Flow Builder](adapty-flow-builder) 或[付费墙编辑工具](adapty-paywall-builder)),请在展示 Flow/付费墙 A 时调用 [`logShowFlow`(iOS SDK v4+)/ `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events)。否则,Adapty 将无法统计测试中的展示次数。请勿为 Flow/付费墙 B 记录展示,因为它从未被展示给用户。 ::: ## 下一步 \{#next-steps\} - [创建、运行和停止 A/B 测试](run_stop_ab_tests) — 设置包含两个实验变体的测试 - [A/B 测试结果与数据图表](results-and-metrics) — 将空白实验变体与你的流程/付费墙进行对比 --- # File: results-and-metrics --- --- title: "A/B 测试结果与数据图表" description: "在 Adapty 中分析结果和关键数据图表,以提升应用的订阅表现和用户参与度。" --- 从我们的 [A/B 测试](ab-tests)中发现重要数据和洞察,比较不同的付费墙和用户引导,了解它们如何影响用户行为、参与度和转化率。通过查看这里的数据图表和结果,您可以做出明智的决策并提升应用性能。深入分析数据,找到可落地的洞察,助力应用取得更大成功。 ## A/B 测试结果 \{#ab-test-results\} 以下是 Adapty 为 A/B 测试结果提供的三个数据图表: <img src="/assets/shared/img/ab-test-results.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: **收入**:该数据图表显示从购买和续订中产生的总收入(以美元计),已扣除退款金额。它同时包含首次购买和后续订阅续订的金额。通过收入数据,你可以了解每个 A/B 测试实验变体的财务表现,并找出哪个实验变体带来的收益最高。 了解更多关于[付费墙](paywall-metrics)数据图表的信息。 **成为最优项的概率**:Adapty 采用严谨的数学分析框架对 A/B 测试结果进行分析,并提供一项名为"成为最优项的概率"的数据图表。该指标评估某个特定实验变体在所有测试变体中表现最佳(从长期收入角度)的可能性,以 1% 至 100% 的百分比形式呈现。关于 Adapty 如何计算该指标的详细信息,请参阅[文档](maths-behind-it)。表现最佳的选项(以每千用户收入为衡量依据)会以绿色高亮显示,并自动设置为默认选项。 **每千用户收益**:每千用户收益数据图表用于计算每个 A/B 测试实验变体在每 1,000 名用户中产生的平均收益。该指标帮助你了解各实验变体的收益效率,而无需关注用户总量。通过统一的标准化维度,你可以比较不同实验变体的表现,并根据收益产生效率做出明智的决策。 **每千用户收入的趋势预测区间**:每千用户收入数据图表还包含趋势预测区间。趋势预测区间表示根据现有数据和统计分析,某一实验变体的真实每千用户收入预计落入的范围。 在 A/B 测试中,分析不同实验变体产生的收入时,我们会计算每个实验变体每 1,000 名用户的平均收入。由于不同用户的收入可能存在差异,预测区间能够清晰地反映每 1,000 名用户收入的合理取值范围,同时充分考虑了预测过程中的变异性和不确定性。 通过将预测区间纳入每千用户收入指标,Adapty 让你能够在考虑潜在收入结果范围的同时,评估 A/B 测试各实验变体的收入效率。这些信息帮助你做出数据驱动的决策,并有效优化订阅策略——同时充分考虑预测过程中的不确定性以及每千用户收入的合理取值范围。 通过分析 Adapty 提供的这些数据图表,您可以深入了解 A/B 测试各实验变体的财务表现、统计显著性和收益效率,从而做出基于数据的决策,有效优化订阅策略。 ## A/B 测试数据图表 \{#ab-test-metrics\} Adapty 提供了一套全面的数据图表,帮助你有效衡量付费墙或用户引导实验变体的 A/B 测试效果。这些数据图表会实时持续更新,但展示次数除外——展示次数为定期更新。深入了解这些数据图表,有助于你评估不同实验变体的效果,并基于数据做出决策,优化付费墙或用户引导策略。 在 A/B 测试列表页面,你可以查看所有 A/B 测试的数据图表,快速了解各测试的整体表现。该视图汇总了每个实验变体的关键指标,方便你对比各变体的表现并发现显著差异。如需更深入地分析某个 A/B 测试,可进入该测试的详情页查看详细数据图表。详情页针对所选 A/B 测试提供专项深度指标,帮助你深入了解各实验变体的具体表现。 除浏览量外,所有数据图表均归因于付费墙或用户引导中的产品。 ## 数据图表控件 \{#metrics-controls\} 系统根据所选时间段显示数据图表,并按左侧列参数以三级缩进方式进行组织。 ### 用户画像安装日期筛选 \{#profile-install-date-filtration\} <img src="/assets/shared/img/2bf4d9f-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **按安装日期筛选数据图表**复选框可根据用户画像的安装日期来筛选数据图表,而非默认的以试用/购买日期(针对交易)或查看日期(针对付费墙或用户引导浏览量)进行筛选。勾选此复选框后,您可以将数据图表与用户画像安装日期对齐,从而专注于衡量特定时期的用户获取效果。此选项适用于根据具体需求自定义数据图表分析。 ### 时间范围 \{#time-ranges\} 您可以从多种时间段中选择来分析数据图表数据,支持按天、周、月或自定义日期范围等特定时长进行聚焦分析。 <img src="/assets/shared/img/ab-test-time-ranges.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 可用的筛选与分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[数据分析控件](controls-filters-grouping-compare-proceeds) ::: Adapty 提供了强大的筛选和自定义数据图表分析工具,满足您的各种需求。在 Adapty 的数据图表页面,您可以使用多种时间范围、分组选项和筛选条件。 - ✅ 筛选依据:目标受众、归因、国家、付费墙、付费墙状态、付费墙分组、用户引导、版位、国家、商店、产品及产品商店。 - ✅ 分组依据:产品和商店。 :::note 按 A/B 测试筛选时,跨版位 A/B 测试会以独立的子测试形式显示(例如 `My test child-0`、`My test child-1`),每个版位对应一个子测试。详情请参阅[跨版位 A/B 测试的限制](ab-test-types#crossplacement-ab-test-limitations)。 ::: ## 单项数据图表 \{#single-metrics-chart\} 付费墙或用户引导数据图表页面的核心组件之一是图表区域,它以可视化方式呈现所选数据图表,便于快速分析。 <img src="/assets/shared/img/e6b0674-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在 A/B 测试数据图表页面的图表区域,有一个水平条形图,直观地展示所选数据图表的各项数值。图表中每条柱子对应一个数据图表值,其长度与数值大小成比例,让你一眼就能看懂数据。横轴表示所分析的时间范围,纵轴显示各数据图表的具体数值。所有数据图表值的汇总结果显示在图表旁边。 此外,点击数据图表区域右上角的箭头图标可以展开视图,在完整的折线图中显示所选数据图表。 ## A/B 测试摘要 \{#ab-test-summary\} 在单项数据图表旁边,会显示 A/B 测试详情摘要区域,其中包含有关 A/B 测试的状态、持续时间、版位及其他相关详情信息。 <img src="/assets/shared/img/90fa3f5-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 数据图表定义 \{#metrics-definitions\} 以下是 A/B 测试可用的关键数据图表: <img src="/assets/shared/img/30c7b68-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 收入 \{#revenue\} 收入是指 A/B 测试产生的所有购买和续订所带来的美元总金额,包括首次购买和后续订阅续订。该数据图表在扣除 App Store 或 Play Store 佣金之前计算。 了解更多关于[付费墙](paywall-metrics#revenue)收入数据图表的信息。 ### CR to purchases \{#cr-to-purchases\} 购买转化率衡量您的 A/B 测试将浏览转化为实际购买的效果。计算方式为购买次数除以浏览次数。例如,若有 10 次购买和 100 次浏览,购买转化率为 10%。 ### CR trials \{#cr-trials\} 试用转化率(CR)是从 A/B 测试中启动的试用次数除以浏览次数。试用转化率衡量您的 A/B 测试将浏览转化为试用激活的效果。计算方式为启动的试用次数除以浏览次数。 ### Purchases \{#purchases\} Purchases 数据图表表示 A/B 测试在付费墙或用户引导中产生的交易总数。包含以下类型的购买: - 新增购买。 - 已激活试用的试用转化。 - 订阅的降级、升级和跨级。 - 订阅恢复(例如,订阅在未自动续订的情况下过期后被恢复)。 请注意,续订不包含在 Purchases 数据图表中。 ### Trials \{#trials\} Trials 数据图表表示 A/B 测试中激活的试用总数。 ### Trials cancelled \{#trials-cancelled\} Trials cancelled 数据图表表示已关闭自动续订的试用数量。当用户手动取消试用订阅时即发生此情况。 ### Refunds \{#refunds\} A/B 测试的 Refunds 表示与测试变体相关的退款购买和订阅数量。 ### Views \{#views\} Views 是 A/B 测试所包含的付费墙或用户引导的浏览次数。如果用户访问两次,则计为两次浏览。 ### Unique views \{#unique-views\} Unique views 是付费墙或用户引导的唯一浏览次数。如果用户访问两次,则计为一次唯一浏览。 ### Probability to be the best \{#probability-to-be-the-best\} Probability to be the best 数据图表量化了 A/B 测试中某一特定实验变体在所有测试付费墙或用户引导中成为表现最佳选项的可能性。它提供一个数值概率,表示每个付费墙或用户引导的相对表现。该数据图表以百分比表示,范围为 1% 至 100%。 ### ARPU(每用户平均收入)\{#arpu-average-revenue-per-user\} 仅适用于用户引导 A/B 测试。衡量特定时期内每位用户产生的平均收入。计算方式为总收入除以唯一用户数。 ### ARPPU(每付费用户平均收入)\{#arppu-average-revenue-per-paying-user\} ARPPU 是 A/B 测试产生的每付费用户平均收入。计算方式为总收入除以唯一付费用户数。例如,若您从 1,000 名付费用户中获得了 $15,000 的收入,则 ARPPU 为 $15。 ### ARPAS(每活跃订阅者平均收入)\{#arpas-average-revenue-per-active-subscriber\} ARPAS 是一项数据图表,用于衡量运行 A/B 测试期间每位活跃订阅者产生的平均收入。计算方式为总收入除以已激活试用或订阅的订阅者数量。例如,若总收入为 $5,000,订阅者数量为 1,000,则 ARPAS 为 $5。此数据图表有助于评估每位订阅者的平均变现潜力。 ### Proceeds \{#proceeds\} A/B 测试的 Proceeds 数据图表表示应用所有者从购买和续订中实际收到的 USD 金额,已扣除适用的 App Store / Play Store 佣金。它反映与 A/B 测试中测试变体相关的净收入,直接贡献于应用的收益。有关 Proceeds 计算方式的更多信息,请参阅 Adapty [文档。](analytics-cohorts#revenue-vs-proceeds) ### Unique subscribers \{#unique-subscribers\} Unique subscribers 数据图表表示通过 A/B 测试变体订阅或激活试用的不同用户数量。无论每位订阅者发起多少次订阅或试用,均只计算一次。 ### Unique paid subscribers \{#unique-paid-subscribers\} Unique paid subscribers 数据图表表示通过 A/B 测试变体成功完成购买并成为付费订阅者的唯一用户数量。 ### Refund rate \{#refund-rate\} A/B 测试的退款率计算方式为:与测试变体相关的退款次数除以首次购买次数(不含续订)。例如,若有 5 次退款和 1,000 次首次购买,退款率为 0.5%。 ### Unique CR purchases \{#unique-cr-purchases\} A/B 测试的唯一购买转化率计算方式为:与测试变体相关的购买次数除以唯一浏览次数。例如,若有 10 次购买和 100 次唯一浏览,唯一购买转化率为 10%。 ### Unique CR trials \{#unique-cr-trials\} A/B 测试的唯一试用转化率计算方式为:与测试变体相关的启动试用次数除以唯一浏览次数。例如,若有 30 次启动试用和 100 次唯一浏览,唯一试用转化率为 30%。 ### Completions & unique completions \{#completions--unique-completions\} 仅适用于用户引导 A/B 测试。Completions 统计用户通过 A/B 测试变体完成用户引导的次数,即从第一屏到最后一屏的完整流程。如果某人完成两次,则计为两次 **completions**,但只有一次 **unique completion**。 ### Unique completions rate \{#unique-completions-rate\} 仅适用于用户引导 A/B 测试。唯一完成次数除以唯一浏览次数。此数据图表帮助您了解用户通过 A/B 测试变体与用户引导的互动情况,当您发现用户忽略用户引导时可据此进行优化。 --- # File: maths-behind-it --- --- title: "A/B 测试背后的数学原理" description: "了解订阅分析背后的数学原理,以获得更好的收入洞察。" --- A/B 测试是一种强大的技术,用于比较两个不同版本的流程、付费墙或用户引导的表现。其核心目标是根据 12 个月内的平均每用户收入,判断哪个版本更有效。然而,等待整整一年来收集数据并做出决策并不现实。因此,系统采用 2 周每用户收入作为代理指标——该指标基于历史数据分析选定,可近似反映目标指标。为了获得准确可靠的结果,必须采用能够处理多种数据类型的稳健统计方法。贝叶斯统计是现代数据分析中的主流方法之一,为 A/B 测试提供了灵活直观的分析框架。通过引入先验知识并用新数据持续更新,贝叶斯方法能够在不确定性条件下做出更优决策。本文档详细介绍了 Adapty 在评估 A/B 测试结果时所采用的数学分析方法,为数据驱动决策提供有价值的参考依据。 ## Adapty 的统计分析方法 \{#adaptys-approach-to-statistical-analysis\} Adapty 采用全面的统计分析方法来评估 A/B 测试的表现,并提供准确可靠的洞察。我们的方法论由以下关键步骤组成: 1. **指标定义:** 要成功开展 A/B 测试,您需要识别并定义与分析的具体目标相符的关键指标。Adapty 利用大量订阅应用的历史数据,确定哪个指标最适合作为"1 年后平均收入"这一长期目标的代理指标——结果是 14 天后的 ARPU。 2. **假设制定:** 我们为 A/B 测试创建两个假设。零假设(H0)假设对照组(A)和测试组(B)之间没有显著差异。备择假设(H1)则表明两个或多个组之间存在显著差异。 3. **分布选择:** 我们根据数据特征和观测指标选择最佳的分布族。最常见的选择是对数正态分布(考虑零值的情况)。 4. **最优概率计算:** 利用贝叶斯 A/B 测试方法,我们计算参与测试的每个付费墙或用户引导变体成为最佳选项的概率。该值与我们之前使用的 p 值相关,但本质上是一种不同的方法,更加稳健且易于理解。 5. **结果解读:** "成为最优的概率"正如其字面意思。概率越大,某一选项成为该任务最佳选择的可能性越高。您需要自行确定决策阈值,这应取决于您具体情况的许多其他因素,但通常使用 95% 作为概率标准。 6. **预测区间:** Adapty 计算每个组的表现指标的预测区间,提供真实总体参数可能落入的值域范围。这有助于量化与估计表现指标相关的不确定性。 ## 样本量确定 \{#sample-size-determination\} 确定合适的样本量对于获得可靠且具有结论性的 A/B 测试结果至关重要。Adapty 考虑统计功效和预期效应量等因素(在贝叶斯方法下这些因素仍然重要),以确保样本量充足。针对我们现在采用的贝叶斯方法,有专门的方法用于估算所需样本量,确保分析的可靠性。 如需进一步了解 A/B 测试的功能,我们建议参阅我们关于[创建](ab-tests)和[运行 A/B 测试](run_stop_ab_tests)的文档,以及了解各种 [A/B 测试数据图表与结果](results-and-metrics)。 Adapty 的 A/B 测试分析框架现已采用贝叶斯方法,但核心仍聚焦于数据指标的定义、假设的构建以及分布的选择。与此前计算 p 值不同,我们现在计算后验分布,并求出每个实验变体成为最优版本的概率,同时给出预测区间。这一改进后的方法不仅更加全面,也更为稳健,能够提供更直观、更易于解读的洞察。我们的目标始终如一:通过对 A/B 测试进行严谨的统计分析,帮助企业优化策略、提升效果、实现增长。 --- # File: autopilot-how-it-works --- --- title: "AI 增长顾问:工作原理" description: "了解 AI 增长顾问背后的逻辑,让我们帮助您提升收入。" --- [AI Growth Advisor](autopilot)(AI 增长顾问)能根据你的实际性能数据以及同类应用在市场上的表现,帮助你确定该运行哪些实验。它不是靠猜测,而是给出更有可能提升效果的具体测试建议。 本文将透明地介绍 AI Growth Advisor 的工作方式——它使用哪些数据、如何评估机会、以及为什么会出现某些建议。目的是帮助你在日常增长工作流中放心地使用它。 ## AI Growth Advisor 的实际功能 \{#what-ai-growth-advisor-actually-does\} AI Growth Advisor 会分析你的应用和付费墙数据,找出最有可能提升收入的实验方向。它的分析维度包括: - **当前配置**:定价、试用、产品以及转化效果 - **市场规律**:同类应用的定价结构和收费方式 - **测试历史**:你已经运行过的实验及其结论 - **增长潜力**:哪些调整最有可能带来显著变化 Growth Advisor 利用 AI 综合评估上述因素,并将其转化为可立即启动的 A/B 测试。你将获得一套现成的方案,无需调研竞品,也无需猜测下一步该测试什么。 ## AI 增长顾问背后的数据 \{#the-data-behind-ai-growth-advisor\} 每条建议都基于三个协同工作的主要数据来源。 #### 你的应用自身数据 \{#your-apps-own-data\} AI 增长顾问会分析你的应用当前的表现: - 各付费墙的转化数据指标 - 定价与产品结构 在提出任何优化建议之前,AI 增长顾问会先以此作为基准参考。 :::note 我们不会使用你的应用性能数据来为其他应用训练推荐模型。你的数据完全私密。 ::: #### 付费墙分析 \{#paywall-analysis\} AI Growth Advisor 会分析您的付费墙截图,并将其设计与同类别顶级应用所采用的成熟模式进行对比。它会评估布局设计、文案、订阅套餐展示,以及转化导向元素(如优惠标签或用户评价区块)。 分析完成后,会生成两类建议: - **基准对比建议**,基于头部应用的差异化做法,每条建议均附有具体数据支撑(例如"72% 的头部教育类应用采用了这一做法")。 - **视觉分析建议**,由 AI 根据你的截图自动生成,涵盖文案优化、布局调整及其他设计改进。 这些建议会直接进入你的[增长计划](autopilot-growth-plan#view-the-growth-plan),作为可[发起 A/B 测试](autopilot-execute-plan)的假设。 #### 竞品数据 \{#competitor-data\} AI Growth Advisor 会利用定价、订阅结构以及同类应用的常见模式等公开信息,将您的配置与同市场中的同类应用进行对比。由于竞争对手的定价和结构因市场而异,这些比较是按国家/地区进行的。竞争对手的定价数据来自第三方和公开来源(如 App Store),与数据图表分析所使用的匿名 Adapty 网络数据不同。 这样,你测试的是那些在同类应用中已经验证有效的策略,而不是随机的想法。看到分析结果后,你可以将自己的基准数据和竞品定价并排对比。如果类似的应用在不同定价或结构下表现更好,这就是一个有力信号,说明相同的策略也可能适合你。 :::tip AI Growth Advisor 会根据你实际具有竞争力的范围自动筛选相关竞争对手。我们通常建议保留这些推荐,而不是添加差距过大的应用。如果你的应用跨越多个品类,可以调整列表,聚焦于最相关的细分市场。 ::: #### 行业基准 \{#industry-benchmarks\} AI Growth Advisor 基于 Adapty 追踪的 20,000 款订阅应用的匿名数据,帮助您了解自己在特定国家/地区内与同类应用平均水平的差距。这些数据经过全网汇总,不与任何具体应用挂钩。 例如,您的转化漏斗和每次安装收入会与同类别、同国家/地区的应用平均值进行对比,让您清晰了解自己是低于平均水平、处于平均水平,还是已经领先于市场。 #### 地区市场数据 \{#geographic-market-data\} AI 增长顾问会分析各个地理市场——借助 Adapty 网络中 20,000 款应用的数据规律——找出哪些地区通过调整定价可以释放更多收益。针对每个国家/地区,它会评估以下维度: - **转化率**:安装到付费的转化率与全球平均水平的对比。转化率较高,可能意味着存在提价空间;转化率较低,则可能表明用户对价格较为敏感。 - **价格指数**:该国家/地区在 [Adapty 定价指数](https://uploads.adapty.io/adapty_pricing_index.pdf) 中的位置,反映当地居民的消费能力。 您可以根据增长计划中的[地区定价建议](autopilot-growth-plan#geo-pricing-hypotheses)创建 A/B 测试,从而将这些建议付诸实践。 ## AI 增长顾问如何生成建议 \{#how-ai-growth-advisor-decides-what-to-recommend\} AI 增长顾问会生成一批建议,帮助你提升付费墙转化率。这些建议应逐一测试,以便准确衡量每项变更的效果。 以下是 AI 增长顾问生成建议的方式: 1. **找出最大的提升空间** AI Growth Advisor 会审查您的定价、产品和漏斗表现,然后与行业规律及同类应用进行对比。分析以您主要市场的货币为基准——而不仅仅是美元——因此价格建议与您的订阅用户实际支付的金额相符。它会找出最具改进空间的环节,无论是调整价格、添加试用期,还是改变优惠结构。 2. **选择下一个实验** 每个假设都基于您现有的测试历史生成。AI 增长顾问了解您已运行过哪些实验、哪些胜出、哪些方向仍值得探索。下一条建议会在上一条实验结果的基础上生成,而非遵循固定的顺序。 3. **进行胜者与挑战者对比测试** 每次实验结束后,胜出方将成为新的基准。该结果将影响增长计划中的下一条建议——AI 增长顾问会保留有效的内容,排除无效的内容,并在此基础上选择下一个测试。 4. **保持实用性** AI Growth Advisor 只会建议你使用现有产品和设置即可发起的测试,或仅需少量改动(如新建产品或调整价格)的测试。其目标是让测试保持高效、易于管理。 5. **向你展示推理依据** 针对每条推荐,AI Growth Advisor 都会提供清晰的假设说明,解释为什么这个测试值得运行。你可以了解到当前数据指标与竞品及行业平均水平的对比、潜在机会所在,以及我们预期哪些核心指标会得到提升。 这让实验成为一个可重复的流程——每次测试都能带来新的洞察,推动你打造出更高效的付费墙。 ## 每次实验结束后会发生什么 \{#what-happens-after-each-experiment\} 建议不会用完。每一次完成的测试都会成为后续实验的基础。只要你持续测试,AI Growth Advisor 就会不断给出下一步的建议。 若要刷新底层市场数据,可在同一版位上重新运行分析。每次重新运行都会拉取最新的竞品定价、转化率基准和品类趋势,并将新发现的假设添加到增长计划中,而不会影响已有内容。AI 生成的假设、自定义假设以及进行中的 A/B 测试均会在重新运行后保留。 一旦你完成了基准优化,也可以与更强的竞争对手展开角逐。这种迭代方式能帮助你随着应用的成长和市场的演变,持续实现收益最大化。 :::tip 准备好了吗?启动 [AI Growth Advisor](autopilot-analysis) 来分析你的付费墙并生成包含 A/B 测试的增长计划。使用[内置向导](autopilot-execute-plan)无缝启动复杂测试:它将引导你完成产品创建、付费墙复制和市场细分设置。 ::: --- # File: autopilot-analysis --- --- title: "付费墙与市场分析" description: "为你的应用生成基于数据的定制化增长计划。" --- 按照本文步骤运行 AI 增长顾问分析,生成增长计划。 如果你已经为目标版位生成过增长计划,本次分析将生成新的假设供你选择。 :::tip 在开始之前,请确保您已满足[分析所需的前提条件](autopilot#prerequisites)。 ::: ## 付费墙分析 \{#paywall-analysis\} ### 选择要分析的付费墙 \{#select-a-paywall-for-analysis\} 1. 打开 **AI Growth Advisor** 页面,点击 [Get Growth plan](https://app.adapty.io/ab-tests/analysis/start) 按钮。 2. 在 **Paywall Diagnostic** 页面,从下拉菜单中选择 **Placement** 和 **Paywall**。Adapty 会自动预选收入最高的版位及其表现最佳的付费墙。如需分析其他付费墙,请先切换版位。 3. 上传截图。AI Growth Advisor 需要一张截图来分析你的付费墙设计和内容。 4. 查看付费墙的有效产品。右侧的产品卡片会显示每个产品的订阅时长、价格和试用期。 5. 点击 **Confirm & Analyze** 继续。Adapty 将分析你的付费墙并生成诊断报告。 ### 付费墙分析报告 \{#paywall-analysis-report\} 选择付费墙并上传截图后,Adapty 会分析你的付费墙设计模式,指出其中的亮点和可改进之处。 #### 哪些地方做得好 \{#whats-working-well\} 本节会列出你已采用的、有助于提升转化率的成熟设计模式。例如:醒目的折扣标签、突出的用户评价区块,或清晰的订阅方案说明。 #### 付费墙需要改进的地方 \{#what-to-fix-on-your-paywall\} Adapty 将改进建议分为两类: - **基准建议**:基于同类别顶尖应用数据驱动生成的建议。每条建议均包含一项基准统计数据(例如:"72% 的顶尖教育类应用采用此方案")以及具体的优化说明。 - **视觉分析建议**:基于付费墙截图由 AI 生成的建议,涵盖文案优化、布局调整等内容。 :::tip 您的[增长计划](autopilot-growth-plan#view-the-growth-plan)将包含基于基准建议的假设。您可以手动将视觉分析建议添加到计划中。 ::: 点击 **Get Market Insights** 继续。 ## 市场与竞品分析 \{#market-and-competitor-analysis\} :::note 市场与竞品分析需要先完成[付费墙分析](#paywall-analysis)。 ::: 市场洞察分析会将您应用的定价和转化数据与竞品及行业平均水平进行比较,且比较结果按国家区分。为提供基准参考,Adapty 会汇总并分析 App Store 中同一子类目和国家下其他应用的数据,这些数据在其他地方并不公开。 ### 选择竞品 \{#select-competitors\} 最多可选择 5 款竞品进行对比。 Adapty 会自动推荐 5 款,并额外建议 5 款供参考。你也可以通过 App Store 链接手动添加应用。为获得更好的分析结果,建议选择 MRR 高于自身的应用。 点击 **Generate report** 确认列表,等待分析完成。 ### 选择国家/地区 \{#select-a-country\} 使用**国家/地区**下拉菜单选择一个主要市场,查看详细分析数据。 ### 收入分布 \{#revenue-distribution\} 收入分布数据图表展示了您的收入来自哪些国家/地区,并提供百分比细分。图表会突出显示您的前 5 个国家/地区,这也是后续分析的重点。 ### 竞品价格对比 \{#competitor-pricing\} 竞品价格对比表展示了付费墙中的订阅价格与竞品在[所选国家](#select-a-country)的定价差异,并按订阅时长分列显示。 ### 转化漏斗 \{#conversion-funnel\} 该数据图表展示了你的转化率——浏览到试用、试用到付费、浏览到付费——以及同类应用的平均水平,供你对比参考。 ### 按时长划分的收入分布 \{#revenue-distribution-by-duration\} 此数据图表展示了不同订阅时长对收入的贡献比例,并与行业平均水平进行对比。如果您的收入高度集中于某一时长,可能意味着有优化定价策略的空间。 ### 激活 ARPU \{#activation-arpu\} **激活 ARPU:您的应用与类别对比** 数据图表将您应用的每次新安装平均收入与类别平均值进行比较。 可结合[转化漏斗](#conversion-funnel)一起使用: - 转化漏斗显示有多少用户付费。 - 激活 ARPU 显示每位用户的平均收入。 转化率高但激活 ARPU 低,可能意味着定价偏低。 该数据图表基于**同期群**计算。Adapty 取过去 90 天内安装应用的用户,将其产生的收入除以用户数量得出结果。 #### 与其他数据图表的比较 \{#comparison-to-other-metrics\} 激活 ARPU 与看板其他地方显示的 ARPU 值不会相同——每个数据图表衡量的内容不同。 - **[ARPU 数据图表](arpu)**:包含旧同期群的续订收入,因此数值通常是激活 ARPU 的数倍。 - **[收入数据图表](revenue),Period 筛选器设为"Activation"**:仅统计每位用户的首次付款,不计算该同期群在 90 天窗口内产生的续订收入。 - **[同期群收入](analytics-cohorts)(90 天)**:最接近的参考指标——建议以此作为对照。 ## 后续步骤 \{#next-steps\} 阅读[管理并执行增长计划](autopilot-growth-plan)一文,了解如何根据分析结果运行 A/B 测试。 你随时可以在 Growth Plan 页面查看分析结果。点击 **Analysis Results** 标签页即可。 --- # File: autopilot-growth-plan --- --- title: "管理增长计划" description: "添加自定义假设、将其归档并更新增长计划。" --- 完成[分析](autopilot-analysis)后,Adapty 会呈现你的增长计划——一份**可执行的改进假设**列表。每个条目都会建议新的价格方案或设计优化方向。 打开假设,[通过 A/B 测试来验证](autopilot-execute-plan)。 每个版位都有各自的增长计划。随着市场环境变化,你可以重新运行分析来刷新建议。历史运行记录会保存在版本历史中。 ## 假设 \{#hypotheses\} 切换增长计划顶部的标签页,按类型筛选假设: - **Top priority(高优先级)** 包含最值得关注的高影响力假设。若无符合条件的假设,此标签页将自动隐藏。 - **All(全部)** 显示当前方案中的所有假设。 - **Pricing(定价)** 假设探索新的价格点或试用配置,每项均基于付费墙诊断或市场洞察报告中的具体建议。 - **Visual(视觉)** 假设是设计改进建议,可能涉及文案、布局或其他视觉元素的调整。 - [**Geo-pricing(地域定价)**](#geo-pricing-hypotheses) 假设测试针对特定国家的价格调整。 - [**Archived(已归档)**](#archive-a-hypothesis) 假设是您从当前方案中移除的建议,随时可恢复。 你可以[添加自己的假设](#add-your-own-hypothesis),也可以[归档](#archive-a-hypothesis)不想测试的假设。 每次测试一个假设,顺序不限。地理定价测试是个例外——由于其目标受众互不重叠,可以并行运行。 ### 地理定价假设 \{#geo-pricing-hypotheses\} :::important 一次性购买不适用于区域价格优化。 ::: 打开 **Geo-pricing** 标签页,查看地理定价建议列表。每条建议针对一个国家进行单次价格调整,并作为独立的 A/B 测试运行。 Adapty 会检测需要价格调整的国家,并提供经 [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf) 验证的数据驱动建议。 <br /> #### Adapty 如何生成地区定价建议 \{#how-adapty-makes-geo-pricing-suggestions\} - 定价建议基于 App Store 数据,生成的 A/B 测试可同时在 App Store 和 Google Play 上运行。 - 价格调整幅度在所有订阅周期中保持一致。 - 所有价格均向最近的 App Store 价格档位取整。 - 如果 Adapty 拥有该国家的交易数据,价格将以当地货币显示(例如 EUR 或 GBP);若无本地数据,则以美元显示。 ### 假设状态标记 \{#hypothesis-status-badges\} 1. **闪电图标** — 标示最高优先级的建议。 2. **产品同步状态** — 当需要执行产品操作才能启动 A/B 测试时显示。 - **Draft** — 产品设置未完成(Adapty 侧) - **Action required** — 产品设置未完成(应用商店侧) - **Pending...** — Adapty 正在等待应用商店完成审核或初始同步。 - **Approved** — 产品已通过应用商店审核,可以开始测试。 - **Rejected** — 应用商店拒绝了该产品。 - **Not connected** — 产品尚未关联到应用商店。 3. **A/B 测试状态** — 启动 A/B 测试后显示: - **Draft test** — 测试已创建草稿,但尚未运行。 - **Running** — 测试正在进行中。 - **Completed** — 测试已结束。 - **Archived test** — 测试已归档,未得出结论。 ## 获取 AI 生成的新假设 \{#get-new-ai-generated-hypotheses\} 每次测试结束后,Adapty 会根据测试结果自动更新你的假设。 若要获取最新的竞品定价、转化率基准和品类趋势数据,请点击增长计划标题栏中的 **Update** Refresh,或在 AI Growth Advisor 主页点击 **Update Analysis**。在弹出提示时,点击 **Get New Ideas**。 Adapty 会打开分析向导,并预先选中您的版位。重复付费墙分析和竞品调研后,Adapty 会从结果中识别出新的假设。 选择您想添加的假设,或点击 **Add All To Plan** Plus 接受全部内容。新添加的假设会显示在列表顶部。您现有的 AI 生成假设、自定义假设以及正在进行的 A/B 测试均不受影响。 如果在完成更新前退出,可以**继续**未完成的操作,也可以**放弃**并重新开始。 ## 添加自定义假设 \{#add-your-own-hypothesis\} 点击 **Add Hypothesis** Plus 创建您自己的定价或视觉建议。 填写表单:标题、描述以及假设类型(**Monetization** 或 **Visual**)。 - 从下拉菜单中选择您希望改善的数据图表。 - 变现类假设还需要选择测试产品。 ## 归档假设 \{#archive-a-hypothesis\} 要归档某个假设,点击 Close 按钮,然后点击 Skip 确认。你可以选择填写原因——这有助于 Adapty 优化后续建议。 该假设将移至 **Archived** 标签页。 如需将已归档的假设恢复到活跃计划中,点击卡片上的 **Restore**。 ## 回顾并复用历史假设 \{#revisit-and-reuse-old-hypotheses\} 要查看过往分析生成的建议,点击增长计划标题中的 **Clock** Clock。**Version history** 弹窗会列出该版位的所有历史运行记录——包括日期、付费墙,以及当时你采纳的假设数量。 点击历史运行记录,查看其生成的假设。你可以通过 **Add to Plan** Plus 将任意假设添加到当前计划中——非常适合重新审视之前未采纳的建议。 --- # File: autopilot-execute-plan --- --- title: "执行增长计划" description: "从增长计划假设中启动 A/B 测试。" --- 你可以按任意顺序运行测试,但每次只能运行**一个**。由于地理定价测试的目标受众互不重叠,它们可以并行运行。每个测试结束后,将胜出策略推进到下一轮。每一轮都会让你更接近适合自己应用的最优方案。 据我们估算,完整运行所有推荐测试可使你的收入**最多提升 80%**。 :::important 每条建议都包含 A/B 测试的最短持续时间。在进入下一阶段之前,请遵循这些建议以获取最准确的数据。你需要手动停止 A/B 测试。 ::: 打开一个假设,然后点击 **Set Up & Run Test** 启动 A/B 测试创建向导。 ## 第一步:查看假设 \{#step-1-view-the-hypothesis\} 第一步展示假设的概览,包括建议的改动及其背后的原因说明。点击 "Set up & Run Test" 进入下一步。 {/* TODO: REPLACE SCREENSHOT */} ## 第二步:创建新产品 \{#step-2-create-new-products\} 如果测试涉及价格变更,第二步将帮助你为测试的实验变体创建新产品。视觉类假设会跳过此步骤。 * 点击 **Create a new product and push to stores**,从头创建一个新产品。 * 点击 **Connect an existing product**,若所需产品已在应用商店配置中存在,可直接关联。 ## 步骤 3:设置市场细分和付费墙 \{#step-3-set-up-segment-and-paywall\} 第三步用于设置付费墙的测试实验变体。Adapty 会提示你复制当前付费墙并应用建议的更改。 对于**地理定价假设**,向导会提示你选择一个现有的地理围栏市场细分,或创建一个新的市场细分。 新付费墙准备就绪且市场细分设置完成后,点击 **Next**。 ## 第 4 步:检查并启动 \{#step-4-review--launch\} 最后一步是对即将进行的测试的汇总说明,包含以下内容: - **实验变体 A vs 实验变体 B** 的关键数据图表 —— 付费墙名称、产品选择、试用时长和价格。 - 测试的**持续时间**、**流量**(分配比例)和**订阅用户数**(最小样本量)。 - **如何解读结果**部分,描述哪些信号表明测试成功。 检查配置后,点击 **Launch Test** 即可启动 A/B 测试。 --- # File: how-adapty-analytics-works --- --- title: "Adapty 分析的工作原理" description: "了解 Adapty 分析如何高效追踪订阅表现。" --- 本文介绍 Adapty Analytics 的工作原理:它显示哪些数据、数据来源以及数据如何被处理。同时也解释了使 Adapty Analytics 与众不同的设计决策,以及这些决策如何为您带来价值。 ## Adapty 分析与应用商店分析的对比 \{#adapty-analytics-vs-store-analytics\} - **数据多样性**:应用商店只能展示自身的数据,无法获取用户在应用内的行为数据。 Adapty 可以整合来自多个应用商店的数据,以及其他来源的数据——包括营销平台和广告网络。Adapty SDK 会追踪用户与付费墙和用户引导的交互行为。 - **更新频率**:应用商店通常每天更新一次数据,这可能限制你进行实时决策的能力。 Adapty 提供[接近实时](#data-processing)的数据分析。 - **高级数据图表**:应用商店只提供基础数据,例如下载量、收入和留存率。 Adapty 还会计算高级数据图表,例如周期性收入或每用户平均收入,并提供专题分析板块,涵盖用户流失、账单失败等订阅问题。完整列表请参阅[数据图表对比表](metric-comparison-table)。 - **趋势预测**:Adapty 采用先进的机器学习算法[预测未来 LTV 和收入](predicted-ltv-and-revenue)。 ## 数据及其来源 \{#data-and-its-sources\} Adapty Analytics 将以下数据处理为[数据图表](analytics): - 用户生命周期中产生的[订阅事件](events)——试用开始、购买、续订、取消、账单失败、退款。Adapty 将这些事件汇总到[数据图表](analytics)中,并实时转发到[webhook](webhook)、[事件动态](event-feed)及[基于事件的集成](analytics-integration)。 - [交易数据](revenue)——收入、退款、买家所在国家等。 - **应用数据**,例如安装数量或[付费墙互动情况](paywalls)。 - [交易的归因数据](attribution-integration):流量来源及广告活动。 这些数据来自以下来源: - <InlineTooltip tooltip="Adapty SDK">[iOS](ios-sdk-overview)、[Android](android-sdk-overview)、[React Native](react-native-sdk-overview)、[Flutter](flutter-sdk-overview)、[Unity](unity-sdk-overview)、[Kotlin Multiplatform](kmp-sdk-overview)、[Capacitor](capacitor-sdk-overview) </InlineTooltip> 从应用内部上报用户行为数据。如果由 Adapty 管理您的购买流程,SDK 会直接共享购买事件的第一手信息。如果您使用[观察者模式](observer-vs-full-mode),SDK 则接收您手动配置的[事件报告](report-transactions-observer-mode)。 - 应用商店通过服务器间通信,将交易事件(试用、订阅续费、取消等)通知给 Adapty。 - 第三方[归因服务](attribution-integration)(Appsflyer、Adjust、Branch 等)共享流量来源和广告活动数据。如果您配置了 [Adapty Attribution](adapty-user-acquisition),Adapty 可以自行处理广告活动数据,无需经过此步骤。 - 用户可以[手动导入历史交易数据](importing-historical-data-to-adapty),供 Adapty 分析和展示。 某个数据源出现问题可能会影响您整体分析数据的质量。详情请查看[故障排查](#troubleshooting)部分。 ## 第三方集成 \{#third-party-integrations\} 你可以启用 [Adapty 归因](adapty-user-acquisition),通过广告活动数据扩展 Adapty 的分析能力,帮助你发现广告投入与用户行为之间的关联。 同样,你也可以将分析数据[导出](analytics-integration)到第三方平台,或发送到[私有服务器](webhook),在其他平台上对 Adapty 的数据进行进一步分析。 ## 数据处理 \{#data-processing\} Adapty 提供近实时的分析功能,让用户能够快速响应关键数据图表的变化。 - **数据图表**:交易发生后,数据会有 **15–30 分钟的延迟**才会出现。Adapty 需要这段时间来验证交易、计算佣金和税费,并汇总数据。 - **[事件流](event-feed)**:实时更新,应用商店一推送事件即刻呈现。 - **[Webhook](webhook) 及基于事件的集成**(AppsFlyer、Branch 等):Adapty 在事件发生时立即转发,不存在 15–30 分钟的数据图表延迟。接收方服务可能会有自身的处理时间。 每个数据来源都有自己的时序。同一事件在数据图表、事件动态和集成中出现的时间可能略有不同,这些细微差异是正常现象。 ## 佣金与税费 \{#commissions-and-taxes\} 查看收入相关数据图表时,你可以在 **Gross revenue**、**Revenue after commissions** 和 **Revenue after commissions and taxes** 之间切换。 ### 佣金 \{#commissions\} 应用商店会从每笔交易中扣除佣金。如果您的组织已加入减免佣金计划,请在 Adapty 中修改相关设置以调整佣金比例的计算方式: * [App Store 小型企业计划](app-store-small-business-program) * Google 的[服务费减免计划](google-reduced-service-fee) 应用商店会自动报告其他可能降低交易佣金的因素: * [1年以上App Store订阅的续费](https://developer.apple.com/app-store/subscriptions/) — 15% 手续费 * 特定国家/地区费率(例如,[在日本分发的App Store应用为21%](https://developer.apple.com/support/app-distribution-in-japan/#business-terms)) ### 税务 \{#taxes\} **Adapty 不计算税务。** Apple 和 Google 负责确定每笔交易适用的税率,并将结果返回给 Adapty,Adapty 会原样展示该数值。 特定交易显示的税率取决于: - **买家账单所在国家**及当地适用的税率。 - **应用商店的税务处理规则**。在某些地区,商店代开发者代收代缴税款;在其他地区,则由开发者自行负责。 - 对于 App Store 交易,还需考虑分配给应用或应用内购买项目的**税务类别**(图书、新闻、视频等)——根据当地规定,不同类别可能适用不同税率。 税率因应用而异,甚至同一应用内不同交易之间也可能存在差异——这取决于买家所在国家/地区、应用商店的处理规则,以及(对于 App Store 而言)所分配的税务类别。 如需了解权威规则,请参阅各应用商店的官方文档: - [App Store:了解税务](https://developer.apple.com/help/app-store-connect/making-payments-to-apple/understanding-taxes/) - [Google Play:税率与增值税](https://support.google.com/googleplay/android-developer/answer/138000) ## 故障排查 \{#troubleshooting\} :::link 主要文章:[数据差异与故障排查](discrepancies-and-troubleshooting) ::: * 数据源配置有误或缺失会对整个分析系统产生负面影响。如果遇到数据问题,请确认您与各应用商店及第三方平台的集成已正确配置并处于活跃状态。 * 如果您将 Adapty 数据图表与其他分析平台进行对比,可能会发现数据存在差异。这属于正常现象,可能是由数据处理方式不同所导致的。请阅读[数据差异指南](discrepancies-and-troubleshooting)文章,了解数据差异的常见原因。 --- # File: metric-comparison-table --- --- title: "比较不同数据图表" description: "Adapty 分析数据图表的参考表格,按类别整理。" --- 以下是 Adapty Analytics 中可用数据图表的概览,帮助你了解每个指标的含义以及它与相关指标的区别。 如需深入了解 Adapty 处理分析数据的方式,请参阅[Adapty 分析的工作原理](how-adapty-analytics-works)。 :::note 本文不涵盖 [Adapty 归因](adapty-user-acquisition) 数据图表。请阅读 [Adapty 归因分析](ua-analytics),了解广告活动相关指标(包括 Spend、CPI、ROAS、CTR 等)。 ::: ## 全局数据图表 \{#global-metrics\} 全局数据图表用于追踪您整个应用在所有版位和付费墙中的表现。 ### 营收 \{#revenue\} 这些数据图表衡量应用产生了多少收入以及收入来源。 | 数据图表 | 描述 | 主要区别 | |--------|-------------|----------------| | [收入](revenue) | 订阅和一次性购买产生的总收入,扣除退款 | 实际产生的收入。根据[图表控件](controls-filters-grouping-compare-proceeds)的设置,可显示总收入、扣除佣金后的收入,或扣除税费和佣金后的收入 | | [MRR](mrr) | 活跃订阅产生的月度经常性收入 | 应用可预期的月度收入。不含一次性购买和非周期性订阅 | | [ARR](arr) | 活跃订阅产生的年度经常性收入 | 计算方式与 MRR 相同,但以年为单位。适合预估全年收入 | | [ARPU](arpu) | 每用户平均收入 | 将收入除以用户总数(含付费用户和非付费用户),反映每位用户平均带来的收入 | | [ARPPU](arppu) | 每付费用户平均收入 | 仅统计所选时间段内有过购买行为的用户(含已退款交易)。始终高于 ARPU | | [LTV(用户生命周期价值)](ltv) | 付费客户产生的收入除以同期群中付费客户数量 | 付费客户随时间累积的实际价值。与 ARPPU(单一周期)不同,LTV 反映整个客户关系周期内的总收入,可按续订次数或按日历天数查看 | | [趋势预测 LTV](predicted-ltv-and-revenue) | 同期群中每位用户的预估生命周期价值 | 面向未来的预测。与已实现的 LTV 不同,基于历史同期群留存规律预测未来价值。支持 3、6、9、12、18 和 24 个月 | | [预测收入](predicted-ltv-and-revenue) | 预估同期群将产生的总收入 | 面向未来的预测。与已实现的收入不同,预测同期群在所选时间段内将产生的总额。每日更新 | | [一次性购买](non-subscriptions) | 应用内购买数量:消耗型商品、非消耗型商品和非续期订阅 | 不含自动续期订阅 | | [退款事件](refund-events) | 已退款的购买或订阅数量 | 按退款日期计算,而非原始购买日期 | | [退款金额](refund-money) | 所选时间段内的退款总额 | 退款造成的财务影响。在扣除渠道费用前计算。与退款事件(计数)不同,此项显示的是具体金额 | ### 订阅者与转化 \{#subscribers-and-conversion\} 以下数据图表用于追踪用户进入应用后在各漏斗阶段的流转情况。 | 数据图表 | 描述 | 主要区别 | |--------|-------------|----------------| | [安装量](installs) | 统计周期内的应用安装次数 | 根据[安装量定义](general#4-installs-definition-for-analytics),统计以下其中一项:<br /> • 设备安装次数(重新安装应用的用户会被再次计入)<br /> • 独立用户数(仅统计设置了 `customer_user_id` 的用户,匿名用户完全排除——若无已识别用户,则计数为 0) | | [新增试用](new-trials) | 统计周期内激活的试用次数 | 统计每次试用开始,即使查看数据图表时该试用已到期或已转化为付费订阅 | | [活跃试用](active-trials) | 尚未到期的试用数量 | 仅统计在统计周期结束时仍处于活跃状态的试用 | | [新增订阅](reactivated-subscriptions) | 统计周期内首次激活的订阅,包括无试用的首次购买和试用转付费 | 不含续订和重新激活。与集成事件 `subscription_started` 不同,该事件仅统计无试用的首次购买——试用转化触发的是 `trial_converted` | | [活跃订阅](active-subscriptions) | 尚未到期的付费订阅数量 | 不含试用及已取消续订的订阅 | | [安装到试用](analytics-conversion#install---trial) | 开始试用的安装用户占比 | 分母包含所有安装用户,而非仅付费墙查看者,因此转化率可能低于"付费墙浏览到试用"。如果应用未记录付费墙浏览,两项数据也可能出现差异。这可能发生在未调用 `logShowFlow`(iOS SDK v4+)/ `logShowPaywall` 的自定义付费墙中,或用户通过[推广应用内购买](https://developer.apple.com/documentation/storekit/supporting-promoted-in-app-purchases-in-your-app)开始试用时。 | | [付费墙浏览到试用](analytics-conversion#paywall-view---trial) | 查看付费墙后开始试用的用户占比 | 仅统计看过付费墙的用户,因此转化率可能高于"安装到试用" | | [试用到付费](analytics-conversion#trial---paid) | 试用用户中购买订阅的占比 | 衡量试用质量和转化效率。与"安装到付费"不同,仅关注完成试用的用户 | | [安装到付费](analytics-conversion#install---paid) | 安装用户中首次购买订阅的占比 | 统计所有安装用户,而非仅付费墙查看者,转化率可能低于"付费墙浏览到付费"。包含直接购买和试用转付费 | | [付费墙浏览到付费](analytics-conversion#paywall-view---paid) | 查看付费墙后最终购买订阅的用户占比 | 仅统计看过付费墙的用户,因此转化率可能高于"安装到付费"。包含先完成试用再付费的用户 | ### 留存率与订阅续费 \{#retention-and-subscription-renewal\} 这些数据图表用于追踪应用留住付费用户的能力随时间的变化情况。 | 数据图表 | 描述 | 主要区别 | |--------|-------------|----------------| | [留存率](analytics-retention) | 每个结算周期后仍保持订阅的原始订阅者比例——第 1 次续订、第 2 次续订,以此类推 | 从首次付款起追踪订阅者。与下方的周期间数据图表不同,始终与原始群体进行比较,让你一目了然地掌握整体情况 | | [付费至第 2 周期](analytics-conversion#paid---2nd-period) | 首次订阅者续订第二个周期的百分比 | 衡量两个特定相邻周期之间的转化。与留存率不同,专注于最关键的单次续订——即第一次续订 | | [第 2 至第 3 周期](analytics-conversion#2nd-period---3rd-period) | 从第 2 个周期续订至第 3 个周期的百分比 | 反映首次续订后的早期留存稳定性 | | [第 3 至第 4 周期](analytics-conversion#3rd-period---4th-period) | 从第 3 个周期续订至第 4 个周期的百分比 | 中期留存指标 | | [第 4 至第 5 周期](analytics-conversion#4th-period---5th-period) | 从第 4 个周期续订至第 5 个周期的百分比 | 长期忠诚度指标 | | [6 个月以上](analytics-conversion#6-months-) | 首次订阅者保持订阅超过 6 个月的百分比 | 按日历时间衡量,而非续订次数。即使没有续订,年度订阅者在 6 个月时仍计为留存 | | [1 年以上](analytics-conversion#1-year-) | 首次订阅者保持订阅超过 12 个月的百分比 | 年度留存里程碑 | | [2 年以上](analytics-conversion#2-years-) | 首次订阅者保持订阅超过 24 个月的百分比 | 长期留存里程碑 | ### 用户流失 \{#churn\} 这些指标衡量应用失去了多少付费订阅用户和试用用户。 | 数据图表 | 描述 | 主要区别 | |--------|-------------|----------------| | [取消续期的试用](trials-renewal-cancelled) | 用户关闭了自动续期的试用 | 用户保留试用访问权限直到到期,但不会自动转为付费。与取消续期的订阅不同,此指标适用于尚未付费的试用用户 | | [已过期(已流失)的试用](expired-churned-trials) | 已过期的试用——用户失去了对高级功能的访问权限 | 用户已失去访问权限。归因于到期日期,即使用户在上一周期取消了续期也如此。可按原因分组(主动取消 vs. 账单问题) | | [取消续期的订阅](cancelled-subscriptions) | 用户关闭了自动续期的订阅 | 用户在周期结束前仍有访问权限。表示流失风险,而非实际流失——用户可能在周期到期前重新开启自动续期 | | [已流失(已过期)的订阅](churned-expired-subscriptions) | 已过期的订阅——用户失去了对高级功能的访问权限 | 实际流失。用户已失去访问权限。归因于到期日期,即使用户在上一周期取消了续期也如此。可按原因分组(主动取消 vs. 账单问题) | ### 账单问题与营收恢复 \{#billing-issues-and-revenue-recovery\} 这些数据图表用于追踪应用从账单问题导致的营收损失中恢复的效果。 | 数据图表 | 描述 | 关键区别 | |--------|-------------|----------------| | [宽限期](grace-period) | 因账单失败而进入宽限期的订阅 | 包含已超过宽限期并失去访问权限的用户 | | [宽限期转付费](analytics-conversion#grace-period---paid) | 在宽限期结束前成功续订的宽限期用户占比 | 比率(%)。回答"有多少宽限期用户成功恢复?" | | [宽限期转化数](grace-period-converted) | 成功续订的宽限期订阅绝对数量 | 与"宽限期转付费"统计的是相同事件,但以数量而非百分比展示 | | [宽限期转化收入](grace-period-converted-revenue) | 来自宽限期恢复的收入 | 宽限期功能带来的财务影响 | | [账单问题](billing-issue) | 进入账单问题状态的订阅 | 在宽限期到期后开始计算。与宽限期不同,仅统计已失去高级访问权限的用户 | | [账单问题转付费](analytics-conversion#billing-issue---paid) | 在账单周期结束前成功续订的账单问题用户占比 | 比率(%)。回答"有多少账单问题用户成功恢复?" | | [账单问题转化数](billing-issue-converted) | 成功续订的账单问题订阅绝对数量 | 成功续订的账单问题订阅数量。与"账单问题转付费"统计的是相同事件,但以数量而非百分比展示 | | [账单问题转化收入](billing-issue-converted-revenue) | 来自账单问题恢复的收入 | 账单问题恢复带来的财务影响 | ## 付费墙、版位与用户引导数据图表 \{#paywall-placement-and-onboarding-metrics\} 这些数据图表分别针对各个[付费墙](paywall-metrics)、[版位](placement-metrics)和用户引导进行计算,用于衡量特定付费墙或版位的表现,而非整个应用的整体情况。**关联的全局数据图表**一栏展示了全局分析部分中对应的数据图表。 | 数据指标 | 描述 | 主要区别 | 关联全局指标 | |--------|-------------|----------------|---------------| | [收益](paywall-metrics#proceeds) | 扣除税费和佣金后,单个版位的收入 | 等同于扣除税费和佣金后的[收入](revenue) | [收入](revenue) | | [ARPPU](paywall-metrics#arppu) | 该付费墙或版位的每付费用户平均收入 | 计算方式与全局 ARPPU 相同,但范围限定在单个付费墙或版位 | [ARPPU](arppu) | | [ARPAS](paywall-metrics#arpas) | 收入除以活跃订阅者数量(含试用和付费用户) | 包含试用用户。与 ARPPU 不同,反映整个订阅用户群的收入潜力 | — | | [浏览量](paywall-metrics#views) | 付费墙或版位的累计展示次数 | 统计每次展示。同一用户浏览同一付费墙两次计为 2 次浏览 | — | | [独立浏览量](paywall-metrics#unique-views) | 浏览过付费墙或版位的独立用户数 | 每位用户仅计一次,无论浏览多少次。与浏览量不同,衡量的是覆盖范围而非互动频率 | — | | [购买转化率](paywall-metrics#cr-to-purchases) | 购买次数除以总浏览量 | 分母使用总浏览量(含同一用户的重复浏览) | [付费墙浏览到付费](analytics-conversion#paywall-view---paid) | | [独立用户购买转化率](paywall-metrics#unique-conversion-rate-cr-to-purchases) | 购买次数除以独立浏览量 | 分母使用独立浏览量。由于重复浏览者只计一次,转化率高于非独立转化率 | [付费墙浏览到付费](analytics-conversion#paywall-view---paid) | | [试用转化率](paywall-metrics#unique-cr-to-trials) | 开始试用次数除以总浏览量 | 衡量付费墙将浏览转化为试用的效果 | [付费墙浏览到试用](analytics-conversion#paywall-view---trial) | | [独立用户试用转化率](paywall-metrics#unique-cr-to-trials) | 开始试用次数除以独立浏览量 | 计算方式与试用转化率相同,但分母为独立浏览用户数 | [付费墙浏览到试用](analytics-conversion#paywall-view---trial) | | [购买次数](paywall-metrics#purchases) | 该付费墙的总交易次数:包括新购、试用转化、升级、降级和续订恢复 | 不含续费。 | [收入](revenue) | | [试用次数](paywall-metrics#trials) | 通过该付费墙激活的试用总次数 | 仅限该付费墙范围内 | [新增试用](new-trials) | | [试用取消次数](paywall-metrics#trials-canceled) | 用户关闭自动续订的试用次数 | 仅限该付费墙的试用范围 | [试用续订已取消](trials-renewal-cancelled) | | [退款率](paywall-metrics#refund-rate) | 退款次数除以首次购买次数(不含续费) | 为比率(%),非计数。将退款次数标准化为相对于购买量的比率 | [退款事件](refund-events)(计数,非比率) | | 完成次数 | 用户从第一屏到最后一屏完成用户引导流程的总次数 | 仅适用于版位和用户引导。统计每次完成,包括同一用户的重复完成 | — | | 独立用户完成次数 | 完成用户引导流程的独立用户数 | 仅适用于版位和用户引导。每位用户仅计一次。与完成次数不同,衡量实际完成流程的用户个数 | — | | 独立用户完成率 | 独立用户完成次数除以独立浏览量 | 仅适用于版位和用户引导。衡量用户引导效果:开始引导的用户中有多少比例实际完成了引导 | — | --- # File: overview --- --- title: "数据概览页面" description: "在同一页面查看多个 Adapty 数据图表,快速了解应用整体表现" --- [概览页面](https://app.adapty.io/overview)将所有应用的数据图表汇总在一处展示,是看板首页,也可通过左侧菜单访问。如需查看单个应用的数据,请直接打开对应的[数据图表](charts)。 ## 数据图表 \{#charts\} 概览页面展示了 Adapty [分析数据图表](charts) 的自定义子集。如需查看每个图表的说明及对比,请参阅[数据图表对比表](metric-comparison-table)。 点击右上角的 **Edit** 按钮,即可自定义显示哪些图表及其排列顺序。在此可以删除、添加或重新排序图表: 以下数据图表可供使用: - [收入](revenue) - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) - [ARPAS](placement-metrics#arpas) - [安装量](installs) - [新增试用](new-trials) - [新增订阅](reactivated-subscriptions) - [活跃试用](active-trials) - [活跃订阅](active-subscriptions) - [新增非订阅购买](non-subscriptions) - [退款事件](refund-events) - [退款金额](refund-money) - [已取消续订的订阅](cancelled-subscriptions) - [从安装到试用、安装到付费以及试用到付费的转化率](analytics-conversion) ## 控制选项 \{#controls\} 概览页面支持大多数[分析控制选项](controls-filters-grouping-compare-proceeds),包括筛选、分组和时间段对比。 概览页面独有的功能是按应用进行分组和筛选。由于该页面汇总了所有应用的数据,按应用查看的视图可以展示每个应用对业务指标的贡献情况: ## 安装数量与时区 \{#install-count-and-timezone\} 概览页面汇总了您所有应用的数据,使用的是**概览页面自身的时区和安装计数设置**——各应用单独配置的值在此不适用。 - **Installs**:选择安装量的统计方式。**By device installations** 将每次设备安装(包括重装)都计为一次独立安装;**By unique users** 则只统计每位已识别用户的首次安装。如需更改,点击 **Edit Metrics** 并从下拉菜单中选择[其他选项](general#4-installs-definition-for-analytics)。 - **Timezone**:如需更改 Overview 的时区,点击 **Edit Metrics**,然后从下拉菜单中选择时区。如果您账户中的不同应用使用了不同时区,此功能尤为实用。 --- # File: controls-filters-grouping-compare-proceeds --- --- title: "分析控制项" description: "筛选、分组并对比 Adapty 分析数据。" --- Adapty 在每个分析标签页中提供多种控制项,用于精细化数据展示:时间范围、周期对比、筛选、分组以及数据图表可视化。各标签页的可用控制项有所不同。 **各分析标签页可用的控制项:** | 控件 | 数据图表 | 同期群 | 漏斗 | 留存 | 转化 | LTV | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | | 日期范围 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 时段对比 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | 筛选 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 分组 | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | | 图表可视化 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | 表格视图 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | CSV 导出 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 佣金与税费 | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ### 设置日期范围 \{#set-the-date-range\} 使用每个数据图表上方的 **Date range** 日历来选择时间段。Adapty 分析使用 **UTC 时区**;[概览页面](overview)有其自己的可配置时区。 #### 预设范围 \{#preset-ranges\} 使用 **Custom** 选项可以指定任意起止日期。预设选项包括: | 预设 | 开始 | 结束 | | --- | --- | --- | | 最近 7 天 | 6 天前 | 今天 | | 最近 28 天 | 27 天前 | 今天 | | 最近一个月 | 上个月的同一日期 | 今天 | | 最近 3 个月 | 3 个月前 | 今天 | | 最近 6 个月 | 6 个月前 | 今天 | | 最近一年 | 1 年前 | 今天 | | 上个月 | 上个月第一天 | 上个月最后一天 | | 本月 | 本月 1 日 | 今天 | | 本季度 | 本季度第一天 | 今天 | | 今年 | 今年 1 月 1 日 | 今天 | :::tip 使用**最近 28 天**来追踪按周计费的订阅产品——该时间范围涵盖四个完整的周期,不会因周期不完整而影响对比数据。 ::: #### 时间粒度 \{#time-scale\} 数据图表上的每个数据点代表一段时间——可从下拉菜单中选择天、周、月、季度或年。天和周适合查看短期波动,月、季度和年更适合分析长期趋势。 在[同期群](analytics-cohorts)和 [LTV](ltv) 分析中,相同的设置称为**同期群长度**——详情请参阅相应文章。 ### 比较两个时间段 \{#compare-two-time-periods\} 点击日历旁边的对比选项,将当前时段与较早时段叠加显示。Adapty 默认的对比区间是紧接当前时段之前、长度相同的时段。如需更改对比范围,再次点击该选项并选择自定义范围。 对比结果将显示: - **在数据图表上** — 以折线、面积或柱形的形式叠加显示,最多可选择一个分组。 - **以数值形式** — 显示两个时间段之间的差异,数值用绿色(更高)或红色(更低)标注。 - **在提示框中** — 将鼠标悬停在任意数据点上,即可查看该点的数值差异。 ### 筛选与分组数据 \{#filter-and-group-data\} 通过**筛选**,可将数据图表限定为符合一个或多个属性的数据(例如特定国家或产品)。通过**分组**,可将数据图表的汇总值拆分为多个独立系列——每个属性值对应一个系列。例如,按国家对收入进行分组,即可为每个国家生成独立的收入曲线,而非合并后的总计。 **可用的筛选与分组属性:** | 属性 | 筛选 | 分组 | 描述 | | --- | :---: | :---: | --- | | 归因 | ✅ | ✅ | 来源、状态、渠道、活动、广告组、广告集和创意(关键词)。需要[归因集成](attribution-integration)。 | | 目标受众 | ✅ | ✅ | 用户所属的[目标受众](audience)。 | | 续订状态 | ❌ | ✅ | 订阅是否会在下一个周期续订。 | | 周期 | ✅ | ✅ | 订阅生命周期阶段:**Trial**(试用)、**Activation**(首次付款)或 **Renewal 1**–**Renewal 5**、**Renewals 6+**(后续续订)。 | | 国家/地区 | ✅ | ✅ | 用户的应用商店所在国家/地区。如果无法获取,Adapty 将根据货币代码或设备 IP 推断。 | | 优惠类型 | ✅ | ✅ | 应用于该交易的优惠:<ul><li>**Introductory** — 订阅初始周期的新用户优惠。使用 **Offer Discount Type** 区分付费新用户优惠和免费试用。</li><li>**Promotional** — App Store 促销活动及同类优惠。</li><li>**Offer Code** — 用户在商店中输入的促销码。</li><li>**No offer** — 未使用任何优惠。</li></ul> | | 优惠 ID | ✅ | ✅ | 具体的优惠 ID。 | | 优惠折扣类型 | ✅ | ✅ | 新用户优惠或促销活动的定价模式:**Free Trial**(免费试用)、**Pay As You Go**(按量付费)或 **Pay Up Front**(预付)。结合 **Offer Type** 使用,例如可区分免费试用型新用户优惠与付费型新用户优惠。 | | 付费墙 | ✅ | ✅ | 购买时使用的[付费墙](paywalls)。 | | A/B 测试 | ✅ | ❌ | 购买期间激活的 [A/B 测试](ab-tests)。 | | 版位 | ✅ | ✅ | 发生购买的[版位](placements)。 | | 商店 | ✅ | ✅ | 处理该交易的商店:App Store、Google Play、Stripe 等。 | | 产品 | ✅ | ✅ | [产品](product) — 订阅和一次性购买。 | | 时长 | ✅ | ✅ | 产品的有效时长。 | | 市场细分 | ✅ | ✅ | 用户[市场细分](segments)。按市场细分分组可将各细分的表现与**所有用户**进行对比。<ul><li>漏斗不支持按市场细分分组。</li><li>如果在某个市场细分使用自定义属性后修改了该属性,Adapty 可能会在分析中将该用户排除在该细分之外。数据仍会显示之前的值。</li></ul> | | 退款原因 | ✅ | ✅ | 交易退款的原因(例如 **Refund** 或 **Upgraded**)。适用于退款和账单问题解决类数据图表。 | | 到期原因 | ❌ | ✅ | 订阅或试用到期的原因:**Cancelled by customer**(用户取消)、**Billing issue**(账单问题)、**Customer hasn't agreed to price increase**(用户未同意涨价)、**Unknown**(未知)或 **Refund**(退款)。适用于已到期(已流失)订阅和已到期(已流失)试用。 | | 同期群(仅限 LTV) | ❌ | ✅ | 在 LTV 数据图表中,按同期群长度分组:**Day**(天)、**Week**(周)、**Month**(月)或 **Year**(年)。在此图表中替代按归因分组。 | 并非所有分析视图都支持上述所有筛选或分组属性。**图表**标签页中的 ARPU 和安装量仅支持按归因、国家、市场细分、应用商店以及(仅筛选)A/B 测试进行分析。LTV、同期群、漏斗、留存率和转化率各标签页所支持的维度各有不同。如需了解具体支持情况,请参阅对应数据图表或标签页的说明文章。 ### 国家/地区的判定方式 \{#how-country-is-determined\} 每笔交易在创建时都会打上国家/地区标签。判定来源按优先级排列如下: 1. 交易发生时用户的**设备 IP 所在国家/地区**。 2. 用户的**应用商店所在国家/地区** —— 即其 App Store 或 Google Play 账户的归属地。 3. 用户最近一次已知的 **IP 所在国家/地区**。 网页支付(Stripe、Paddle)、手动授权访问,或应用商店未提供归属地信息的交易,均无法获取应用商店国家/地区。在这些情况下,Adapty 会回退到基于 IP 的国家/地区判定。 由于国家信息是按每笔交易单独记录的,用户在安装后切换 App Store 所在国家,切换前后的交易将显示不同的国家值。历史交易会保留其原始国家信息。 **GB 与 United Kingdom。** 国家数据以 ISO 3166-1 alpha-2 代码形式存储(即"GB",而非"United Kingdom")。看板的展示层通过查找表将代码映射为完整名称,其中包含一个历史遗留的 `'UK' → 'United Kingdom'` 别名——这也是为什么在创建市场细分时,两者都可能作为选项出现。 ### 更改数据图表可视化方式 \{#change-the-chart-visualization\} 从可视化下拉菜单中选择数据图表的显示方式: - **堆积柱形图** — 每个柱子显示总量,按组别用不同颜色分段展示。 - **堆积面积图** — 与堆积柱形图相同,但用填充区域连接各数据点。 - **折线图** — 每组一条折线,无填充。 - **百分比堆积柱形图** — 每个柱子高度相同,均占满图表高度;各段显示每组的相对占比(百分比),而非实际数值。适合查看各组随时间变化的比例关系。 - **百分比堆积面积图** — 与百分比堆积柱形图相同,但用填充区域代替柱子。 ### 以表格形式查看数据 \{#view-data-as-a-table\} 每个数据图表下方都有一张对应的数据表格,以日期作为列。"Total"行和列显示图表中不可见的汇总数据。 ### 将数据导出为 CSV \{#export-data-to-csv\} 点击 **Export** 按钮,可将数据图表的原始数据下载为 CSV 文件。 :::tip 如需通过程序或定时任务获取数据,建议改用 [Export API](export-analytics-api)——它返回的数据与 CSV 下载内容完全一致。 ::: ### 显示毛收入或净收入 \{#display-gross-or-net-revenue\} 对于与收入相关的数据图表([Revenue](revenue)、[MRR](mrr)、[ARR](arr)、[ARPU](arpu)、[ARPPU](arppu)),Adapty 提供一个下拉菜单,包含三种显示模式: - **Gross revenue** — 未扣除任何费用前的总收入。 - **Proceeds after store commission** — 扣除应用商店佣金后的收入,仍含税。 - **Proceeds after store commission and taxes** — 同时扣除佣金和税费后的收入。 有关佣金和税费计算的详细信息,请参阅 *Adapty 分析工作原理* 中的[佣金与税费](how-adapty-analytics-works#commissions-and-taxes)。 --- # File: revenue --- --- title: "营收" description: "使用 Adapty 的订阅洞察功能追踪并分析您应用的营收数据。" --- 收入数据图表显示了订阅和一次性购买所产生的总收入,扣除已退款的部分。这是监控应用财务表现的核心指标。 切换到月度视图,可以评估过去 12 个月的整体趋势。按产品、用户市场细分或归因来源对数据图表进行分组,了解收入的来源构成,同时关注新增与续订的比例,判断业务增长的主要驱动力。 ## 计算方式 \{#calculation\} :::warning 下方计算器**未考虑**[应用商店佣金和税费](how-adapty-analytics-works#commissions-and-taxes)。请将结果与您的**总收入**数据进行对比。 ::: 收入是统计周期内所有已付款交易的总和(包括新订阅、续订、试用转化、一次性购买),减去同期处理的退款:**收入 = 交易总额 − 退款**。 每笔交易的全额在购买当天入账,不会按订阅周期分摊。 该数据图表默认显示总收入。使用[数据图表控件](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)可在总收入、扣除佣金后收入或扣除佣金及税后收入之间切换。 <CompoundCalculator client:load heading="收入" formuLatex="\sum P_i \times Q_i - D" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "单价", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "数量", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "退款金额", variableValue: 35, global: true } ]} rowFormula="price * qty" resultFormula="_sum - refunds" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## 退款处理 \{#refund-handling\} Revenue 会在退款处理当天扣减对应金额,而非原始购买日期。当某个分组或某天的退款额超过新增收入时,数据图表可能显示负值。 有关各数据图表处理退款方式的完整对比,请参阅[各指标如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 货币 \{#currency\} Adapty 以**美元**显示所有货币相关的数据图表,无论原始交易货币是什么。这包括收入、MRR、ARR、ARPU、ARPPU、LTV、趋势预测收入、退款金额,以及同期群和 A/B 测试报告中的收入数据。目前没有切换显示货币的设置。 Adapty 使用来自 [currencylayer.com](https://currencylayer.com/) 的汇率将每笔交易转换为美元,该汇率每 8 小时刷新一次,并**在交易发生时固定**。历史美元值不会随外汇波动而重新计算。 以下位置可查看本地货币金额(按交易明细): - Webhook 中的 `price_local` 和 `currency` 字段 - S3、GCS 和 BigQuery 导出中的 `_local` 列(如 `revenue_local` 和 `proceeds_local`)以及 `currency` - 用户画像页面(按交易明细查看) 如需以本地货币进行财务报表统计,请从导出数据中提取各交易的本地货币金额并自行汇总。 ## 续订定价 \{#renewal-pricing\} Adapty 按产品当前价格计算续费收入,即使某些用户最初订阅时使用的是旧价格。在 App Store Connect 或 Google Play 中修改价格后,看板中现有订阅者的 Revenue、MRR 和 ARR 数据可能与实际收款金额出现偏差——Adapty 会应用新价格,即便商店仍以旧价格向这些用户收费。 如需核实,请将 S3、GCS 或 BigQuery 导出文件中每笔交易的 `price` 字段与看板中相同交易的数据进行对比。导出字段反映的是商店上报的金额(即用户实际支付的价格),看板反映的则是产品当前价格。 ## 可用筛选项与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组依据:时间段、续订状态、产品、国家、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: mrr --- --- title: "MRR" description: "了解并优化 Adapty 中的月度经常性收入(MRR)。" --- 月度经常性收入(MRR)数据图表展示了活跃付费订阅的收入,并将其标准化为月度数据。无论订阅周期长短,它都能反映你的订阅业务所产生的稳定收入。 若要查看每个订阅者同期群随时间推移对经常性收入的贡献,可按首次购买月份对数据图表进行分组,并切换至月度粒度。堆叠面积视图能直观呈现每个同期群逐月的贡献情况。 ## 计算方式 \{#calculation\} :::warning 以下计算器**不考虑**[商店佣金和税费](how-adapty-analytics-works#commissions-and-taxes)。请将结果与您的**总收入**数据进行对比。 ::: MRR 将每笔订阅的收入统一折算为月度等值金额——例如一笔 $240 的年度订阅,每月贡献 $20,而非一次性计入 $240。无论订阅的计费周期如何分布,这种方式都能保持 MRR 的平稳性。 MRR 是所有订阅类型中(价格 × 活跃订阅用户数 ÷ 计费周期月数)的总和。周订阅的计费周期约为 0.23 个月。 <SimpleCalculator client:load heading="MRR" formuLatex="\sum_{subscriptions}^{}\frac{P_s\times N_s}{D_m}" variables={[ { nameInTheFormula: "P_s", variableName: "subscriptionPrice", variableDescription: "价格", variableValue: 10 }, { nameInTheFormula: "N_s", variableName: "activeSubs", variableDescription: "订阅者", variableValue: 1, isInteger: true }, { nameInTheFormula: "D_m", variableName: "duration", variableDescription: "订阅周期", variableValue: 1, options: [ { label: "按周", value: 0.23 }, { label: "按月", value: 1 }, { label: "2个月", value: 2 }, { label: "3个月", value: 3 }, { label: "6个月", value: 6 }, { label: "按年", value: 12 } ] } ]} formulaCalculation="(subscriptionPrice * activeSubs) / duration" isSum={true} defaultRows={[ { subscriptionPrice: 240, activeSubs: 2, duration: 12}, { subscriptionPrice: 30, activeSubs: 10, duration: 1}, { subscriptionPrice: 10, activeSubs: 20, duration: 0.23}, ]} /> MRR 不统计以下不产生周期性收入的产品: - 一次性购买 - 消耗型商品 - 非续期订阅 你的用户群可能通过一次性产品持续产生收入,但由于这些购买本身不具备周期性,因此不计入 MRR。 ## 退款处理 \{#refund-handling\} 当订阅被退款时,MRR 会从该订阅此前被计入的每个日期中扣除相应的贡献值。退款发生后,历史 MRR 数据可能会下降。 有关各数据图表如何处理退款的完整对比,请参阅[各数据图表如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 货币 \{#currency\} Adapty 以**美元**显示所有货币相关的数据图表,无论原始交易货币是什么。这包括收入、MRR、ARR、ARPU、ARPPU、LTV、趋势预测收入、退款金额,以及同期群和 A/B 测试报告中的收入数据。目前没有切换显示货币的设置。 Adapty 使用来自 [currencylayer.com](https://currencylayer.com/) 的汇率将每笔交易转换为美元,该汇率每 8 小时刷新一次,并**在交易发生时固定**。历史美元值不会随外汇波动而重新计算。 以下位置可查看本地货币金额(按交易明细): - Webhook 中的 `price_local` 和 `currency` 字段 - S3、GCS 和 BigQuery 导出中的 `_local` 列(如 `revenue_local` 和 `proceeds_local`)以及 `currency` - 用户画像页面(按交易明细查看) 如需以本地货币进行财务报表统计,请从导出数据中提取各交易的本地货币金额并自行汇总。 ## 续费定价 \{#renewal-pricing\} Adapty 按产品当前价格计算续费收入,即使某些用户最初订阅时使用的是旧价格。在 App Store Connect 或 Google Play 中修改价格后,看板中现有订阅者的 Revenue、MRR 和 ARR 数据可能与实际收款金额出现偏差——Adapty 会应用新价格,即便商店仍以旧价格向这些用户收费。 如需核实,请将 S3、GCS 或 BigQuery 导出文件中每笔交易的 `price` 字段与看板中相同交易的数据进行对比。导出字段反映的是商店上报的金额(即用户实际支付的价格),看板反映的则是产品当前价格。 ## 可用筛选器和分组 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、周期、市场细分、商店、产品和时长。 - ✅ 分组依据:周期、续订状态、产品、国家、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [Revenue](revenue) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arr --- --- title: "ARR" description: "跟踪年度经常性收入(ARR)并优化您的订阅策略。" --- 年度经常性收入数据图表展示了所有有效自动续费订阅的收入折算为一年后的总和。 该数据图表将所有已付费且未到期的订阅视为有效订阅。ARR 是衡量订阅业务增长和预测未来收入的关键数据图表。 ## 计算方式 \{#calculation\} :::warning 下方计算器**未考虑**[商店佣金和税费](how-adapty-analytics-works#commissions-and-taxes)。请将结果与您的**总收入**数据进行对比。 ::: ARR 是将订阅的循环收入年化后得到的数值。当年度订阅是您的主要产品时,ARR 最具参考价值——对于以月度或周度订阅为主的业务,[MRR](mrr) 更能反映实际情况。 ARR 是所有订阅类型的(价格 × 活跃订阅用户数 ÷ 以年为单位的计费周期)之和。月订阅使用 1/12,周订阅使用 1/52。 <SimpleCalculator client:load heading="ARR" formuLatex="\sum \frac{P_s \times U_s}{D_y}" variables={[ { nameInTheFormula: "P_s", variableName: "price", variableDescription: "订阅价格", variableValue: 240 }, { nameInTheFormula: "U_s", variableName: "subs", variableDescription: "活跃付费订阅数", variableValue: 2, isInteger: true }, { nameInTheFormula: "D_y", variableName: "periods", variableDescription: "订阅周期", variableValue: 1, options: [ { label: "每周", value: "1/52" }, { label: "每月", value: "1/12" }, { label: "2个月", value: "2/12" }, { label: "3个月", value: "3/12" }, { label: "6个月", value: "6/12" }, { label: "每年", value: 1 } ] } ]} formulaCalculation="(price * subs ) / periods" isSum={true} defaultRows={[ { price: 240, subs: 2, periods: "1" }, { price: 30, subs: 10, periods: "1/12" }, { price: 10, subs: 20, periods: "1/52" } ]} /> ## 退款处理 \{#refund-handling\} 当某笔订阅被退款时,ARR 会从该笔订阅此前被计入的所有日期中扣除其贡献值。退款到账后,历史 ARR 数据可能会下降。 如需了解各数据图表对退款的完整处理方式,请参阅[数据图表如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 货币 \{#currency\} Adapty 以**美元**显示所有货币相关的数据图表,无论原始交易货币是什么。这包括收入、MRR、ARR、ARPU、ARPPU、LTV、趋势预测收入、退款金额,以及同期群和 A/B 测试报告中的收入数据。目前没有切换显示货币的设置。 Adapty 使用来自 [currencylayer.com](https://currencylayer.com/) 的汇率将每笔交易转换为美元,该汇率每 8 小时刷新一次,并**在交易发生时固定**。历史美元值不会随外汇波动而重新计算。 以下位置可查看本地货币金额(按交易明细): - Webhook 中的 `price_local` 和 `currency` 字段 - S3、GCS 和 BigQuery 导出中的 `_local` 列(如 `revenue_local` 和 `proceeds_local`)以及 `currency` - 用户画像页面(按交易明细查看) 如需以本地货币进行财务报表统计,请从导出数据中提取各交易的本地货币金额并自行汇总。 ## 续费定价 \{#renewal-pricing\} Adapty 按产品当前价格计算续费收入,即使某些用户最初订阅时使用的是旧价格。在 App Store Connect 或 Google Play 中修改价格后,看板中现有订阅者的 Revenue、MRR 和 ARR 数据可能与实际收款金额出现偏差——Adapty 会应用新价格,即便商店仍以旧价格向这些用户收费。 如需核实,请将 S3、GCS 或 BigQuery 导出文件中每笔交易的 `price` 字段与看板中相同交易的数据进行对比。导出字段反映的是商店上报的金额(即用户实际支付的价格),看板反映的则是产品当前价格。 ## 可用筛选条件与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家/地区、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组方式:时间段、续订状态、产品、国家/地区、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arpu --- --- title: "ARPU" description: "分析每用户平均收入(ARPU),优化收入表现。" --- ARPU(每用户平均收入)数据图表显示特定时段内每位用户的平均收入。该指标的计算方式为:将某个同期群的总收入除以该同期群的用户数量。使用 ARPU 可按归因来源、国家或产品对比不同市场细分的收入表现。 ## 计算方式 \{#calculation\} :::warning 以下计算器**未考虑**[商店佣金和税费](how-adapty-analytics-works#commissions-and-taxes)。请将结果与您的**总收入**进行对比。 ::: ARPU 表示您的应用每位用户平均带来的收入,是衡量变现效率的常用指标。 ARPU = 该时期内的收入(扣除退款)÷ 该时期内的应用总用户数。 <CompoundCalculator client:load heading="ARPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "产品价格", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "已购产品数量", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "退款金额", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "总用户数", variableValue: 160, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## 退款处理 \{#refund-handling\} 退款会在处理日期从收入分子中扣除。 有关各数据图表如何处理退款的完整对比,请参阅[各数据图表的退款处理方式](refund-events#how-metrics-handle-refunds)。 ## 货币 \{#currency\} Adapty 以**美元**显示所有货币相关的数据图表,无论原始交易货币是什么。这包括收入、MRR、ARR、ARPU、ARPPU、LTV、趋势预测收入、退款金额,以及同期群和 A/B 测试报告中的收入数据。目前没有切换显示货币的设置。 Adapty 使用来自 [currencylayer.com](https://currencylayer.com/) 的汇率将每笔交易转换为美元,该汇率每 8 小时刷新一次,并**在交易发生时固定**。历史美元值不会随外汇波动而重新计算。 以下位置可查看本地货币金额(按交易明细): - Webhook 中的 `price_local` 和 `currency` 字段 - S3、GCS 和 BigQuery 导出中的 `_local` 列(如 `revenue_local` 和 `proceeds_local`)以及 `currency` - 用户画像页面(按交易明细查看) 如需以本地货币进行财务报表统计,请从导出数据中提取各交易的本地货币金额并自行汇总。 ## 可用筛选条件与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、国家、A/B 测试、市场细分和商店。 - ✅ 分组方式:国家、商店、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [收入](revenue) - [MRR](mrr) - [ARPPU](arppu) - [ARR](arr) --- # File: arppu --- --- title: "ARPPU" description: "了解 ARPPU(每付费用户平均收入)及其对应用变现的影响。" --- 每付费用户平均收入(ARPPU)数据图表展示了每位付费用户带来的平均收入。该数据图表显示的是付费用户实际产生的收入除以用户数,并扣除退款后的结果。按归因对 ARPPU 进行分组,可以了解哪些获客渠道带来了价值更高的付费用户。 ## 计算方式 \{#calculation\} :::warning 以下计算器**不考虑**[渠道佣金和税费](how-adapty-analytics-works#commissions-and-taxes)。请将结果与您的**毛收入**进行比较。 ::: ARPPU 表示每位付费用户的平均收入——由于分母中排除了非付费用户,该值通常远高于 [ARPU](arpu)。 ARPPU 的计算方式为:该时段内的收入(扣除退款)除以该时段内的付费用户数。 <CompoundCalculator client:load heading="ARPPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "产品价格", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "已购产品数量", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "退款金额", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "付费用户数", variableValue: 16, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## 退款处理 \{#refund-handling\} 退款会在处理日期从收入分子中扣除。已购买后申请退款的用户仍会计入付费用户分母,因此大量退款会导致 ARPPU 下降速度超出预期。 各数据图表处理退款方式的完整对比,请参阅[数据图表如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 货币 \{#currency\} Adapty 以**美元**显示所有货币相关的数据图表,无论原始交易货币是什么。这包括收入、MRR、ARR、ARPU、ARPPU、LTV、趋势预测收入、退款金额,以及同期群和 A/B 测试报告中的收入数据。目前没有切换显示货币的设置。 Adapty 使用来自 [currencylayer.com](https://currencylayer.com/) 的汇率将每笔交易转换为美元,该汇率每 8 小时刷新一次,并**在交易发生时固定**。历史美元值不会随外汇波动而重新计算。 以下位置可查看本地货币金额(按交易明细): - Webhook 中的 `price_local` 和 `currency` 字段 - S3、GCS 和 BigQuery 导出中的 `_local` 列(如 `revenue_local` 和 `proceeds_local`)以及 `currency` - 用户画像页面(按交易明细查看) 如需以本地货币进行财务报表统计,请从导出数据中提取各交易的本地货币金额并自行汇总。 ## 可用筛选项与分组方式 \{#available-filters-and-grouping\} :::link 主文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选方式:归因、目标受众、国家、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组方式:时间段、续订状态、产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARR](arr) --- # File: installs --- --- title: "安装量" description: "使用 Adapty 追踪应用安装量并了解其对订阅的影响。" --- 安装量数据图表显示了在所选时间段内安装你的应用的用户数量。什么算作一次安装,以及每次安装如何分组,取决于安装计数设置。 本文介绍如何选择合适的计数模式,以及如何[排查不同分析来源之间的潜在差异](#troubleshooting)。 ## 什么算作安装 \{#what-counts-as-an-install\} 当用户第一次启动应用时,Adapty SDK 会注册一次"安装"并将其发送至 Adapty。 这带来两个影响: - 安装记录在用户首次打开应用时出现在 Adapty 中,这可能是在他们下载应用后数小时乃至数天之后。 - 如果用户下载了应用但从未打开,Adapty 不会将其计入安装数。 您在 **App Settings** 中设置的**上报时区**决定了每次安装归属到哪一天。例如,某次安装发生在 UTC 时间 6 月 1 日 23:30,若上报时区设置为 +02:00,则该安装会归入 6 月 2 日;而 App Store Connect 或 Google Play 可能仍将其显示为 6 月 1 日。 ### 计数模式 \{#counting-modes\} **Installs definition for analytics** 设置决定了什么算作新安装。要修改此设置,请打开 [App Settings → General → Installs definition for analytics](general#4-installs-definition-for-analytics)。 | 模式 | 计数规则 | 示例 | 第三方数据图表 | 可能存在的差异 | | --- | --- | --- | --- | --- | | **新增 device_ids**(推荐) | **每次应用安装**均计数,包括重新安装。身份验证、用户画像创建及版本升级不计入。 | 同一用户在 5 台设备上安装 = 5 次安装。<br /><br />在同一设备上重新安装 = 2 次安装。 | App Store:<br />**Total Active Devices**<br /><br />Google Play:**Devices** | **高于应用下载量**:重新安装频繁时。<br /><br />**低于应用下载量**:大量用户下载后未打开应用时。 | | **新增 customer_user_ids** | 仅计算每位[已识别用户](identifying-users)的**首次安装**。多设备登录和匿名用户不计入。 | 同一用户在 5 台设备上安装 = 1 次安装。<br /><br />重新安装后重新登录 = 不计为新安装。<br /><br />未登录账号使用应用 = 不计为新安装。 | 应用身份验证系统的注册数据 | **若完全不识别用户,则始终为空。** | | **Adapty 中的新增用户画像**(旧版) | 统计每次安装和重新安装,**以及退出登录时创建的匿名用户画像**。 | 同一用户、同一设备,退出 3 次 = 4 次安装。 | 无 | **高于所有外部数据图表**。每次退出登录时创建的匿名用户画像均计为一次安装。 | 使用 **New device_ids**,除非你有特定原因需要切换。 ## 故障排查 \{#troubleshooting\} ### Adapty 的计数高于 App Store Connect 或 Google Play \{#adaptys-count-is-higher-than-app-store-connect-or-google-play\} 两个可能的原因: - **重新安装。** 如果你的[计数模式](#counting-modes)设置为 **New device_ids**,Adapty 会同时统计首次启动和后续重新安装。App Store Connect 的"Total Downloads"只统计初始下载。 - **首次启动日期 ≠ 下载日期。** 应用商店按下载日期归因。延迟打开应用的用户会被计入不同的日期。 为了进行更清晰的对比,请打开 **App Store Connect → Total Active Devices** 或 **Google Play → Devices**。这些数据图表以设备为维度,与 Adapty 的 **New device_ids** 模式更接近。 ### Adapty 的计数为零 \{#adaptys-count-is-zero\} 如果你的计数模式为 **New customer_user_ids**,但你没有 <InlineTooltip tooltip="验证用户身份">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)、[Kotlin Multiplatform](kmp-quickstart-identify)、[Capacitor](capacitor-quickstart-identify)</InlineTooltip>,Adapty 将不会记录任何安装。在该模式下,匿名安装会被排除在外。请切换至 **New device_ids**,或实现用户身份验证。 ### Adapty 的统计数据与 AppsFlyer 或 Adjust 不一致 \{#adaptys-count-differs-from-appsflyer-or-adjust\} MMP 通过自身 SDK 初始化或首次触点事件来归因安装,这些事件的触发时机与 Adapty SDK 首次启动的时机不同——出现一定差异是正常的。 ## 可用的筛选与分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选方式:归因、国家、A/B 测试、市场细分和商店。 - ✅ 分组方式:国家、商店、市场细分和归因。 ## 类似数据图表 \{#similar-metrics\} 如需并排对比这些数据图表,请参阅[数据图表对比表](metric-comparison-table#subscribers-and-conversion)。 - [新增订阅](reactivated-subscriptions) - [活跃订阅](active-subscriptions) - [新增试用](new-trials) --- # File: active-subscriptions --- --- title: "活跃订阅" description: "通过 Adapty 强大的分析功能,监控和管理活跃订阅。" --- "有效订阅"数据图表显示在每个所选周期结束时尚未到期的唯一付费订阅数量。它包含已开始且当前仍有效的常规(未过期)应用内订阅,同时排除免费试用和已取消续订的订阅。该图表可作为订阅用户规模及增长情况的参考指标。 ## 计算方式 \{#calculation\} 活跃订阅数据图表统计每个周期结束时已付费且未过期的订阅数量。对于没有宽限期的订阅,当下次续费日期过后未能成功续费,即视为已过期。 例如:上个月末有 500 个活跃订阅,本月新增 50 个,到期 25 个,则本月末共有 525 个活跃订阅。 ## 退款处理 \{#refund-handling\} 当订阅被退款时,Adapty 会将其从活跃计数中移除——无论是当前数据还是过去日期的历史数据,均会同步更新。 完整的各数据图表退款处理对比,请参阅[数据图表如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 可用筛选器与分组 \{#available-filters-and-grouping\} :::link 主要文章:[Analytics controls](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品及时长。 - ✅ 分组依据:时间段、续订状态、产品、国家、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分及归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排对比这些数据图表,请参阅[数据图表对比表](metric-comparison-table#subscribers-and-conversion)。 - [已流失(已到期)订阅](churned-expired-subscriptions) - [已取消订阅](cancelled-subscriptions) - [非订阅](non-subscriptions) --- # File: reactivated-subscriptions --- --- title: "新订阅" description: "在 Adapty 中追踪新订阅,监控首次转化和免费试用付费转化情况。" --- 新增订阅数据图表显示应用内新增(首次激活)订阅的数量。该数据图表统计特定时间段内新增的订阅数,包括全新开始的订阅以及由免费试用转换为付费订阅的情况,但不包括订阅续期或重新启动的订阅。 ## 计算方式 \{#calculation\} 新增订阅数据图表统计的是在选定时间段内首次激活的订阅数量,包括全新开始的订阅,以及由免费试用转化为付费订阅的情况。 ## 退款处理 \{#refund-handling\} 新增订阅**不**扣除退款——该计数包含后续被退款的订阅。如需评估净影响,请与[退款事件](refund-events)进行比较。 有关各数据图表如何处理退款的完整对比,请参阅[数据图表如何处理退款](refund-events#how-metrics-handle-refunds)。 ## 可用的筛选和分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[数据分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选方式:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、应用商店、产品和时长。 - ✅ 分组方式:续订状态、产品、国家、应用商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#subscribers-and-conversion)。 - [活跃订阅](active-subscriptions) - [已流失(过期)订阅](churned-expired-subscriptions) - [已取消订阅](cancelled-subscriptions) - [非订阅](non-subscriptions) --- # File: non-subscriptions --- --- title: "非订阅商品" description: "了解如何在 Adapty 中管理非订阅商品并高效追踪用户购买情况。" --- 非订阅商品数据图表统计非自动续费订阅的应用内购买,包括消耗型商品、非消耗型商品和非续费订阅,不含续费记录。 :::note "非订阅"比"一次性购买"涵盖范围更广——消耗型商品和非续订订阅都可以多次购买。 ::: ## 计算方式 \{#calculation\} 每笔非订阅类应用内购买都属于以下三种类型之一: - **消耗型商品**:用户可多次购买的商品,例如钓鱼应用中的鱼食或游戏内额外货币。 - **非消耗型商品**:用户购买一次、永久使用的商品,例如游戏中的赛道或去广告版本。 - **非续订订阅**:到期后不会自动续订的订阅,例如一年期内容目录访问权限。内容可以是静态的,但订阅到期后不会续订。 :::note 此数据图表仅统计购买事件,不扣除已退款的购买。如果您有经常发生退款的非订阅产品,显示的数量将高于实际产生收入的购买数量。 ::: ## 可用筛选与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[Analytics 控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家、付费墙、A/B 测试、版位、市场细分、应用商店和产品。 - ✅ 分组方式:产品、国家、应用商店、付费墙、目标受众、版位、市场细分和归因。 ## 类似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [活跃订阅](active-subscriptions) - [新增订阅](reactivated-subscriptions) - [流失(已过期)订阅](churned-expired-subscriptions) - [已取消订阅](cancelled-subscriptions) --- # File: cancelled-subscriptions --- --- title: "订阅续费已取消" description: "使用 Adapty 的管理工具高效处理已取消的订阅。" --- "订阅续订已取消"数据图表显示了已关闭自动续订状态(即用户主动取消)的订阅数量。当订阅的自动续订状态被关闭后,该订阅在下一个周期将不再自动续订。但用户仍可访问应用的高级功能,直到当前周期结束。 ## 计算方式 \{#calculation\} 订阅续订取消数据图表统计的是在指定时间段内关闭自动续订的订阅数量。用户在当前计费周期结束前仍可享有高级权限,但此后订阅不会自动续订。 ## 可用筛选器与分组 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家/地区、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组方式:产品、国家/地区、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排对比这些数据图表,请参阅[数据图表对比表](metric-comparison-table#churn)。 - [活跃订阅](active-subscriptions) - [已流失(已过期)订阅](churned-expired-subscriptions) - [新订阅](reactivated-subscriptions) - [非订阅](non-subscriptions) --- # File: churned-expired-subscriptions --- --- title: "已流失(过期)的订阅" description: "管理已流失和过期的订阅,提升用户留存率。" --- 已流失(已到期)订阅数据图表显示了已到期的订阅数量,即用户不再享有应用高级功能访问权限的情况。通常,这发生在用户决定在订阅期结束时停止付费,或遭遇支付问题时。可按到期原因分组,以区分主动流失与因账单问题导致的流失。 ## 计算方式 \{#calculation\} 流失(到期)订阅数据图表统计的是在该时间段内到期的订阅数量——即用户失去高级功能访问权限的情况。这包括主动选择不续订的用户,以及因付款问题而失去订阅的用户。 ## 可用筛选器与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[数据分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组方式:到期原因、产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需对这些数据图表进行并排比较,请参阅[数据图表对比表](metric-comparison-table#churn)。 - [活跃订阅](active-subscriptions) - [新增订阅](reactivated-subscriptions) - [已取消订阅](cancelled-subscriptions) - [非订阅](non-subscriptions) --- # File: active-trials --- --- title: "活跃试用" description: "通过 Adapty 分析功能追踪和管理活跃的订阅试用。" --- Adapty 中的活跃试用数据图表显示在某一时间段末尾仍处于有效期内的免费试用数量。"活跃"指尚未到期的订阅,即用户仍可访问应用付费功能。 ## 计算方式 \{#calculation\} 活跃试用数据图表统计每个周期结束时尚未过期的免费试用数量。取消自动续订不会将试用从计数中移除——只有过期才会。 例如:昨天有 100 个活跃试用,今天新增 10 个,今天有 5 个过期,则今天的活跃试用数为 105 个。 ## 可用的筛选和分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组方式:周期、续订状态、产品、国家、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#subscribers-and-conversion)。 - [新增试用](new-trials) - [试用续订已取消](trials-renewal-cancelled) - [已到期试用](expired-churned-trials) --- # File: new-trials --- --- title: "新试用" description: "管理新订阅试用期,优化试用转付费转化率。" --- 新试用数据图表展示所选时间段内激活的试用次数。可用于追踪广告投放及其他获客活动带来的试用量。 ## 计算方式 \{#calculation\} 新增试用数量统计的是在该时间段内开始试用的用户数,无论这些试用在时间段结束时是否仍处于活跃状态。 例如,如果 5 月有 50 位用户开始试用,则 5 月的数据点显示为 50——即使在你查看数据图表时,其中一些试用已到期或已转化为付费用户。 ## 可用筛选条件与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排对比这些数据图表,请参阅[数据图表对比表格](metric-comparison-table#subscribers-and-conversion)。 - [活跃试用](active-trials) - [已取消试用续订](trials-renewal-cancelled) - [已到期试用](expired-churned-trials) --- # File: trials-renewal-cancelled --- --- title: "试用期续订已取消" description: "通过 Adapty 了解试用期续订、取消及订阅流程的相关信息。" --- "试用期续订已取消"数据图表显示已取消续订(由用户主动取消)的试用期数量。当试用期的自动续订被关闭后,该试用期将不会自动转换为付费订阅,但用户仍可在当前周期结束前继续使用应用的高级功能。 ## 计算方式 \{#calculation\} 试用续订取消数据图表统计的是在统计周期内被用户关闭自动续订的试用。用户仍可使用试用权益直至到期,但试用不会自动转为付费订阅。 ## 可用的筛选和分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#churn)。 - [新增试用](new-trials) - [活跃试用](active-trials) - [已到期试用](expired-churned-trials) --- # File: expired-churned-trials --- --- title: "已过期(已流失)试用" description: "通过 Adapty 分析功能有效管理已过期和已流失的试用。" --- "已过期(已流失)试用"数据图表展示已过期的试用数量,这些用户将无法继续使用应用的高级功能。大多数情况下,这是因为用户决定不付费购买,或者遇到了付款问题。 ## 计算方式 \{#calculation\} 过期试用数据图表统计在该时间段内结束的试用次数——即用户失去了高级功能访问权限。这包括主动选择不续订的用户,以及因账单问题导致转化失败的用户。 按 **Expiration reason** 分组,可区分主动流失与账单问题导致的流失。 ## 可用的筛选与分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组方式:到期原因、产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#churn)。 - [新增试用](new-trials) - [活跃试用](active-trials) - [已取消续订的试用](trials-renewal-cancelled) --- # File: refund-events --- --- title: "退款事件" description: "在 Adapty 中管理退款事件,降低流失率并优化收入。" --- 退款事件数据图表展示了有多少购买和订阅发生了退款。Adapty 将每个退款事件关联到退款发起的日期,而非订阅开始的日期。 ## 计算方式 \{#calculation\} Adapty 统计所选时间段内所有已退款的购买或订阅。每笔退款按实际发生日期计入,而非订阅开始日期。试用期的退款不计入统计,因为试用期本身不产生收入。 ## 数据图表如何处理退款 \{#how-metrics-handle-refunds\} 不同的数据图表对退款的处理方式各不相同。同一笔退款事件,在某张图表中可能立即减少当期数值,在另一张图表中可能追溯修改历史数据,而在第三张图表中则完全不受影响。下表列出了各数据图表的处理规则。 | 数据图表 | 是否应用退款? | 归因日期 | 是否可为负值? | 备注 | | --- | --- | --- | --- | --- | | [收入](revenue) | 是 | 退款日期——而非原始购买日期 | 是——当退款超过当日新增收入时 | 收入 = 总交易额 − 退款。 | | [MRR](mrr) | 是,追溯应用 | 订阅从其所有活跃周期中移除 | 否 | 退款发生后,历史周期数值可能下降。 | | [ARR](arr) | 是,追溯应用 | 同 MRR | 否 | 退款发生后,历史周期数值可能下降。 | | [ARPU](arpu) | 是 | 退款日期 | 是(在退款较多的时期) | 退款从收入分子中扣除。 | | [ARPPU](arppu) | 是,仅分子 | 退款日期 | 是(在退款较多的时期) | 退款从收入分子中扣除。已退款用户仍计入付费用户分母,因此大量退款会导致 ARPPU 下降幅度超出预期。 | | [活跃订阅](active-subscriptions) | 是,追溯应用 | 订阅从计数中移除 | 否 | | | [新增订阅](reactivated-subscriptions) | **否** | — | 否 | 计数包含后续已退款的订阅。可与[退款事件](refund-events)对比,了解净影响。 | | [退款金额](refund-money) / [退款事件](refund-events) | 退款**即为**数据本身 | 退款日期 | 否(始终 ≥ 0) | | | [留存率](analytics-retention) | **否** | — | 否 | 已退款用户仍计入留存曲线。这可能导致同一同期群的留存率看起来高于[活跃订阅](active-subscriptions)或[收入](revenue)。 | | [同期群收入](analytics-cohorts) | 是,累计应用 | 退款日期 | 否(累计扣减不会使同期群收入降至零以下) | 退款发生时从同期群收入中扣除。其他同期群指标的处理方式,请参阅[同期群 > 退款处理](analytics-cohorts#refund-handling)。 | | [付费墙数据图表](paywall-metrics) / [A/B 测试数据图表](results-and-metrics)(计数) | **否** | — | 否 | 这些页面上的订阅者、付费订阅者及 ARPPU 计数不扣除退款。 | | GCS / S3 导出 | 退款作为独立事件行 | `event_datetime` = 退款时间戳 | 聚合时净值列可为负值 | 退款行携带 `is_refund = true`(S3/GCS)或事件类型 `subscription_refunded`(webhooks)。 | ### 负值 \{#negative-values\} 在聚合视图中(Revenue 数据图表、导出的自定义分析),当某个时间段或分组内的退款金额超过同期新增收入时,该数据图表可能显示为负值。这不是 bug,而是按设计逻辑正常运算的结果。 举例来说:某个国家/地区在周二没有新的购买记录,但当天处理了一笔 100 美元的退款(对应此前的旧订单)。那么该国家/地区周二的收入就会显示为 −$100。 ## 可用筛选条件与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、退款原因、国家/地区、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组方式:退款原因、产品、国家/地区、商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [退款金额](refund-money) - [账单问题](billing-issue) - [宽限期](grace-period) --- # File: refund-money --- --- title: "退款金额" description: "了解如何在 Adapty 中处理订阅退款而不损失收入。" --- 退款金额数据图表显示所选时间段内的退款总额。Adapty 将每笔退款事件与其发生日期关联,因此该时间段的收入会相应减少。 ## 计算方式 \{#calculation\} Adapty 仅统计产生收入的交易——新增付费订阅、续订和一次性购买。免费试用不产生收入且无法退款,因此不计入统计。每笔退款金额与其处理日期挂钩,因此收入减少会体现在对应时间段内。 :::info 退款金额在扣除应用商店手续费之前计算。 ::: ## 可用筛选器与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、退款原因、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、应用商店、产品及时长。 - ✅ 分组方式:退款原因、产品、国家、应用商店、付费墙、目标受众、版位、时长、优惠类型、优惠折扣类型、优惠 ID、市场细分及归因。 ## 退款申请管理 \{#refund-request-management\} 退款挽留功能帮助 Adapty 用户通过自动化流程更高效地处理来自 Apple App Store 的退款申请,节省时间、减少营收损失。借助实时通知和可操作的洞察数据,该工具让您在符合 Apple 政策的前提下更轻松地应对退款申请。 了解更多关于[退款挽留](refund-saver)的内容。 ## 相似数据图表 \{#similar-metrics\} 如需并排对比这些数据图表,请参阅[数据图表对比表](metric-comparison-table#revenue)。 - [退款事件](refund-events) - [账单问题](billing-issue) - [宽限期](grace-period) --- # File: grace-period --- --- title: "宽限期" description: "了解订阅宽限期的工作原理,提升用户留存率。" --- 宽限期数据图表显示了因[账单问题](billing-issue)而进入宽限期状态的订阅数量。在此期间,订阅保持激活状态,应用商店会尝试向用户收取费用。若在宽限期结束前仍未成功收款,订阅将进入账单问题状态。 ## 计算方式 \{#calculation\} 宽限期数据图表统计在所选时间范围内进入宽限期的订阅数量。宽限期从订阅续费付款失败时开始,周订阅最长持续 6 天,其他计费周期最长持续 16 天。若在此期间付款成功,订阅将正常续期;否则,订阅将进入[账单问题](billing-issue)状态。 ## 可用的筛选与分组选项 \{#available-filters-and-grouping\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、国家、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组方式:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相似数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#billing-issues-and-revenue-recovery)。 - [退款金额](refund-money) - [退款事件](refund-events) - [账单问题](billing-issue) --- # File: grace-period-converted --- --- title: "宽限期转化" description: "追踪进入宽限期并在宽限期结束前成功续订的订阅数量。" --- **Grace period converted** 数据图表显示进入[宽限期](grace-period)状态后,在宽限期结束前成功续订的订阅数量。 ### 计算方式 \{#calculation\} 宽限期转化数据图表显示处于宽限期内的用户每日订阅续订数量。 宽限期从订阅因付款失败进入账单问题状态时开始,在指定时间后结束(每周订阅为 6 天,其他所有订阅为 16 天),或在成功收到付款时结束。该数据图表有助于了解宽限期功能的有效性,并可帮助识别付款处理或订阅管理中的潜在问题。 ### 可用筛选项 \{#available-filters\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、退款原因、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ### 宽限期转化数据图表用途 \{#grace-period-converted-chart-usage\} 使用此数据图表可追踪宽限期功能在恢复存在付款问题的订阅方面的效果。通过监控一段时间内的转化趋势,您可以识别付款解决模式,并评估在宽限期内对付款更新流程或沟通策略所做更改的影响。 ### 相似数据图表 \{#similar-metrics\} - [账单问题](billing-issue) - [账单问题转化](billing-issue-converted) - [账单问题转化收入](billing-issue-converted-revenue) - [宽限期](grace-period) - [宽限期转化收入](grace-period-converted-revenue) - [退款金额](refund-money) - [退款事件](refund-events) --- # File: grace-period-converted-revenue --- --- title: "宽限期转化收入" description: "追踪宽限期转化的总收入。" --- **Grace period converted revenue** 数据图表展示了来自[宽限期转化](grace-period-converted)所产生的收入:即进入[宽限期](grace-period)状态后,在宽限期结束前成功续费的订阅所带来的收入。 ### 计算方式 \{#calculation\} 宽限期转化收入数据图表展示处于宽限期的用户每日通过订阅续费产生的收入。 宽限期在订阅因付款失败进入账单问题状态时开始,并在指定时间后结束(周订阅为 6 天,其他所有订阅为 16 天),或在成功收到付款时结束。该数据图表有助于了解宽限期功能的有效性,并可帮助识别付款处理或订阅管理中的潜在问题。 ### 可用筛选器 \{#available-filters\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选条件:归因、目标受众、退款原因、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品和时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ### 宽限期转化收入数据图表用途 \{#grace-period-converted-revenue-chart-usage\} 使用此数据图表,通过追踪从存在付款问题的订阅中回收的收入,衡量宽限期功能的财务影响。这有助于您量化宽限期策略的有效性,并评估实施宽限期相关功能或沟通措施的投资回报率。 ### 相关数据图表 \{#similar-metrics\} - [账单问题](billing-issue) - [账单问题已转化](billing-issue-converted) - [账单问题转化收入](billing-issue-converted-revenue) - [宽限期](grace-period) - [宽限期转化](grace-period-converted) - [退款金额](refund-money) - [退款事件](refund-events) --- # File: billing-issue --- --- title: "账单问题" description: "使用 Adapty 的支持工具解决订阅账单问题。" --- 账单问题数据图表显示了进入"账单问题"状态的订阅数量。这一状态通常在应用商店(如 Apple 或 Google)因某种原因无法向订阅者收款时触发,常见原因包括信用卡过期或余额不足等。 ## 计算方式 \{#calculation\} 计费问题数据图表统计的是在所选周期内进入计费问题状态的订阅数量。当 App Store 或 Google Play 无法处理续费付款时(通常是因为信用卡过期或余额不足),订阅就会进入此状态。处于计费问题状态期间,订阅不处于活跃状态。 如果启用了[宽限期](grace-period)功能,订阅只会在宽限期到期且仍未完成付款后,才进入计费问题状态。 ## 可用筛选与分组方式 \{#available-filters-and-grouping\} :::link 主要文章:[Analytics 控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、国家、付费墙、A/B 测试、版位、周期、市场细分、商店、产品和时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分和归因。 ## 相关数据图表 \{#similar-metrics\} 如需并排比较这些数据图表,请参阅[数据图表对比表](metric-comparison-table#billing-issues-and-revenue-recovery)。 - [账单问题已转化](billing-issue-converted) - [账单问题转化收入](billing-issue-converted-revenue) - [退款金额](refund-money) - [退款事件](refund-events) - [宽限期](grace-period) - [宽限期已转化](grace-period-converted) - [宽限期转化收入](grace-period-converted-revenue) --- # File: billing-issue-converted --- --- title: "账单问题已转化" description: "跟踪在账单周期结束前解决的账单问题数量。" --- 账单问题已转化数据图表显示每日进入[账单问题](billing-issue)状态后、在账单周期结束前完成续费的订阅数量。 ### 计算方式 \{#calculation\} 账单问题转化数据图表显示的是:在当前计费周期内进入[账单问题](billing-issue)状态、并在当天成功续订的订阅数量。 当商店(如 Apple、Google)因某种原因(例如信用卡过期或余额不足)无法向订阅者收款时,订阅将进入"账单问题"状态。在此状态下,订阅不被视为有效订阅。如果在商店设置中启用了宽限期功能,则订阅只有在宽限期到期后才会进入"账单问题"状态。 ### 可用筛选条件 \{#available-filters\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、退款原因、国家/地区、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、应用商店、产品及时长。 - ✅ 分组依据:产品、国家/地区、应用商店、付费墙、目标受众、版位、时长、市场细分及归因。 ### 账单问题已转化数据图表的使用 \{#billing-issue-converted-chart-usage\} 使用此数据图表可跟踪宽限期到期后,账单问题在结算周期内的解决效率。通过监控一段时间内的解决趋势,您可以识别付款回收的规律,并评估支付重试逻辑或账单问题期间沟通策略变更所带来的影响。 ### 相关数据图表 \{#similar-metrics\} - [账单问题](billing-issue) - [账单问题已转化收入](billing-issue-converted-revenue) - [退款金额](refund-money) - [退款事件](refund-events) - [宽限期](grace-period) - [宽限期已转化](grace-period-converted) - [宽限期已转化收入](grace-period-converted-revenue) --- # File: billing-issue-converted-revenue --- --- title: "账单问题转化收入" description: "使用 Adapty 的支持工具解决订阅账单问题。" --- **Billing issue converted revenue** 数据图表展示了[账单问题转化](billing-issue-converted)所产生的收入,即进入[账单问题](billing-issue)状态后、在账单周期结束前成功续订的订阅所对应的收入。 ### 计算方式 \{#calculation\} **Billing issue converted revenue** 数据图表展示当天成功续费的订阅所带来的每日收入,这些订阅在本计费周期内曾进入[账单问题](billing-issue)状态。 当应用商店(如 Apple、Google)因某些原因(例如信用卡过期或余额不足)无法向订阅者扣款时,订阅将进入「账单问题」状态。在「账单问题」状态下,订阅被视为非活跃状态。如果在商店设置中启用了宽限期功能,订阅将在宽限期到期后才会进入「账单问题」状态。 ### 可用筛选条件 \{#available-filters\} :::link 主要文章:[Analytics controls](controls-filters-grouping-compare-proceeds) ::: - ✅ 筛选依据:归因、目标受众、退款原因、国家、优惠类型、优惠 ID、优惠折扣类型、付费墙、A/B 测试、版位、时间段、市场细分、商店、产品及时长。 - ✅ 分组依据:产品、国家、商店、付费墙、目标受众、版位、时长、市场细分及归因。 ### 账单问题转化收入数据图表的用途 \{#billing-issue-converted-revenue-chart-usage\} 使用此数据图表可衡量已解决账单问题的财务影响,通过追踪宽限期到期后从订阅中恢复的收入,帮助您量化账单问题恢复策略的有效性,并评估实施账单重试机制或定向用户沟通的投资回报率。 ### 相关数据图表 \{#similar-metrics\} - [账单问题](billing-issue) - [账单问题转化](billing-issue-converted) - [退款金额](refund-money) - [退款事件](refund-events) - [宽限期](grace-period) - [宽限期转化](grace-period-converted) - [宽限期转化收入](grace-period-converted-revenue) --- # File: ltv --- --- title: "Lifetime Value (LTV)" description: "Learn how to calculate and optimize Lifetime Value (LTV) in Adapty." --- 已实现的 LTV(生命周期价值)每付费用户数据图表,展示的是某一付费用户同期群在扣除退款后实际产生的收入,除以该同期群中付费用户的数量。换句话说,这张数据图表告诉你平均每位付费用户为你带来了多少收入。 Adapty 设计 LTV 数据图表,旨在回答以下几个关于应用收入与用户行为的重要问题: 1. 每个同期群在其生命周期内为您的应用带来了多少收入? 2. 一个同期群在哪个时间点实现盈亏平衡? 3. 如何优化应用的营销和获客支出,吸引高 LTV 的优质用户? 4. 收回新用户获取成本需要多长时间? LTV 数据图表基于我们通过 SDK 和应用内事件收集的数据进行分析。 通过这些信息,您可以深入了解订阅的表现情况,以及在特定时间段内订阅者带来的收入。您可以利用这些数据,对订阅方案、广告投入和用户获取策略做出更明智的决策。此外,筛选器支持按国家、归因及其他维度对数据进行市场细分,帮助您更精细地了解用户群体。 ### 按续订周期查看 LTV \{#ltv-by-renewals\} **LTV by renewals** 视图展示与订阅周期 (P) 相关的数据,具体捕捉的是用户首次付款的时间节点。对于按周订阅,这对应于下一个每周订阅周期。 ### 按天数查看 LTV \{#ltv-by-days\} **LTV by days** 视图按日、周或月为间隔对数据进行整理和筛选。它展示在特定日期、周或月安装应用的所有用户产生的总收入,除以同一时期内付费用户的数量。该视图为收入追踪提供了宝贵洞察,并有助于全面了解用户随时间的行为变化。 ### 同期群长度与时间范围 \{#cohort-length-and-time-frame\} 两个时间设置共同决定表格显示的内容: - **Time frame**(时间范围)——日期区间,在表格上方的日历中设置。 - **Cohort length**(同期群长度)——每行的粒度:天、周、月、季度或年。选择"月"时,每行对应一个月的安装数据。 这两个设置相互独立。举个例子:6 个月的时间范围搭配按月划分的同期群长度,表格会有 6 行;1 年的时间范围搭配按周划分的同期群长度,则会有 52 行。 ### 计算方式 \{#calculation\} 已实现 LTV 的计算方式为:每个用户同期群产生的总收入减去退款金额。 _日/周/月 LTV = 在该日/周/月安装应用的所有付费用户产生的收入 / 在该日/周/月安装应用的付费用户数量_ LTV 计算包含升级、降级和重新激活等情况,例如用户变更订阅计划或定价。它会综合考虑初始订阅以及基于更新后计划的后续续订所产生的收入。 ### 可用的分组与筛选 \{#available-grouping-and-filtering\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: 筛选和分组均可应用于 LTV 数据图表的续订视图和天数视图,帮助你深入分析特定同期群,了解其随时间变化的行为规律。 - ✅ 筛选条件:归因、目标受众、国家/地区、付费墙、A/B 测试、版位、市场细分、商店、产品和时长。 - ✅ 分组方式:产品、国家/地区、商店、时长、市场细分和同期群(按天、周、月或年)。 Adapty 中的已实现 LTV 数据图表可帮助你深入了解用户行为、优化营销策略、追踪收入表现,并基于数据做出决策,从而最大化客户的长期价值。 --- # File: analytics-cohorts --- --- title: "同期群分析" description: "使用 Adapty 的同期群分析功能,跟踪用户参与度和订阅趋势。" --- Adapty 的同期群分析旨在回答以下几个重要问题: 1. 同期群在哪天开始盈利? 2. 特定同期群为应用带来了多少收入? 3. 我最多可以花多少钱来获取一个付费用户? 4. 广告投入需要多长时间才能回本? 同期群功能使用我们通过 SDK 和应用商店通知收集的应用数据,无需你进行任何额外配置。 ## 按续订次数或按天数划分的同期群 \{#cohorts-by-renewals-or-by-days\} 你可以按续订次数或按天数来分析同期群。切换该控件会改变列的标题,分析思路也随之不同。 按**天**追踪可帮助您深入了解预算安排和付款时间线,对追踪消耗型商品或一次性购买等非订阅类产品尤为实用。在该模式下,表格单元格中的蓝色往往集中在行的中间位置,原因有两点:一是按天查看同期群能够较早显示与短期产品相关的付款(而在续订视图中,这些付款会与月度和年度续订合并显示);二是延迟付款会影响分布规律,因为部分用户的实际付款时间晚于预期。 而按**续订**追踪则展示了同期群从一次付款到下一次付款的留存与流失情况,不考虑具体日期。因此,延迟付款的用户(可能延迟数月)会被计入其所在订阅周期的数量。这种方式虽然不能反映实际的日历收益情况,但在分析同期群的留存与流失、洞察用户行为方面更为便捷。 根据需要选择合适的模式,或同时使用两种模式,以获取更多结论和思路。 ## Adapty 如何构建同期群 \{#how-adapty-builds-cohorts\} 下面以续订同期群为例,说明表格的构建方式。构建同期群需要两个维度:应用安装量和交易量(购买量)。同期群的每一行代表一个特定时间区间,从一天到一年不等。每行的起点是在该时间区间内安装应用并激活订阅或完成永久授权/一次性购买的用户数量。 行中每一列显示的是续订到该周期的用户数量。M3 表示第 3 个月,即订阅者已连续续订 3 次;W7 表示第 7 周;Y2 表示第 2 年。有时你会在同期群中看到 P2,P 代表订阅周期。当同一同期群中包含多个不同续订周期的产品时,Adapty 会用 P 代替 W/M/Y 显示。 我们使用渐变色来突出同期群数值之间的差异。数值越大,颜色越深。 在下图中,您可以看到一个典型的同期群。 1. 此同期群仅展示每周产品的数据(标注 #1)。 2. 它不排除手续费,以绝对值显示收入(标注 #2)。 3. 当前时间周期为最近 6 个月,同期群长度为 1 个月(标注 #3)。 4. **Total** 行(标注 #4)显示每个周期的累计值。**Total** 行第一个单元格中的 $442K,汇总了所有月份(11 月、12 月等)直至时间段末尾的第一周期(订阅激活)收入。Total 单元格显示整个时间段内安装应用的用户总数。 5. Nov 2023 行的第一列(标注 #5)显示 2023 年 11 月安装应用的用户在第一周期(订阅激活)产生的收入 $37.7K。2023 年 11 月安装应用的用户数量为 95,129,显示在表头列中。 Nov 2023 行的第二列显示 2023 年 11 月安装应用的用户在第 2 周(订阅续费至第 2 周)产生的收入 $8.77K。 6. 表格中可查看总收入、ARPU、ARPPU 和 ARPAS(标注 #6)。本文稍后将详细介绍这些指标。 7. 可通过表格右侧的 **Columns** 下拉字段配置显示的列(标注 #7)。 8. 表格右上方(标注 #8)还有一个下拉字段,用于针对特定同期群分析计算应用商店佣金和税费。你可以在[本文](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)中了解 Adapty 如何计算应用商店佣金和税费。从下拉菜单中选择对应选项后,收入数据将据此重新计算。 9. 在表格右侧,可查看预测收入(Predicted Revenue)和预测生命周期价值(Predicted LTV)(标注 #9)。**Predicted Revenue** 字段估算某一订阅用户同期群在特定时间段内产生的总收入,**Predicted LTV** 字段则代表同期群中每位用户的预期价值。 将鼠标悬停在同期群中的任意单元格上,可查看该时段的详细数据图表。 背景带有斜线的单元格表示该时段尚未结束,其中的数值可能还会继续增长。 ## 同期群长度与时间范围 \{#cohort-length-and-time-frame\} 两个时间设置共同决定表格显示的内容: - **Time frame**(时间范围)——日期区间,在表格上方的日历中设置。 - **Cohort length**(同期群长度)——每行的粒度:天、周、月、季度或年。选择"月"时,每行对应一个月的安装数据。 这两个设置相互独立。举个例子:6 个月的时间范围搭配按月划分的同期群长度,表格会有 6 行;1 年的时间范围搭配按周划分的同期群长度,则会有 52 行。 ## 筛选器、数据图表、同期群细分与 CSV 导出 \{#filters-metrics-cohort-segments-and-export-in-csv\} :::link 主要文章:[分析控件](controls-filters-grouping-compare-proceeds) ::: 默认情况下,Adapty 使用所有购买数据来构建同期群。你可以按产品时长、特定产品、国家、商店、付费墙、市场细分和归因数据进行筛选。 在控制面板右侧,有一个将同期群数据导出为 CSV 的按钮。你可以在 Excel 或 Google Sheets 中打开它,也可以将其导入到自己的分析系统中。 同期群中可以展示 6 项数据图表:订阅数、付费用户数、营收、ARPU、ARPPU 和 ARPAS。你可以选择以绝对值显示,也可以显示相对于同期群起始时间的变化幅度。 ## 订阅、付费用户、总收入、ARPU、ARPPU 与 ARPAS \{#subscriptions-payers-total-revenue-arpu-arppu-and-arpas\} **订阅**是指在所选时间范围内,某一同期群的活跃订阅数、永久授权购买数及一次性购买数的总和。监控这一数据图表有助于了解用户行为以及产品方案的实际效果,从而帮助你优化产品策略、精准营销,并提升收入。 **付款用户数**是指同期群内完成过购买的用户总数。它帮助你了解有多少独立用户为你的收入做出了贡献。对于非订阅购买占比较高的应用来说,这一数据图表能更直观地反映产品的实际覆盖范围——究竟是广泛的用户群体在持续购买,还是收入主要来自少数重复购买的用户。掌握付款用户数有助于评估用户参与度、制定精准营销计划,并优化收入策略。 **总收入**是在所选时间范围(2022 年 11 月 25 日 — 2023 年 5 月 24 日)内针对某个同期群累计的收入。它帮助你了解从特定同期群的用户身上共收入了多少钱,并用于计算 ROAS。例如,如果 2022 年 9 月的广告支出为 10000 美元,而 2022 年 9 月同期群的总收益为 30000 美元,则 ROAS = 3:1。 **ARPU** 即每用户平均收入,计算公式为:总收入 / 唯一用户数。例如:$60000 收入 / 5000 用户 = $12 ARPU。将此值与每次安装成本(CPI)进行对比,有助于评估营销活动的效果。 **ARPPU** 即每付费用户平均收入,计算公式为:总收入 / 唯一付费用户数。例如:$60000 收入 / 1000 付费用户 = $60 ARPPU。它能帮助你了解每位付费用户平均带来的收入。 **ARPAS** 是每位活跃订阅者的平均收入,计算方式为总收入 / 活跃订阅者数量。这里的订阅者是指已激活试用期或订阅的用户。$60000 收入 / 1500 位订阅者 = $40 ARPAS。 ## 手续费与税费 \{#commission-fees-and-taxes\} 同期群收入计算中有一个重要方面,即商店手续费和税费的纳入(具体金额因用户商店账户所在国家/地区而异)。Adapty 目前在同期群分析中同时支持 App Store 和 Play Store 的手续费及税费计算。 有关 Adapty 如何在分析中计算税费和手续费的详细信息,请参阅我们的[文档](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)。 ## 收入与实际收益 \{#revenue-vs-proceeds\} 收入(Revenue)和实际收益(Proceeds)都是金额类指标。你可以把收入理解为毛收入,实际收益理解为净收入。收入不扣除 App Store / Play Store 手续费,而实际收益已扣除,因此实际收益始终低于收入。 实际扣除的佣金比例受多种因素影响,包括是否符合[小型企业计划](app-store-small-business-program)资格(15%)、长期订阅的优惠费率(续订满一年后降至 15%)、特定国家/地区费率,以及标准费率(最高 30%)。 Adapty 会自动确定每笔交易适用的佣金率,并据此计算收益。有关佣金率确定方式的更多信息,请参阅[商店佣金与税费](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)文档。 ## 退款处理 \{#refund-handling\} 以下两条规则适用于所有同期群数据图表: - 退款以**退款发生日期**计入统计,而非原始购买日期。只有当退款日期落在所选时间范围内时,才会影响相应同期群。 - 退款不会将用户移出其所属同期群,也不会改变安装量。用户安装应用时,其同期群归属即已固定。 除上述规则外,退款的具体影响还取决于所用数据图表及[查看模式](#cohorts-by-renewals-or-by-days)。请参阅您所使用模式对应列的说明。 | 数据图表 | 按续订次数 | 按天数 | | --- | --- | --- | | 安装量(同期群规模) | 不受影响。用户保留在其同期群中。 | 与"按续订次数"相同。 | | 订阅量 | 已退款的订阅仍计入统计。 | 退款会将该订阅从计数中移除。 | | 付费用户数 | 已退款的付费用户仍计入统计。 | 退款会将该用户从计数中移除,即使其存在其他成功付款记录。 | | 收入 | 退款金额从最初计入付款的续订周期列中扣除。 | 退款金额从退款日起向后扣除。 | | ARPU | 收入 / 安装量。退款会降低收入,安装量不变。 | 与"按续订次数"相同。 | | ARPPU | 收入 / 付费用户数。退款可同时降低收入和付费用户数,因此 ARPPU 的波动幅度可能大于单纯的收入变化。 | 与"按续订次数"相同。 | | ARPAS | 收入 / 活跃订阅者数。退款会降低收入,订阅者数量不变。 | 与"按续订次数"相同。 | | 留存率 | 不受影响。仅统计试用和购买事件,不统计退款。 | 与"按续订次数"相同。 | 退款会从收入中扣除退款金额,无论当前使用哪种收入核算模式。 ### 转化率与 ARPPU \{#conversion-rate-and-arppu\} 退款不会影响基于安装量的转化率,因为安装数量本身不会改变。基于付费用户的转化率则不同:在**按天**视图中,退款会降低转化率;但在**按续订**视图中不会。 在**按续订次数**视图中,Revenue 列和 Payers 列各自只显示单个周期的数据。ARPPU 列则不同——每个 ARPPU 单元格会将该同期群从第一个周期到当前列所在周期的所有数据累加在一起。因此,ARPPU 始终涵盖多个周期,并且不包含已退款的用户。正因如此,用单个周期的 Revenue 除以该周期的付费用户数,是无法还原出所显示的 ARPPU 值的。 **示例。** 某用户在 2 月安装应用并购买了订阅,随后在 4 月收到全额退款。查看 2 月同期群的**按天**视图时: - 时间段为二月至三月(退款进入窗口期之前):该用户计为 1 名付费用户,其收入全额计入。 - 时间段为二月至六月(退款进入窗口期之后):该用户计为 0 名付费用户,其收入降至 0。 在两个时间段内,二月的安装数量和留存率保持不变。 [各数据图表如何处理退款](refund-events#how-metrics-handle-refunds)一文对比了退款规则在 MRR、收入数据图表和数据导出中的处理方式。 ## 趋势预测:营收与 LTV \{#prediction-revenue-and-ltv\} **预测营收**是指一批付费订阅用户在同期群创建后的选定周期内,预计产生的总收入。计算方式为:该同期群的预测 LTV 乘以同期群内预计付费用户数。例如,若预测 LTV 为 $50,同期群内有 100 名付费用户,则预测营收为 $5,000。 **趋势预测 LTV** 是每位付费订阅者的预估生命周期价值,代表每位付费订阅者在同期群创建后所选时间段内预计产生的平均收入。 这些预测基于历史同期群留存规律:当应用自身积累了足够的历史数据时,会优先使用该应用的数据;否则将采用跨应用的平均数据。如需了解 Adapty 趋势预测模型的详细说明,请参阅[趋势预测文档](predicted-ltv-and-revenue)。 Adapty 的同期群功能为您提供应用内用户行为和财务表现的深度洞察。通过基于续订或天数的同期群分析,您可以判断同期群何时开始盈利、追踪营收、计算每用户平均收入,并了解收回广告支出所需的时间。借助可自定义的筛选条件、数据图表和导出选项,Adapty 助您做出数据驱动的决策,优化用户获取和变现策略,实现应用的最大化成功。 --- # File: analytics-funnels --- --- title: "漏斗分析" description: "了解 Adapty 的分析漏斗,监控用户行为并提升转化率。" --- Adapty 漏斗旨在帮助你解答以下问题: 1. 有多少比例的安装用户最终转化为付费用户? 2. 试用过产品的用户中,有多少成为了忠实用户? 3. 哪些步骤的流失率较高,需要重点关注? 4. 用户为什么停止付费? 通过漏斗图,您还可以通过设置筛选条件和分组来深入了解用户行为。 漏斗基于我们通过 SDK 和应用商店通知收集的数据,无需您进行任何额外配置。 :::note 漏斗中的安装数据反映的是您在 [App Settings](general#4-installs-definition-for-analytics) 中定义的安装口径。 ::: <img src="/assets/shared/img/funnels-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 漏斗图逐步解析 \{#funnel-chart-step-by-step\} 下面逐一介绍漏斗的各个组成部分,帮助你读懂图表中的用户旅程。 ### 安装量 \{#installs\} 第 1 列(1)显示的是安装量。它以绝对值(2)的形式呈现总安装次数(非独立用户数),同时以 100% 作为基准,用于计算后续各步骤的转化率。如果用户删除应用后重新安装,则计为两次独立安装。 旁边的灰色区域表示各步骤之间的过渡参数。转化至下一步骤(展示付费墙)的转化率显示在标签(3)上。流失百分比及流失绝对值显示在下方(4)。 <img src="/assets/shared/img/00416f9-CleanShot_2022-06-23_at_14.02.06.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 付费墙展示 \{#paywall-displayed\} 第 2 列(5)显示了至少看过一次付费墙的用户数量(6)。这些用户仅来自所选时间段内完成安装的用户。如果某用户在所选时间段内查看了付费墙,但其安装日期不在该范围内,则该次查看不计入统计。 此外,该列还显示了此类查看占第 1 步的百分比(7)。你会注意到,这个百分比与第 1 步的灰色标记(3)数值相同。这种相等关系仅在这两个第一步之间成立。 我们通过所有调用了 `logShowFlow()`(iOS SDK v4+)/ `logShowPaywall()` 方法的付费墙来收集此步骤的数据。因此,请务必按照[文档](present-remote-config-paywalls#track-paywall-view-events)所述,使用该方法将每次付费墙展示事件上报给 Adapty。 第 2 列旁边的灰色区域表示转化过渡。旗标(8)上显示的是流向下一步骤(试用)的转化率。下方(9)显示的是流失率及付费墙之后的绝对流失用户数。 <img src="/assets/shared/img/fb11650-CleanShot_2022-06-23_at_15.54.32.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 试用期 第3列(10)显示在所选时间段内安装应用的用户(11)在付费墙上激活的试用期数量。如果筛选条件设置为非试用产品,该值将为零,且该列为空。 另外,您还可以看到从第 1 步开始的试用转化率(12),即从安装到试用的转化比例。 您可能会发现,这个百分比与上一步转化率的灰色标签(8)并不相同。这是因为当前数值分别与数据图表顶部的第 1 步以及灰色标签上的上一步进行比较。 因此,第 3 列旁边的灰色区域显示的是进入下一步(付费)的转化率百分比,该数值展示在标签(13)上。试用期间的流失率百分比和流失用户绝对数量显示在下方(14)。 <img src="/assets/shared/img/7b88909-CleanShot_2022-06-23_at_15.54.32_-_2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 订阅与续订 \{#subscriptions-and-renewals\} 第 4 列显示已激活的订阅数量(15)。对于没有试用期的产品,这个数字包含从付费墙直接发起的订阅量。对于有试用期的产品,这个数字代表从试用转化为付费订阅的数量。如果你同时拥有有试用期和没有试用期两种类型的产品,则显示两者之和。 顶部的百分比显示从安装数(16)的转化率。 灰色旗帜上的百分比显示到下一步的转化率(续订至第 2 个周期)(17)。 续订至第 2 个周期前的流失百分比和绝对值显示在转化率下方(18)。 <img src="/assets/shared/img/d13bf9b-CleanShot_2022-06-23_at_15.54.32-3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 这一步骤开启了一系列结构相似的步骤序列。第 2 次续订之后是第 3 次、第 4 次,以此类推。如果您的应用历史数据足够丰富,通过水平滚动可能会看到数十个周期。这些步骤的逻辑保持一致: - 顶部显示相对于安装量的百分比, - 底部显示相对于上一步骤的百分比, - 顶部显示续订的绝对数量, - 底部显示流失的绝对数量, - 悬停时弹出流失原因。 ### 流失原因 \{#churn-reasons\} Adapty 会详细列出试用阶段及后续阶段的*流失*统计数据。每个进入某一阶段但未进入下一阶段的用户,都会被计为一次流失。 * 如果某个具体事件(例如试用到期或账单问题)导致用户未能转化,Adapty 会显示相应原因。 * **unknown**(未知)状态是一种临时状态,表示该用户尚未遇到允许其进入下一阶段的事件。 在试用阶段,这通常意味着试用期尚未结束。当查看短日期范围或单日的漏斗时,这种情况较为常见,因为试用需要一定时间才能完成转化。 一旦用户完成转化或取消试用,Adapty 将更新相关信息。 <img src="/assets/shared/img/churn-reasons.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 表格视图、筛选器与 CSV 导出 \{#table-view-filters-and-csv-export\} 漏斗数据图表配有详细数据表格,方便你直接处理具体数字。 <img src="/assets/shared/img/4787aff-CleanShot_2022-06-23_at_21.01.44.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 此表格沿用了漏斗的分析方式,但做了一些调整。 表格中包含除"首次付费订阅"步骤以外所有步骤的数据列。 取而代之的是两列独立数据:安装 -> 付费 和 试用 -> 付费。这两列展示了免费用户转化为付费用户这一核心转化节点。 产品类型的划分看似是这样的:Install -> Paid 列只显示无试用期的产品,而 Trial -> Paid 列只显示有试用期的产品。但实际情况并非如此。因为我们还会将那些试用期已过期、之后又购买了含试用期产品的用户,视为购买了不含试用期的产品来计算。 <img src="/assets/shared/img/a9bcbc7-CleanShot_2022-06-23_at_21.29.12.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 深入挖掘数据时,你会发现筛选工具非常强大,有助于提出新的假设。 可以从不同维度自由设置条件,基于数据获取真实洞察。 可变维度: 1. 产品类型——定价策略、时长等。 2. 时间范围。 3. 国家维度细分。 4. 流量归因。 5. 应用商店。 选择绝对值(Absolute #)、相对值(Relative %),或两者同时显示,以便只查看所需数据。 <img src="/assets/shared/img/1475e42-CleanShot_2022-06-23_at_21.50.33_-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 最后,控制面板右侧有一个按钮,可将漏斗数据导出为 CSV 文件,之后可在 Excel 或 Google Sheets 中打开,也可导入到您自己的分析系统。 :::important 如果您的应用参与了佣金减免计划,请务必通知 Adapty。为确保计算准确,请在您的[应用设置](general)中填写 [Small Business Program](app-store-small-business-program) 和 [Reduced Service Fee program](google-reduced-service-fee) 的状态。 ::: <img src="/assets/shared/img/ff23846-CleanShot_2022-06-23_at_22.15.49.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: analytics-retention --- --- title: "留存分析" description: "了解用户留存分析,优化您的订阅策略。" --- 留存数据图表可以帮助您解答以下问题: 1. 您的应用如何留住每个周期的客户? 2. 哪些产品更具吸引力、留存率更高? 3. 哪些用户群体更忠诚? 4. 哪个留存水平可以作为增长的基准? 5. 当然,如何通过将资金投入已获取的用户群来节省成本,而非一味拓展新用户? 通过设置筛选条件和分组,您可以深入了解用户行为。 留存数据来源于我们通过 SDK 和应用商店通知收集的信息,无需您进行任何额外配置。 ### 我们如何计算留存率?\{#how-do-we-calculate-retention\} 通过观察留存数据图表,你可以了解用户数量与其所处步骤之间的关系:试用(如果勾选了"显示试用"复选框)、第 1 次付款、第 2 次付款,以此类推。下面说明在为留存数据图表选择日期范围时,系统会统计哪些用户。 例如,你在日历中选择了最近 3 个月,且未勾选"显示试用"复选框。这意味着我们只统计在最近 3 个月内完成第 1 次订阅的用户。如果勾选了"显示试用"复选框,且日历中选择了最近 3 个月,则统计所有在这 3 个月内开始试用的用户。对于这些订阅者,第 N 步的绝对留存值为完成第 N 次付款的用户数;第 N 步的相对留存值则为第 N 次付款的绝对数量与所选时间范围内订阅(或试用)总数的比值。 :::info 留存率会随时间回溯变化 无论何时查看数据图表,所选时间段的基准数值(100%)始终保持不变。但下一周期的留存率可能会随时间增长。 例如,对于月度订阅,若在 12 月 1 日至 12 月 31 日之间有 20 笔首次购买,那么在整个 1 月份(乃至之后),随着用户陆续进入下一个订阅周期,第二周期的留存率预计会持续增长(例如由于宽限期等原因)。 ::: ### 退款处理 \{#refund-handling\} 退款**不会**从留存率中剔除。已退款的用户仍计入留存曲线,这可能导致同一同期群的留存率看起来高于[活跃订阅](active-subscriptions)或[收入](revenue)。 如需了解各数据图表对退款的完整处理方式对比,请参阅[各指标如何处理退款](refund-events#how-metrics-handle-refunds)。 ### 留存机会 \{#retention-opportunities\} 让我们来看看如何充分利用 Adapty 的留存功能。 除了对数据本身的热情,更重要的是在落地分析结果后看到真实的业务价值。所以不妨先想清楚目的是什么。深入了解数据图表功能之前,有必要搞清楚这些数据能带来什么影响。 让我们一起从"为什么"和"怎么做"两个维度来看。 1 - 与目标受众互动。 首先,留存率关乎目标受众、他们的偏好,以及你的产品在整个使用周期内是否符合他们的预期。如果你想衡量业务中最核心的那条"生钱"关系,留存率正是你需要的工具。 这种衡量方式很有价值,因为向现有用户销售通常比开发陌生用户成本更低。成本低有两个原因:销售所需的努力更少,客单价也更高。因此,当留存率下滑时,投资于订阅用户的忠诚度往往是明智之举。 2 - 与产品协作。 第二个"为什么"在于:留存数据图表能够反映产品的实际消费生命周期,并支持长期趋势预测。如果你希望有所改进,就调整负责交付产品的工作,改变其生命周期,然后重新预测,以便更接近业务目标。这类更新可以融入战略愿景,与预测流程协同推进。是的,这个过程永无止境——因为在一个不断变化的环境中,我们都在拼命奔跑,只为保持原地。 3 - 把握市场。 比主要竞争对手跑得更快固然不错,但有时跳出常规竞争反而能带来更大的收益。当你分析不同国家和应用商店中用户的行为时,一些本地特性往往能带来绝佳的洞察,为业务开辟新的机会。文化与市场背景可以从留存率的角度加以分析,进而用于市场细分和后续发展。例如,你可能会在某些地区发现蓝海市场,并在那里实现更快速的增长。 当然,留存数据的用途远不止这些基础解读,但如果你想快速获取实际价值,这不失为一个好的起点。 ### 曲线、表格视图、筛选器与 CSV 导出 \{#curves-table-view-filters-and-csv-export\} 现在我们对留存目的和基本解读方式有了共同认识,接下来介绍让这一切变得便捷的工具。 Adapty 留存功能的核心是数据图表。它展示了留存率如何随用户生命周期各阶段的推进而变化。 各阶段显示在横轴上:Trial(试用)、Paid(第 1 次订阅)、P2(第 2 次订阅)、P3、P4,依此类推。 请注意,只有勾选了"Show trials"复选框时,横轴才会从 Trial 阶段开始。 该复选框对数据计算的影响如下:勾选"Show trials"后,横轴从 Trial 阶段起始,此时仅展示包含试用的路径,不显示从安装直接产生的交易,Paid 阶段也仅包含由试用转化而来的交易。若未勾选"Show trials",横轴从 Paid 阶段起始,则第一个阶段包含所有首次交易,既包括来自试用的,也包括从安装直接产生的。 当您将鼠标悬停在数据图表上时,会弹出一个包含数据摘要的浮层。如果您将鼠标悬停在下方表格的某一列上,同样会看到一个摘要浮层,并在数据图表上高亮显示相关数据。 表格中的分组和筛选条件与数据图表保持一致。 自由组合筛选条件与分组方式,进行深度分析,从数据中获取真实洞察。 可变维度: 1. 产品类型 2. 时长 3. 时间范围 4. 国家 5. 流量归因 6. 商店 使用 #Absolute 和 %Relative 控件切换所需的数据视图。 最后,在控制面板右侧有一个按钮,可将漏斗数据导出为 CSV 格式。你可以在 Excel 或 Google Sheets 中打开该文件,也可以将其导入自己的分析系统,在你熟悉的环境中继续进行分析和预测。 :::warning 请务必在 [Adapty General Settings](https://app.adapty.io/settings/general) 中标注你的应用已加入 Small Business Program。 ::: --- # File: analytics-conversion --- --- title: "转化分析" description: "使用 Adapty 的分析工具衡量订阅转化率。" --- 漏斗分析帮你掌握整体概况,留存分析关注用户忠诚度,而转化分析则专注于评估用户旅程中每个关键步骤的效果——并追踪其随时间的变化趋势。 转化分析可以帮助你回答以下问题: 1. 应用转化率随时间如何变化?是否存在季节性趋势? 2. 营销活动或其他新情况发生时,转化率如何变化? 3. 不同地区的用户对应用更新的响应有何不同? 4. 哪种产品类型的长期转化效果更好? 转化数据来源于我们通过 Adapty SDK 和应用商店通知收集的信息,无需您进行额外配置。 ## 主要控件与数据图表 \{#main-controls-and-charts\} 营收虽然是衡量成功的常用指标,但它只是整体图景的一部分。了解业务在不同用户行为和生命周期阶段随时间的表现同样重要,这正是转化分析的用武之地。 通过设置筛选条件和分组,您可以挖掘更多关于用户行为的有价值洞察。要识别和分析趋势,可以按日、月或年监控转化数据的变化情况。 在数据图表左侧,你可以找到转化步骤控制器,用于选择要追踪的具体转化路径——例如安装 → 试用、试用 → 付费,或付费 → 续订。 每项转化数据图表遵循以下逻辑: - 设 **X** 为在所选日期进入起始状态的用户数(例如安装量)。 - 设 **Y** 为其中最终达到目标状态的用户数(例如开始试用)。 - 转化率计算公式为:**转化率 = (Y / X) × 100%** :::note 数据图表中显示的日期对应用户进入初始状态 (X) 的时间——即他们具备转化条件的那一刻。 ::: 请参阅下方各转化说明及对应示例。 ### 安装 -> 付费 \{#install---paid\} 此数据图表显示在特定日期安装应用的用户中,最终购买了首个订阅的用户占比。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期的安装量(对所有产品相同,因为安装时尚未选择产品)。 - **Y** = 其中最终完成首次订阅购买(试用或非试用)的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日共有 100 次安装。 - 截至 1 月 8 日,其中 20 名用户已完成订阅。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,1月1日安装组又有30名用户购买了订阅。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,截至当前时刻,1月1日安装应用的用户中,有50%最终转化为付费订阅用户。 </details> ### 安装 -> 试用 \{#install---trial\} 该数据图表显示在特定日期安装应用的用户中,最终开启试用的比例。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期的安装量(对所有产品相同,因为用户在安装时尚未选择任何产品)。 - **Y** = 其中最终在任意时间激活试用的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日共有 100 次安装。 - 到 1 月 8 日,其中 20 名用户已开启试用。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,1月1日安装组又有30名用户开始了试用。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,截至当前时刻,1月1日安装应用的用户中有50%最终开始了试用。 </details> ### 付费墙展示 -> 试用 \{#paywall-view---trial\} 此数据图表追踪在看到付费墙后开始试用的用户数量。 <details> <summary>工作原理</summary> **设**: - **X** = 在所选日期看到付费墙的用户数量。 - **Y** = 此后任意时间开始试用的用户数量。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日,共有 100 次付费墙展示。 - 到 1 月 8 日,其中 20 名用户已开始试用。 - 1 月 8 日,1 月 1 日的转化率 = (20 / 100) × 100% = 20% </details> - 到2月1日,又有30名用户开始了试用。 - 2月1日时,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这表明,截至当前时刻,1月1日看到付费墙的用户中有50%开始了试用。 ### 付费墙展示 -> 已付费 \{#paywall-view---paid\} 此数据图表追踪有多少用户在看到付费墙后完成了购买。 <details> <summary>工作原理</summary> **设**: - **X** = 在所选日期看到付费墙的用户数。 - **Y** = 此后任意时间完成购买的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日,共有 100 次付费墙展示。 - 到 1 月 8 日,其中 20 名用户已完成购买。 - 1 月 8 日,1 月 1 日的转化率 = (20 / 100) × 100% = 20% </details> - 到2月1日,又有30名用户完成了购买。 - 2月1日时,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这说明,在1月1日看到付费墙的用户中,有50%在截至当前时刻完成了购买。 ### 试用转付费 \{#trial---paid\} 此数据图表显示在特定日期开始试用的用户中,后续购买了首个订阅的百分比。 <details> <summary>计算方式</summary> **设**: - **X** = 所选日期开始试用的用户数。 - **Y** = 其中最终在试用结束后购买了订阅的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日共有 100 名用户开始试用。 - 到 1 月 8 日,其中 20 名用户已订阅。 - 1 月 8 日,1 月 1 日的转化率 = (20 / 100) × 100% = 20% </details> - 到2月1日,1月1日试用组又有30名用户完成了订阅。 - 2月1日时,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 也就是说,1月1日开始试用的用户中,有50%最终转化为付费订阅(截至当前时刻)。 ### 付费 -> 第 2 个周期 \{#paid---2nd-period\} 该数据图表显示订阅用户在首次付款后续订的百分比。 <details> <summary>运作原理</summary> **设**: - **X** = 所选日期内首次订阅的用户数。 - **Y** = 续订第二个周期的用户数,续订可发生在任意时间(通常在一个订阅周期后;包含宽限期内的续订)。 - **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日,共有 100 名用户首次订阅。 - 到 1 月 8 日,其中 20 名用户已完成续订。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,该组又有30名用户完成了续订。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这说明,在1月1日首次完成订阅付款的用户中,截至当前时刻有50%完成了第二个周期的续订。 </details> ### 第2期 -> 第3期 \{#2nd-period---3rd-period\} 此数据图表跟踪在第二个订阅周期后再次续订的用户数量。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期的第二期订阅数量。 - **Y** = 续订第三期的用户数量,发生在任意后续时间(通常在再经过一个计费周期后;包含宽限期内的续订)。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1月1日,共有 100 个第二期订阅。 - 截至1月8日,其中 20 名用户已完成续订。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,又有30名用户续订了。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这表明,在1月1日进入第二个订阅周期的用户中,有50%在当前时刻之前续订了第三个周期。 </details> ### 第3期 -> 第4期 \{#3rd-period---4th-period\} 该数据图表显示在第三个订阅周期后续订的用户百分比。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期第三周期订阅的数量。 - **Y** = 之后任意时间续订第四周期的用户数量(通常在一个计费周期后;包含宽限期内的续订)。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1月1日,共有100个第三周期订阅。 - 截至1月8日,已有20名用户完成续订。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,又有30名用户续订。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,在1月1日进入第三个订阅周期的用户中,有50%在当前时刻为止完成了第四次续订。 </details> ### 第4周期 → 第5周期 \{#4th-period---5th-period\} 该数据图表显示在第四个订阅周期后续订的用户百分比。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期当天处于第四周期的订阅数量。 - **Y** = 此后任意时间续订第五个周期的用户数量(通常在一个计费周期后;包含宽限期内的续订)。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1月1日,共有100个处于第四周期的订阅。 - 截至1月8日,已有20名用户完成续订。 - 1月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到2月1日,又有30名用户续订。 - 2月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,在1月1日进入第四个订阅周期的用户中,有50%在当前时间点之前续订了第五个周期。 </details> ### 6 个月以上 \{#6-months-\} 此数据图表显示从首次订阅起持续订阅超过 6 个月的用户占比。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期的首次订阅数量。 - **Y** = 其中在首次订阅日期 6 个月后至少续订一次的用户数量。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1 月 1 日共有 100 次首次订阅。 - 到 7 月第一周,其中 20 人完成了续订(例如第 25 次周订阅)。 - 7月8日,1月1日的转化率 = (20 / 100) × 100% = 20% - 到8月1日,又有30人在6个月后续订。 - 8月1日,1月1日的转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,截至8月1日,1月1日订阅的用户中有50%在6个月后仍保持订阅状态。 </details> ### 1年以上 \{#1-year-\} 该数据图表显示了从首次订阅起,持续订阅超过12个月的用户占比。 <details> <summary>工作原理</summary> **设**: - **X** = 所选日期的首次订阅数量。 - **Y** = 其中在原始订阅日期12个月后至少续订一次的用户数量。 **公式**:转化率 = (Y / X) × 100% **示例**: - 2021年1月1日,共有100次首次订阅。 - 到2022年1月的第一周,其中20人已续订。 - 2022年1月8日,转化率 = (20 / 100) × 100% = 20% - 到2022年2月1日,又有30人在12个月后续费。 - 2022年2月1日,转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,在2021年1月1日开始订阅的用户中,有50%保持活跃超过一年。 </details> ### 2 年以上 \{#2-years-\} 此数据图表显示从首次付款日期起订阅超过 24 个月的用户百分比。 <details> <summary>工作原理</summary> **设**: - X = 在选定日期内首次订阅的用户数。 - Y = 其中在原始订阅日期起 24 个月后至少续订一次的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 2020 年 1 月 1 日,共有 100 名用户首次订阅。 - 到 2022 年 1 月的第一周,其中 20 人已完成续订。 - 2022年1月8日,转化率 = (20 / 100) × 100% = 20% - 到2022年2月1日,又有30人在2年后续订。 - 2022年2月1日,转化率 = ((20 + 30) / 100) × 100% = 50% 这意味着,截至2022年2月1日,在2020年1月1日订阅的用户中,有50%在2年后仍处于活跃状态。 </details> ### 宽限期 -> 已付款 \{#grace-period---paid\} 此数据图表显示进入[订阅宽限期](grace-period)的用户中,在宽限期结束*前*成功解决问题的用户占比。 <details> <summary>计算方式</summary> **设**: - X = 进入宽限期的订阅用户数。 - Y = 其中在宽限期到期前完成续订的用户数。 **公式**:转化率 = (Y / X) × 100% **示例**: - 2025年1月1日,100人的订阅未能自动续期,进入为期16天的宽限期,到期日为1月17日。 - 在1月1日至1月17日期间,50人更新了支付信息,订阅成功续期。 - 2025年1月17日,转化率 = (50 / 100) × 100% = 50% </details> ### 账单问题 -> 已付款 \{#billing-issue---paid\} 此数据图表展示遭遇[账单问题](/billing-issue)后,在账期结束前恢复付款的用户占比。 <details> <summary>运作原理</summary> **定义**: - X = 遭遇账单问题的订阅者数量。 - Y = 其中在账单问题发生至账期结束这段时间内续订的用户数量。 **公式**:转化率 = (Y / X) × 100% **示例**: - 1月1日,100名订阅者在订阅无法自动续费时遇到了账单问题。 - 注意:如果启用了宽限期,账单问题状态仅在宽限期结束后才开始。本示例假设宽限期于1月1日结束。 - 到1月8日,其中10名用户已解决付款问题并完成续费。 - 1月8日,1月1日的转化率 = (10 / 100) × 100% = 10% - 到1月31日(账单周期结束),又有10名用户完成了续费。 - 1月31日,1月1日的转化率 = ((10 + 10) / 100) × 100% = 20% 这表明,1 月 1 日进入账单问题状态的用户中,有 20% 在账单周期结束前解决了问题并完成了续订。 </details> ## 分组与时间范围 \{#grouping-and-time-ranges\} 当选择转化率作为分析对象时,核心展示内容是数据图表。它呈现了转化百分比随时间变化的趋势。请使用日期选择器,通过快速选项来设定时间范围。 数据图表通常包含多条曲线。默认情况下,分组列表中最多选中五条曲线,你可以通过勾选图表右侧区域中的复选框来调整选择。 首次打开页面时,默认以产品时长作为分组维度。之后你的设置会保存在缓存中,下次打开时将显示你最近选择的分组。 以下分组维度可供选择: - 产品 - 国家 - 商店 - 付费墙 - 时长 - 营销归因 如果所选日期范围内没有数据可展示,系统会弹出提示,建议你调整日期范围,点击即可一键完成调整。 ## 表格视图、筛选与 CSV 导出 \{#table-view-filters-and-csv-export\} 曲线对比能呈现直观的整体趋势,而图表下方的表格视图则可以帮助你深入分析数据。表格与图表保持同步——将鼠标悬停在某一列上时,对应的弹窗会同步显示在曲线上。 上文提到的分组设置会同时作用于图表和表格。你可以按产品快速筛选,也可以使用其他高级筛选条件,包括产品、国家、商店、时长和归因。 我们深知用户需要以自己习惯的方式处理数据。因此,控制面板右侧提供了一个按钮,可将漏斗数据导出为 CSV 格式。你可以在 Excel 或 Google Sheets 中打开该文件,也可以将其导入自己的分析系统,在你熟悉的环境中继续进行分析和预测。 :::important 如果您的应用已加入折扣佣金计划,请告知 Adapty。为确保计算准确,请在您的[应用设置](general)中说明您的 [App Store 小型企业计划](app-store-small-business-program)和[降低服务费计划](google-reduced-service-fee)状态。 ::: --- # File: reports --- --- title: "报告" description: "在 Adapty 中生成详细的订阅报告,分析应用收入和用户行为。" --- 直接将及时、相关的信息发送到您的邮箱,包括收入、流失率、活跃订阅者、活跃试用次数等——与[数据图表](charts)中提供的数据图表相同。这些报告可按日、周或月发送,并通过与前一时期的对比展示最新时期的动态变化。 我们在报告中发送的数据基于您在 [**Overview**](https://app.adapty.io/overview) 页面上的配置,包括数据图表、排列顺序、报告时区和收入类型。 您可以灵活选择报告的详细程度:汇总报告或单应用报告。汇总报告是一封包含所有应用(或您选定的部分应用)聚合数据的邮件。单应用报告则只包含某个指定应用的数据。我们建议为所有应用启用汇总报告,同时为近期发布的应用、高优先级应用以及您个人负责的应用启用单应用报告。 无论选择哪种详细程度,电子邮件报告都会在您当地时间上午 9 点发送到您的收件箱:日报每天发送,周报每周一发送,月报在每月第一天发送。每份报告均包含当前数据以及与上一周期的对比(例如,今天的日报会对比昨天和前天的数据;今天的周报会对比上周和上上周的数据,以此类推)。 请放心,无论您选择哪些报告,都将在收件箱中收到最新、最准确的信息。 ## 启用报告 \{#enable-reports\} 1. 打开 Adapty 顶部菜单中的 [**Account**](https://app.adapty.io/account) 部分。 2. 在 **Email reports** 区域下,选择您希望接收的报告类型——每日、每周和/或每月。 2. 通过选择相关应用来自定义每种报告类型。为此,点击 **Edit** 按钮。 3. 在报告窗口中,选择要包含的应用。 4. 最后,点击 **Save changes** 按钮以应用您的选择。 ## 设置时区 \{#set-your-time-zone\} 1. 打开 Adapty 主菜单中的 [**Overview**](https://app.adapty.io/overview) 页面。 2. 点击 **Edit** 按钮,选择你的时区。 3. 点击 **Done** 按钮保存。 --- # File: discrepancies-and-troubleshooting --- --- title: "排查数据差异" description: "查找不同数据来源之间差异的原因" --- Adapty 用户在比较来自不同来源的相似数据集时,可能会遇到**数据差异**。这种情况尤其容易在以下比较中出现: * Adapty 数据图表与应用商店报告之间的比较 * Adapty 数据图表与第三方数据图表之间的比较 * Adapty 内部不同数据图表之间的比较 ## 故障排查流程 \{#troubleshooting-algorithm\} Adapty 与其他平台之间的大多数数据差异都是正常现象,原因在于**不同数据源对相同数据的处理方式不同**。 但有时,这些差异也可能意味着你的 **Adapty 配置存在问题**。 如果你怀疑各平台之间的数据存在出入,最好的排查方式是[导出原始数据](export-analytics-api-requests)并**对比文件内容**。 * 即使是应用商店本身也可能存在数据处理和展示方面的问题。请访问商店的**原始交易数据**,以获得最准确的对比参考。 * 在将 Adapty 与其他分析平台进行比较时,请以商店的交易报告作为真实来源和对比基准。 * 使用较小的数据集更容易定位差异。建议对比少量数据——聚焦某个特定产品的单日数据。 * 判断差异是由**定价**还是**事件数量**引起的。定价问题可以通过[更新产品](#product-pricing)来解决;事件数量问题则可能表明存在[服务端问题](#issues-with-server-notifications-and-rtdn)。 * 查看[事件流](event-feed)以监控传入事件——你可能会发现一些异常行为。 确定数据出现分歧的位置后,可以排查以下常见原因: ## 服务器通知与 RTDN 问题 \{#issues-with-server-notifications-and-rtdn\} 如果未正确配置应用商店连接,Adapty 将无法接收必要的事件数据。这尤其会影响那些无需用户直接操作就会触发的事件——例如订阅续费、账单问题等。 请尽快完成服务器对服务器的配置([App Store](enable-app-store-server-notifications) | [Play Store](enable-real-time-developer-notifications-rtdn)),并[等待](#data-delays)商店建立连接。 您可以[手动上传](importing-historical-data-to-adapty)缺失的 App Store Connect 数据到 Adapty。 ## 数据缺失 \{#missing-data\} ### 使用旧版应用的用户 \{#users-with-out-of-date-app-versions\} 如果部分用户运行的是不含 Adapty SDK 的旧版应用,Adapty 将无法接收其数据。因此,Adapty 与其他来源的数据数量将出现差异。 ### 集成问题 \{#integration-issues\} 部分 Adapty 集成(例如 Adjust 或 AppsFlyer)需要在应用中添加额外代码才能正常工作。如果你只在 Adapty 看板中完成了配置,但没有更新应用代码,相关数据将无法出现在 Adapty 中。 ### 缺少历史数据 \{#missing-historical-data\} Adapty 无法访问您应用的历史数据,除非您[手动导入](importing-historical-data-to-adapty)。如果数据图表的[时间范围](controls-filters-grouping-compare-proceeds#set-the-date-range)早于您接入 Adapty 的时间,且您未导入历史数据,则图表数值将与其他来源存在差异。 ## 数据延迟 \{#data-delays\} Adapty 致力于为您的应用经济提供接近实时的分析。以下是相关限制和例外情况: * 当您首次集成 Adapty 时,数据可能不会立即显示。 * 启用与第三方平台的集成后,数据完全同步前可能存在一定延迟。 * Adapty 收到商店数据后,还需要 **15-30 分钟** 进行处理,才会显示在 Analytics 页面上。 * 由于涉及的变量较多,Adapty 与第三方之间的数据交换**并非总是实时的**。 * 部分高级数据图表(例如[同期群趋势预测](predicted-ltv-and-revenue))的计算需要积累一定量的数据,Adapty 只有在收集到足够数据后才会执行这些计算。 ## 时间与日历 \{#time-and-calendar\} #### 日期与时区 \{#dates-and-timezones\} 数据出现差异,最常见的原因之一就是时区设置不同。 Adapty 按 `UTC` 时区计算天数。如果其他平台使用不同的时区,计算结果就会有偏差。随着统计周期拉长,这种偏差会逐渐缩小。 你可以为每个应用[修改时区设置](general#3-reporting-timezone)。 #### Apple 财年日历 \{#the-apple-fiscal-calendar\} Apple 使用自己的[财务日历](https://adapty.io/apple-fiscal-calendar/)来确定销售周期和付款日期。 日历中的每个"月份"由 **4 或 5 周**组成,**可能包含相邻日历月份的日期**。付款通常在销售周期结束后 30–45 天内发放。 例如,"2026 年 1 月"的销售周期从 2025 年 12 月 28 日开始——比日历月份的起始日提前 4 天。该周期的预计付款日期为 3 月 5 日。 不要将 Apple 收款报告中的数据与自然月进行比较。请改为选择与相应销售周期对应的[自定义日期范围](controls-filters-grouping-compare-proceeds#set-the-date-range)。 #### 交易日期 \{#transaction-dates\} 部分服务(例如 AppsFlyer)在展示交易记录时可能会应用[同期群](analytics-cohorts)规则,将交易归因到应用的安装日期,而非交易本身的发生日期。 ## 收入计算 \{#revenue-calculation\} ### 费用与税款 \{#fees-and-taxes\} 根据[设置](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue),Adapty 数据图表可以显示您的**毛收入**、**扣除应用商店佣金后的收入**或**扣除应用商店佣金及税款后的收入**。 部分应用商店和第三方平台可能不支持显示毛收入,或会自动扣除税款。如果您发现两个收入数据图表之间存在差异,请确认两者的对比口径是否一致。 ### 取消与退款 \{#cancellations-and-refunds\} 不同平台对退款数据的显示方式不同。Adapty 将退款视为负收入。如果用户订阅后次日申请退款,这两个事件都会反映在 Adapty 数据图表中——各自对应其发生的日期。其他平台可能会从原始交易中直接扣除退款金额。 ## 沙盒购买 \{#sandbox-purchases\} [事件流](event-feed)会显示沙盒账户的购买记录,而分析数据图表则不会显示。但是,如果您的历史导入数据包含沙盒购买记录,Adapty 将无法区分,其数据图表将会反映历史沙盒购买记录。 ## 安装与下载 \{#installs-and-downloads\} 应用商店(尤其是 Apple App Store)可以直接追踪用户下载数据。其统计数据可能包括已安装但从未启动应用的情况。 无论您的[安装定义](general#4-installs-definition-for-analytics)如何设置,Adapty 只能在用户启动应用时才能注册一次安装。 ## 国家/地区与商店 \{#country-and-store\} 为确保报告准确,Adapty [可能会根据](controls-filters-grouping-compare-proceeds#filter-and-group-data) 用户的 IP 地址推断其所在国家/地区。商店则始终将下载和购买行为归因到特定的应用商店。 如果需要明确区分这两者,可以使用 `Country by store account` 属性[创建新的市场细分](segments),并[按市场细分筛选分析数据](controls-filters-grouping-compare-proceeds#filter-and-group-data)。 ## 产品定价 \{#product-pricing\} 如果产品定价错误导致收入差异,更改价格并不会对历史数据产生追溯效果。要更改现有交易的价格,您需要通过导入正确数据来强制覆盖。 用户在价格变更后恢复旧购买时,Apple 可能会错误报告购买金额。您需要导入历史数据,以便 Adapty 反映正确的金额。 ## 归因冲突 \{#attribution-conflicts\} Adapty 对每笔交易只能使用[单一归因来源](attribution-integration#prevent-data-issues),且无法在事后覆盖此数据。 如果您的配置包含多个相互不一致的归因提供商,则同一笔交易在两个不同平台上可能显示为两个不同的流量来源。 ## 术语差异 \{#differences-in-terminology\} 不同平台对同一概念可能使用不同的名称。与[收入](#fees-and-taxes)相关的数据图表在各平台之间的叫法也不尽相同: | Adapty | App Store Connect | Google Play Console | |--------|-------------------|----------------------| | **Gross revenue** | Sales | Gross Revenue | | **Proceeds after store commission** | N/A | N/A | | **Proceeds after store commission and taxes** | Proceeds | Earnings | | **ARPPU** | Proceeds per paying user | ARPPU | 其他数据图表在定义上也可能存在差异: - **订阅**: - Adapty 不将新试用计入订阅。[新订阅](reactivated-subscriptions)始终以实际付款交易为起点。 - Google Play Console 等其他平台可能将**每次试用都计为一个新订阅**,即使尚未完成首次付款。 - **留存率**: - Adapty 根据订阅续费次数来衡量留存率。 - App Store Connect 将"在指定日期打开应用的用户"视为留存用户。没有订阅的用户也会被计入,但当天未打开应用的订阅用户则不会被计入。 - Google Play Console 的"Retained Installers"指标根据应用在用户设备上的安装天数来衡量留存率,即使用户未打开应用也会被计入。 ## 新订阅数据图表与 `subscription_started` 事件的区别 \{#new-subscriptions-metric-vs-the-subscription_started-event\} [新增订阅](reactivated-subscriptions)数据图表与 `subscription_started` [集成事件](events)统计的内容不同,因此两者的数字不会一致。该数据图表同时统计不经试用期直接首次购买的情况以及试用转付费的情况。而 `subscription_started` 事件仅在用户不经试用期直接首次购买时触发——当试用期转换为付费订阅时,Adapty 发送的是 `trial_converted` 事件。因此,只要您的应用存在试用转化,新增订阅的数量就会高于 `subscription_started` 事件的数量。 --- # File: predicted-ltv-and-revenue --- --- title: "同期群中的趋势预测" description: "使用 Adapty 的预测分析来预测 LTV 和收入。" --- Adapty 趋势预测旨在帮助你回答以下问题: 1. 你的用户同期群的预测生命周期价值(LTV)是多少? 2. 哪些同期群未来最有可能带来最高收入? 3. 基于预测的回报,你可以投入多少资金? 借助 Adapty Predictions,您可以基于数据做出营收与增长方面的决策。 Adapty 的趋势预测模型能够估算应用各用户同期群的长期营收潜力。针对每个同期群,它会预测营收、付费订阅用户数量以及平均 LTV 随时间的变化趋势。这有助于您在用户获取、营销策略和产品开发方面做出更明智的决策。 Adapty 为付费订阅用户的同期群提供预测性生命周期价值(LTV)和预测收入。趋势预测显示在同期群分析页面上,涵盖同期群创建后 3、6、9、12、18 和 24 个月的数据。 对于历史数据非常有限的应用,模型会回退到跨应用平均值,因此较新应用的趋势预测可能无法完全反映其特定用户行为。 ## 模型工作原理 \{#how-the-model-works\} Adapty 的趋势预测模型利用历史同期群数据中的留存规律,对未来营收和 LTV 进行预测。 针对每种应用与订阅类型的组合,模型会衡量付费订阅者数量和总营收在相邻续费周期之间的变化情况。它根据应用的历史同期群计算两个留存率——一个针对订阅者,一个针对营收——再将这些留存率应用于新同期群,预测其在同期群创建后 3、6、9、12、18 和 24 个月的增长趋势。所使用的数据均已完全匿名化处理。 该模型为每个同期群生成两个值: - **趋势预测收入**:预计该同期群在所选时间范围内产生的总收入。 - **趋势预测 LTV**:趋势预测收入除以该同期群中预计付费订阅用户数。 ### 应用专属权重与跨应用权重 \{#app-specific-and-cross-app-weights\} 默认情况下,同期群的趋势预测会使用从该应用自身历史同期群中学习到的留存权重,从而反映其特有的用户行为规律。 当应用的历史数据不足以支撑某个预测周期时,Adapty 会回退到使用同类订阅应用的平均留存权重。例如,一个只有六个月历史的应用,其 12 个月趋势预测就会使用跨应用的回退数据。这种回退机制按预测周期独立应用,因此同一个同期群可能在 3 个月预测中使用应用自身的权重,而在 12 个月预测中使用跨应用权重。 ### 可用性与更新 \{#availability-and-updates\} 趋势预测会在同期群完成首个续订周期后开始生效——对于按周订阅,通常是创建后约一周;对于按月订阅,则约为四周。此后,系统每天会根据最新的交易数据进行更新,以保持预测结果与同期群行为的同步。 ### 局限性 \{#limitations\} - **数据质量**:同期群行为异常或付费用户极少时,预测准确性会降低。付费用户不足 30 人的同期群将被排除在模型训练数据之外。 - **新应用**:历史数据不足的应用将使用跨应用的备用权重,可能无法反映该应用的实际用户行为。 - **同期群年龄**:当同期群超过特定预测时间跨度后,该跨度的预测结果将被隐藏。例如,3 个月的预测在三个月后停止显示,超过 24 个月的同期群将不再显示任何预测。 ## 在看板中 \{#in-the-dashboard\} 要查看趋势预测,请在 Adapty 看板中导航至同期群分析页面。有关同期群的详细信息,请参阅[同期群分析](analytics-cohorts)。 <img src="/assets/shared/img/4d808b4-Export-1691486610612.gif" alt="同期群分析页面,显示预测收入和预测 LTV 列" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **预测收入**列显示所选时间范围内,某个订阅者同期群在创建后预计产生的总收入估算值。该值基于应用历史同期群留存数据,由 Adapty 的趋势预测模型计算得出。 **预测 LTV**列显示所选同期群中每位用户的预计生命周期价值。该值由预测收入除以同期群中预测付费用户数得出。 ### 选择预测周期 \{#select-the-horizon\} 如需更改趋势预测周期,请从 **Predictions** 下拉菜单中选择一个值。可选项包括同期群创建后的 3、6、9、12、18 和 24 个月。 ### 按产品筛选 \{#filter-by-product\} 您可以按产品筛选预测收入和 LTV。默认情况下,趋势预测基于所有购买数据——按产品筛选后,可以查看每个产品的贡献情况。 <img src="/assets/shared/img/66a9c61-Export-1691486288948.gif" alt="按产品筛选的同期群分析" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 预测不可用的情况 \{#when-predictions-are-unavailable\} 当某个同期群无法生成趋势预测时,"预测收入"和"预测 LTV"列将显示破折号(—)而非具体数值。这可能有以下几种原因: - **同期群创建后时间不足**:趋势预测仅在同期群完成首次续费周期后才可用——周订阅约需一周,月订阅约需四周。 - **同期群规模过小**:付费订阅者数量太少,无法生成可靠的预测结果。 - **同期群行为异常**:该同期群与模型预期的规律存在显著偏差。等待数周后随着数据积累,此问题可能自行解决。 - **超出预测范围**:同期群的存续时间超过所选预测范围。例如,3 个月的预测在三个月后隐藏,12 个月的预测在十二个月后隐藏,超过 24 个月的同期群将不显示任何预测。 :::warning 启用趋势预测时,请注意,Revenue 和 LTV 的趋势预测数据可能最多延迟 24 小时才会显示在您的 Adapty 看板上。 ::: --- # File: predictions-in-ab-tests --- --- title: "A/B 测试中的趋势预测" description: "了解 A/B 测试中的趋势预测如何帮助优化订阅定价策略。" --- 欢迎阅读 Adapty A/B 测试功能的预测分析文档。该工具将为您正在运行的 A/B 测试提供未来结果洞察,并帮助您借助 Adapty 的机器学习驱动趋势预测,更快速地做出数据驱动决策 🚀。 ### A/B 测试趋势预测是什么?\{#what-are-ab-test-predictions\} Adapty 的 A/B 测试趋势预测采用先进的机器学习技术(特别是梯度提升模型),对 A/B 测试中所比较付费墙的长期收入潜力进行预测。 该预测模型使您能够根据一年后的预计收入来选择最有效的付费墙,而不仅仅依赖于测试运行期间观察到的数据图表。这样一来,您可以更可靠、更快速地确定获胜者,无需等待数周时间积累数据。 ### 模型是如何工作的? \{#how-does-the-model-work\} 该模型基于来自不同类别应用的大量历史 A/B 测试数据进行训练,并整合了多维度特征,用于预测付费墙在实验开始后一年内可能产生的收入。这些特征包括: - 不同时间段内的用户交易情况与转化率 - 用户的地理分布 - 平台使用情况(iOS 或 Android) - 退出率和退款率 - 订阅产品及其订阅周期长度(日、月、年等) - 其他与交易相关的数据 该模型还会考虑付费墙中的试用期,使用历史转化率来预测收入,就如同用户已完成转化一样。这确保了有试用优惠和无试用优惠的付费墙之间的公平比较,因为我们也会将正在进行的试用期可能带来的未来收入纳入计算。 ### 预测 P2BB 与普通 P2BB 有何不同?\{#how-is-predicted-p2bb-different-from-just-the-p2bb\} 我们的 A/B 测试采用贝叶斯方法:简单来说,我们对每位用户的收入分布(具体为"每 1000 用户收入")进行建模,然后计算一个分布"真正"优于另一个分布(而非随机偶然)的概率——这就是我们所说的"成为最优方案的概率"(P2BB)。如需了解更多,请参阅[此处](maths-behind-it)。 需要注意的是,在此过程中,我们仅依赖测试运行期间累积的收入数据。因此,如果您要运行一个对比年度订阅与周订阅的测试,则需要等待相当长的时间才能真正了解哪种方案表现更好。当您在 A/B 测试中对比试用订阅与非试用订阅时,也会出现类似情况——因为那些可能影响胜出者结果的有效试用期,在收入统计中始终未被纳入计算。 这就是我们预测模型发挥作用的地方。它基于 A/B 测试中当前的收入分布,并在大量数据集上完成训练,能够预测收入分布的未来状态(即一年后的情况)。在此基础上,它会输出一个预测的 P2BB——即如果你将测试运行整整一年所能得到的结果。 请注意,有时预测的 P2BB 可能与当前的 P2BB 相矛盾。遇到这种情况时,我们会用黄色高亮显示对应的实验变体行,如下所示: <img src="/assets/shared/img/74577c6-CleanShot_2024-02-15_at_13.08.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们认为这是一个信号,提示您需要积累更多数据来确认获胜者,或深入分析 A/B 测试以找出背后的原因。一般来说,我们建议优先参考预测 P2BB,而非当前 P2BB,因为前者纳入了更多数据作为依据,但最终决策当然由您来做。 ### 模型准确性与置信度 \{#model-accuracy-and-certainty\} 该模型准确性较高,平均绝对百分比误差(MAPE)略低于 10%。这一精度水平使企业能够在做出数据驱动的决策时,放心地依赖模型的趋势预测结果。 为进一步保证结果的稳定性,模型采用了基于以下三个因素的"置信度"标准: - 较窄的预测区间——模型对其结果有较高把握 - 测试中有足够数量的订阅与收入数据 - 距测试开始至少已过去 2 周 当以下三个标准中至少满足两个时,趋势预测被视为可靠。 当新的 A/B 测试开始时,模型会为每个付费墙提供未来一年每千次展示收入(我们 A/B 测试的核心数据图表)的趋势预测。只有满足确定性标准时,才会显示趋势预测。如果数据不足,模型将显示"数据不足,无法进行趋势预测"。 ### 限制与注意事项 \{#limitations-and-considerations\} 虽然我们的预测模型是一个强大的工具,但了解其局限性同样重要。 模型的表现取决于可用数据的质量和代表性。异常的同期群行为,或训练集中未包含的新应用,都可能影响预测准确性。 尽管如此,趋势预测每天都会更新,以反映最新数据和用户行为,确保您获取的洞察始终基于最新信息。 🚧 注意:此工具是对您专业判断和对应用独特动态理解的补充,而非替代。请将这些趋势预测作为参考,结合其他数据图表和市场知识,做出明智的决策。 --- # File: adapty-ads-manager --- --- title: "Adapty Ads Manager" description: "从 Apple Ads 获取实时分析数据,管理和优化您的广告活动" --- **Adapty Ads Manager** 是一款一体化平台,专为帮助您更高效地管理、优化和扩展 Apple Ads 广告系列而设计。它将您的 Apple Search Ads 效果数据与关键收入指标(如安装量、试用、订阅和用户生命周期价值)连接起来,无需借助 MMP。 借助实时分析、AI 驱动的预测和智能自动化,Adapty Ads Manager 消除了繁琐的人工出价调整、电子表格操作和凭感觉猜测,取而代之的是清晰的洞察和帮助您快速采取行动的工具。 使用 Adapty Ads Manager,您将获得: - **[概览](ads-manager-overview)**:一览所有关键数据——花费、收入、ROAS、CPA 等——每项均附有每日趋势数据图表 - **[AI 助手](ads-manager-ai-agent)**:用自然语言提问,获取全链路分析与优化建议 - **实时效果数据**:覆盖广告系列、广告组和关键词 - **端到端收入追踪**:从搜索 → 安装 → 试用 → 订阅 → LTV - **AI 趋势预测与建议**:助力盈利增长 - **批量管理**:出价、预算、状态与结构 - **[基于规则的自动化](ads-manager-automations)**:管理关键词全生命周期 - **[市场洞察](ads-manager-market-intelligence)**:覆盖 50+ 个国家的竞品关键词策略 - **[CPP A/B 测试](ads-manager-cpp-ab-tests)**:对比自定义产品页面,找出最佳方案 <CustomDocCardList ids={['adapty-ads-manager-get-started', 'ads-manager-overview', 'ads-manager-ai-agent', 'adapty-ads-manager-analytics', 'ads-manager-create-campaign', 'ads-manager-create-ad-group', 'ads-manager-manage-keywords', 'ads-manager-automations', 'ads-manager-market-intelligence', 'ads-manager-cpp-ab-tests']} /> ## 为什么选择 Adapty Ads Manager?\{#why-choose-adapty-ads-manager\} 因为我们为您提供**市场上最精准的数据**。 与原生 Apple Ads 控制台或 MMP 不同,我们的数据是**实时、无损且完整关联**的——涵盖试用、订阅和 LTV,没有延迟,也不存在归因缺口。 凭借简便的接入方式和流畅的使用体验,您可以在一个地方管理所有内容,无需在多个工具之间来回切换。 ## 入门指南 \{#get-started\} 要开始使用 Adapty Ads Manager,请按照[指南](adapty-ads-manager-get-started)操作,之后即可开始探索。 --- # File: adapty-ads-manager-get-started --- --- title: "开始使用 Adapty Ads Manager" description: "从 Apple Ads 导入历史数据,并在看板上获取实时更新" --- [Adapty Ads Manager](adapty-ads-manager) 是专为 Apple Ads 打造的优化与分析平台。 本指南将通过两个步骤,带你快速上手 Adapty Ads Manager: 1. 安装 Adapty SDK,让它追踪您的购买数据。 2. 将 Adapty Ads Manager 连接到您的 Apple Ads 账户,以导入历史数据并开始追踪实时更新。 :::note Adapty Ads Manager 不使用 **App settings** 中的 [Apple Ads 集成](apple-search-ads)。 若要使用 Adapty Ads Manager,您只需完成本指南中描述的设置即可。 ::: ## 1. 安装 Adapty SDK \{#1-install-the-adapty-sdk\} :::important Adapty Ads Manager 是一款**独立产品**。即使你的付费墙、订阅或数据分析功能并非由 Adapty 处理,也可以直接使用它——无需将整个技术栈迁移至 Adapty。 若要获取准确的收入数据,最低配置要求是:以观察者模式安装 Adapty SDK,并在 Adapty 中启用 App Store 服务器通知。 ::: 若要将收入数据与广告系列效果关联,请让 Adapty 追踪你的购买记录: 1. 第一步取决于你是否已经实现了应用内购买: - 如果你**已经通过 Adapty 实现了应用内购买**,此阶段无需做任何其他操作。 - 如果你**已经在不使用 Adapty 的情况下实现了应用内购买**,且不打算迁移到 Adapty,请以观察者模式为你的平台安装 Adapty SDK。此阶段只需将 SDK 添加到项目中,以观察者模式激活它,并上报交易记录: - [iOS](implement-observer-mode) - [Android](implement-observer-mode-android) - [React Native](implement-observer-mode-react-native) - [Flutter](implement-observer-mode-flutter) - [Unity](implement-observer-mode-unity) - [Kotlin Multiplatform](implement-observer-mode-kmp) - [Capacitor](implement-observer-mode-capacitor) - 如果你**尚未实现应用内购买且希望使用 Adapty**,请按照[快速入门指南](quickstart)完成相关步骤,将购买处理委托给 Adapty。 2. 若要直接从 App Store 接收收入相关的更新,请[在 Adapty 中启用 App Store 服务端通知](enable-app-store-server-notifications)。 ## 2. 连接 Apple Ads \{#2-connect-apple-ads\} :::important 您需要在 Apple Ads 中拥有 **Account Admin** 角色,才能将 Apple Ads 连接到 Adapty。 ::: 接下来,您需要将 Adapty Ads Manager 账号与 Apple Ads 账号关联: 1. 点击页眉中的 Adapty 标志,选择 **Search Ads**。 2. 点击 **Continue with Apple**。 3. 登录您的 Apple 账户。 4. 选择您要授予 Adapty Ads Manager 的访问权限: - **Read and Write**:提供对所有广告系列组的访问权限。 - **Limited access**:选择特定广告系列组,并分配 **Read & Write** 角色,以仅授予对这些组的访问权限。 5. 点击 **Grant access**。 之后,Adapty 将开始从 Apple Ads 同步您的历史数据。您现在就可以开始探索 Adapty Ads Manager,但导入所有历史数据需要一些时间。 ## 下一步 \{#whats-next\} 成功同步交易数据后,请继续学习如何: - [管理您的广告系列、广告组和关键词](ads-manager) - [设置自动化规则,根据广告系列效果调整出价](ads-manager-automations) --- # File: ads-manager-overview --- --- title: "Adapty Ads Manager 概览" description: "在一处查看所有关键 Apple Ads 数据,每项均附有趋势图表。" --- **Overview** 页面将所有关键 Apple Ads 数据集中展示,每项指标均附有趋势图表。 默认情况下,该页面显示所有已连接应用的数据。如需查看单个应用,请从顶部的应用下拉菜单中进行选择。 要打开它,请在 Adapty Ads Manager 左侧边栏中点击 **Overview**。 :::tip 如果不想逐项查看各数据图表,可以直接向 [AI Agent](ads-manager-ai-agent) 询问需要重点关注的内容摘要。 ::: ## 数据图表 \{#metrics\} 每个数据图表以卡片形式呈现,并包含所选时间范围内的趋势图。有关数据图表的定义和计算公式,请参阅 [Apple 广告管理器中的数据图表](adapty-ads-manager-metrics)。 ## 配置显示的数据图表 \{#configure-displayed-metrics\} 要更改 **Overview** 页面上显示的数据图表,请点击 **Edit metrics**。在此处,您可以: - **添加数据图表**:点击 **Add metric**,然后勾选您想要的数据图表对应的复选框。 - **移除数据图表**:在 **Add metric** 面板中取消勾选其复选框,或点击其旁边的 **×**。 ## 控件 \{#controls\} 使用顶部的控件来调整**概览**页面所显示的内容: - **Date range**:选择预设时间段(**Last 7 days**、**Last 30 days**、**Last 90 days**),或输入自定义范围。所有数据图表和汇总数值均会根据所选时间段更新。 - **Chart type**:在堆叠柱状图、折线图和饼图之间切换。 - **Revenue display**:选择收入数据的计算方式: - **Gross revenue**:扣除任何费用前的总收入。 - **Proceeds after store commission**:扣除 Apple 佣金后的收入。 - **Proceeds after store commissions and taxes**:扣除 Apple 佣金和适用税费后的净收入。 --- # File: ads-manager-ai-agent --- --- title: "Adapty 广告管理器中的 AI 智能体" description: "用自然语言提问,获取有关你的 Apple Ads 账户的全漏斗分析和建议。" --- AI 智能体是 Adapty 广告管理器中的一个对话助手,可以用自然语言回答你关于 Apple Ads 账户的问题。 它基于完整的转化漏斗——展示、安装、试用、订阅和收入——因此能够分析收入和 ROAS,而不仅仅是点击数据。Adapty 内置的归因功能可将漏斗数据近实时地提供给该智能体。 该智能体仅提供建议:它会分析您的账户并给出操作建议,但不会替您修改广告活动、出价或预算。 ## 你可以问什么 \{#what-you-can-ask\} 向 agent 询问你 Apple Ads 账户的任何内容。agent 可以: - **账户概览**:汇总账户整体情况,标出最需要优先关注的问题。 - **低效对象**:找出投入产出比不佳的广告系列,以及没有带来转化的关键词。 - **预算**:识别已触达每日预算上限的广告系列,并给出是否提高预算的建议。 - **出价与暂停决策**:建议是否提高出价或维持现状,以及是否暂停某个广告系列。 - **地域表现**:展示各国家/地区的表现差异,并给出预算重新分配建议。 ## 示例问题 \{#example-questions\} 当你说明具体的数据图表、时间窗口以及正在权衡的决策时,AI 智能体给出的答案最有价值。例如: - 哪些关键词的 ROAS 最高,我应该如何向这些关键词重新分配预算? - 哪些关键词在过去 30 天内花费较高但没有带来试用或订阅,哪些应该暂停? - 哪些广告活动在保持盈利的同时触达了每日预算上限,每个应该提高多少? - 按 ROAS 和每次订阅成本比较各国表现——应该将预算转移到哪里? - 我应该降低这个关键词的出价、暂停它,还是再观察一段时间?请展示背后的漏斗数据。 - 哪些关键词能带来低成本安装,但很少转化为付费订阅? ## 打开 AI 助手 \{#open-the-ai-agent\} 要打开助手,点击账户顶部的 **Ask AI Agent**。 提问之前,请先选择一个应用以设定助手的分析范围。然后输入问题并发送。 助手会在后台处理任务,你可以在它准备回答的同时继续在 Adapty Ads Manager 中正常操作。 要更改回答问题的模型,请使用消息输入框旁边的选择器。 ## 访问历史对话 \{#access-previous-chats\} 紧凑面板仅显示当前会话。若需查看历史对话,点击面板顶部的展开按钮进入全屏模式,左侧会出现 **Chats** 列表,你可以在其中搜索历史会话,或通过 **New chat** 开始新对话。 ## 限制 \{#limitations\} AI 智能助手仅提供建议,不会自动为你执行任何操作。请查看其建议,并在[管理推广活动](ads-manager-create-campaign)和[关键词](ads-manager-manage-keywords)时自行应用更改。 --- # File: adapty-ads-manager-metrics --- --- title: "Adapty 广告管理器中的数据图表" description: "在 Adapty 广告管理器中查看应用分析数据。" --- Adapty 广告管理器提供全面的数据图表,帮助您衡量广告系列效果与用户行为。 ## 性能 \{#performance\} | 数据图表 | 说明 | |--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Spend | 用户每次点击广告所产生的费用总和。 | | Impressions | 在报告周期内,您的赞助广告在 App Store 搜索结果中展示的次数。 | | CPM | 每一千次广告展示的平均费用。平均 CPM = 花费 /(展示次数 / 1000)注意:对于采用 CPT 定价模式的 App Store 搜索结果广告系列,此处显示有效 CPM。 | | Taps | 在报告周期内,用户点击广告的次数。 | | CPT | 每次点击广告的平均费用。平均 CPT = 花费 / 点击次数 | | TTR | 用户点击广告的次数除以广告获得的总展示次数。TTR = 点击次数 / 展示次数 * 100% | | Downloads(总计) | 在报告周期内,通过点击和浏览产生的新下载及重新下载总次数。 | | Downloads(浏览带来) | 在 24 小时窗口内浏览了您的广告但未点击的用户所产生的下载及重新下载次数。 | | Downloads(点击带来) | 在 30 天窗口内点击了您的广告的用户所产生的新下载及重新下载总次数。 | | 平均 CPA(总计) | 总平均每次获客成本(CPA),即广告系列总花费除以报告周期内通过浏览或点击广告产生的总下载次数。 | | 平均 CPA(点击带来) | 点击带来的平均每次获客成本(CPA),即广告系列总花费除以报告周期内点击带来的下载次数。 | | 下载率(总计) | 通过浏览或点击广告产生的总下载次数除以报告周期内的总点击次数。公式:若点击次数 > 0,则(总下载次数 / 点击次数)× 100%,否则为 0%。 | | 下载率(点击带来) | 通过点击广告产生的总下载次数除以报告周期内的总点击次数。公式:若点击次数 > 0,则(点击带来的下载次数 / 点击次数)× 100%,否则为 0%。 | | DPM(总计) | 每千次展示下载量(DPM),即每一千次展示所带来的下载次数。公式:若展示次数 > 0,则(总下载次数 / 展示次数)× 1000,否则为 0。 | ## 转化率 \{#conversions\} :::note 收入、ARPU、ARPPU、ARPAS、ROAS 和 ROI 也可作为同期群数据图表,用于对用户群体进行基于时间的分析。 ::: | 数据图表 | 描述 | |--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Conversions | Conversions(转化数)是报告期内的转化事件总数。计算公式:试用开始数 + 订阅开始数 + 非订阅购买数 | | Conversion CR | Conversion CR(转化率)是总下载量中产生转化的百分比。计算公式:若总下载量 > 0,则为(转化数 / 总下载量)× 100%,否则为 0% | | Cost per Conversion | Cost per Conversion(每次转化成本)是总花费除以转化数。计算公式:若转化数 > 0,则为花费 / 转化数,否则为 0 | | Revenue | Revenue(收入)是在所选时间段内,应用通过购买、续订或其他变现转化所产生的总金额(扣除平台佣金前)。 | | ROAS | ROAS(广告支出回报率)是广告带来的收入除以广告花费,以百分比表示。计算公式:若花费 > 0,则为(收入 / 花费)× 100%,否则为 0% | | ROI | ROI(投资回报率)衡量净利润相对于花费的比例。计算公式:若花费 > 0,则为((收入 − 花费)/ 花费)× 100%,否则为 0% | | ARPU | ARPU(每用户平均收入)是总收入除以独立用户数的平均值。例如:60000 美元收入 / 5000 用户 = 12 美元 ARPU。将此值与每次安装成本(CPI)对比,有助于评估营销活动的效果。 | | ARPPU | ARPPU(每付费用户平均收入)是总收入除以独立付费用户数的平均值。例如:60000 美元收入 / 1000 名付费用户 = 60 美元 ARPPU。它帮助你了解每位付费用户平均带来多少收入。 | | ARPAS | ARPAS(每活跃订阅者平均收入)是总收入除以活跃订阅者数。订阅者指已激活试用期或订阅的用户。例如:60000 美元收入 / 1500 名订阅者 = 40 美元 ARPAS。 | | Installs | Installs(安装数)是首次安装应用的用户总数,以及现有用户的重新安装次数。同一用户在不同设备上的多次安装均计入在内。请注意,未完成的下载或安装中途取消的情况不计入安装数。 | | Installs CR | Installs CR(安装转化率)是总下载量中成功安装应用的用户百分比。计算公式:若总下载量 > 0,则为(安装数 / 总下载量)× 100%,否则为 0% | | CPI | CPI(每次安装成本)是 Adapty 统计的每次安装花费。计算公式:若安装数 > 0,则为花费 / 安装数,否则为 0 | | Trials | Trials(试用数)是报告期内新开始的试用订阅数量。 | | Trial CR | Trial CR(试用转化率)是总下载量中开始试用的百分比。计算公式:若总下载量 > 0,则为(试用数 / 总下载量)× 100%,否则为 0% | | Cost per Trial | Cost per Trial(每次试用成本)是总花费除以新试用开始数。计算公式:若试用数 > 0,则为花费 / 试用数,否则为 0 | | Trials converted | Trials converted(试用转付费数)是在报告期内成功转化为付费订阅的试用订阅数量。 | | Trial converted CR | Trial converted CR(试用转付费率)是试用订阅转化为付费订阅的百分比。计算公式:若试用数 > 0,则为(试用转付费数 / 试用数)× 100%,否则为 0% | | Cost per Trial converted | Cost per Trial converted(每次试用转化成本)是总花费除以同期已转化的试用数。计算公式:若试用转付费数 > 0,则为花费 / 试用转付费数,否则为 0 | | Subscriptions | Subscriptions(订阅数)是报告期内新增订阅(非试用)的总数量。 | | Subscription CR | Subscription CR(订阅转化率)是总下载量中直接开始付费订阅(无免费试用)的百分比。计算公式:若总下载量 > 0,则为(订阅数 / 总下载量)× 100%,否则为 0% | | Cost per Subscription | Cost per Subscription(每次订阅成本)是总花费除以新增订阅数。计算公式:若订阅数 > 0,则为花费 / 订阅数,否则为 0 | | Non-subscriptions started | Non-subscriptions started(非订阅购买数)是非订阅类一次性应用内购买的总数量。 | | Non-subscription CR | Non-subscription CR(非订阅转化率)是总下载量中发生非订阅购买的百分比。计算公式:若总下载量 > 0,则为(非订阅购买数 / 总下载量)× 100%,否则为 0% | | Cost per Non-subscription | Cost per Non-subscription(每次非订阅购买成本)是总花费除以非订阅购买数。计算公式:若非订阅购买数 > 0,则为花费 / 非订阅购买数,否则为 0 | ## 高级下载 \{#advanced-downloads\} | 数据图表 | 描述 | |--------|------| | 新下载量(总计) | 报告期内点击型和浏览型新下载量的总和。 | | 新下载量(浏览型) | 用户查看了您的广告但未点击,且此前未下载过您的应用,此类新下载在 24 小时归因窗口内计入。 | | 新下载量(点击型) | 用户点击了您的广告且此前未下载过您的应用,此类新下载在 30 天归因窗口内计入。 | | 新下载量占比(点击型) | 显示点击广告带来的总下载量中,新下载量所占的百分比(统计 30 天归因窗口内点击广告的用户)。 | | 重新下载量(总计) | 报告期内点击型和浏览型重新下载量的总和。 | | 重新下载量(浏览型) | 用户查看广告后未点击,在 24 小时窗口内重新下载的次数。当用户下载应用后将其删除,并在查看广告后在同一设备或其他设备上再次下载时计入。 | | 重新下载量(点击型) | 用户点击广告后,在 30 天归因窗口内重新下载的次数。当用户下载应用后将其删除,并在点击广告后在同一设备或其他设备上再次下载时计入。 | | 重新下载量占比(点击型) | 显示点击广告带来的总下载量中,重新下载量所占的百分比。 | ## 洞察 \{#insights\} | 数据图表 | 描述 | |--------|------------------------------------------------------------------------------------------------------------------------------------------| | Impression Share | Impression Share 是您的广告在相同搜索词的总展示次数中所占的展示百分比。 | | Rank | Rank(当前)是您的应用在特定国家或地区中,针对所选搜索词的展示份额排名位置。 | | Search Popularity | Search Popularity(当前)是基于国家或地区的搜索词热度。评分范围为 1–5,5 代表搜索量最高。 | --- # File: ads-manager-create-campaign --- --- title: "在 Adapty 广告管理器中管理广告活动" description: "在 Adapty 广告管理器中创建和编辑 Apple Ads 广告活动。" --- Adapty 广告管理器与 Apple Ads 实现双向集成:你可以获取近实时的效果数据,还能直接在 Adapty 看板中创建和编辑广告活动,比在原生界面中操作便捷得多。 如果您在原生 Apple Ads 看板中创建了广告系列,它将在 24 小时内自动显示在 Adapty Ads Manager 中。 除了[查看全面的广告系列数据](adapty-ads-manager-analytics),您还可以管理所有广告系列设置: - 创建广告系列 - 编辑现有广告系列 - 启动和暂停广告系列 :::tip 如需找出需要关注的广告系列(例如已达到每日预算上限或投入产出比不佳的广告系列),可以咨询 [AI 智能助手](ads-manager-ai-agent)。 ::: ## 什么是广告系列 \{#what-is-a-campaign\} 广告系列专注于单个应用,并在 App Store 的某个版位投放广告。每个广告系列包含每日预算以及[广告组](ads-manager-create-ad-group),广告组针对推广应用的特定策略。广告系列将根据其预算设置持续投放。 :::important 请注意,广告系列本身无法独立运行;广告组才是设置默认出价、目标受众和关键词的层级。没有广告组,广告系列将没有定向或出价设置,也无法投放。创建广告系列后,请[至少添加一个广告组](ads-manager-create-ad-group)以激活它。 ::: ## 创建广告活动 \{#create-campaigns\} 要创建广告活动: 1. 打开 **Ads Manager** 页面,点击 **+**,选择 **Create campaign** 启动广告活动向导。 2. 为广告选择一个 **placement**,然后点击 **Start**: | 版位 | 广告展示位置 | | --- | --- | | **Search Results** | 位于 App Store 搜索结果顶部。 | | **Search Tab** | 在搜索标签页的推荐应用列表中,显示于用户搜索之前。 | | **Today Tab** | 在 App Store 的 Today 页面上。 | | **Product Pages** | 在其他产品页面的 **You Might Also Like** 列表中。Apple 会自动为你选择相关页面。 | | **Duplicate a Campaign** | 复用已有广告活动的版位、广告组和关键词。 | 3. 对于 **Search Result campaigns**,选择**营销活动类型**。按类型对营销活动进行分组可以整理报告数据,并允许 Adapty 建议合适的关键词策略。 :::note 选择 **Max Conversions** 可跳过手动关键词选择,让 Apple 的自动出价系统优化转化效果。 ::: | 推广活动类型 | 说明 | | --- | --- | | **Generic** | 描述应用功能的非品牌词。 | | **Competitor** | 竞争对手的品牌关键词。 | | **Discovery** | 搜索匹配自动发现新关键词,方便筛选效果好的词。 | | **Max Conversions** | Apple 自动出价策略,以转化为优化目标。在 Settings 步骤中设置 **Target CPA**。 | | **Brand** | 与应用品牌相关的关键词。 | | **Custom** | 无预设策略,从零开始自定义推广活动。 | 4. 选择**应用**(你想要推广的应用)和**广告系列组**(运营并支付广告系列费用的 Apple Ads 账户)。 5. 选择要定向的**国家或地区**。为便于优化,建议每个国家使用一个广告系列。 6. 配置广告系列的**基本设置**。受众定向和关键词在广告系列的[广告组](ads-manager-create-ad-group)中设置,广告组在创建广告系列后添加。 | 设置 | 描述 | | --- | --- | | **Campaign name** | 自动从应用、版位和国家填充。可随时编辑。 | | **Daily budget** | 该活动每天可花费的金额。 | | **Target CPA** | 自动出价系统的每次获客成本目标。仅在 Max Conversions 活动中显示。 | | **Ad scheduling** | 可选。除非设置了更晚的开始日期,否则活动将立即启动;添加结束日期可在指定时间停止活动。 | 信用额度账户还需在此步骤中填写开票详情。 7. 查看摘要,确认详情,然后点击 **Create campaign**。 8. 继续[配置广告组](ads-manager-create-ad-group)。广告系列若无广告组则无法投放——广告组用于确定目标受众和/或关键词。 ## 编辑广告系列 \{#edit-campaigns\} 如需编辑已创建的广告系列: 1. 通过以下任意方式打开广告系列设置: - 在 **Ads Manager > Campaigns** 中点击广告系列名称,然后点击右上角的 **Edit campaign**。 - 或勾选广告系列名称旁的复选框,然后点击 **Actions > Edit campaign settings**。 2. 调整广告系列设置。您可以编辑广告系列名称、广告系列类型(针对搜索结果广告系列)、国家/地区及每日预算。如需更改广告展示位置、出价策略或排期,请改为创建新的广告系列。 3. 点击 **Save changes**。 您也可以在广告系列表格的 **Campaign type** 列中直接修改广告系列类型。 :::note 直接在 Apple Ads 中所做的修改会自动同步到 Adapty Ads Manager,但可能需要一些时间才能显示。 ::: ## 导出活动 \{#export-campaigns\} 要将活动表格导出为 CSV,请点击表格上方的下载图标,然后选择 **Export current page** 或 **Export all pages**。 **Export all pages** 会将所有页面的活动合并到一个文件中下载。下载过程中会显示进度弹窗,你可以随时取消。此外还提供两个可选筛选项: - **Enabled only**:仅包含已启用的活动。 - **With spend ≥**:仅包含消耗金额超过指定阈值的活动。 - **Group by country**:将每条活动数据按国家细分展示。 表格导出时的内容与您在看板上看到的一致,包含您选择显示的列。 ## 启动和暂停广告活动 \{#launch--pause-campaigns\} 在 Adapty 广告管理器中启动或暂停任意广告活动的步骤如下: 1. 进入 **Ads Manager > Campaigns**。 2. 在 **Status** 列中,点击广告活动名称旁边的开关以启动或暂停。 ## 删除广告活动 \{#delete-campaigns\} Adapty Ads Manager 无法删除广告活动。您可以改为[暂停](#launch-pause-campaigns)广告活动,以停止消耗预算,同时保留其设置和分析数据。 --- # File: ads-manager-create-ad-group --- --- title: "在 Adapty Ads Manager 中管理广告组" description: "在 Adapty Ads Manager 中创建和编辑 Apple Ads 广告组。" --- Adapty Ads Manager 与 Apple Ads 实现了双向集成:你可以获取近实时的效果数据,还可以直接在 Adapty 看板中创建和编辑广告活动,操作体验比原生 UI 更加便捷。 如果您在原生 Apple Ads 看板中创建了广告组,该广告组将在 24 小时内自动出现在 Adapty Ads Manager 中。 除了[查看全面的广告系列数据](adapty-ads-manager-analytics),您还可以管理所有广告组设置: - 创建广告组 - 编辑现有广告组 - 启动和暂停广告组 ## 什么是广告组 \{#what-is-ad-group\} 广告组隶属于某个[广告系列](ads-manager-create-campaign),用于配置广告的定向和竞价策略。每个广告组包含竞价设置、受众定向以及[关键词](ads-manager-manage-keywords),共同决定广告在何时向哪些用户展示。广告组让你能够在广告系列内有条理地组织广告策略,并测试不同的定向方案。 :::important 请注意,广告系列必须包含广告组才能投放。广告组是设置默认出价、目标受众和关键词的层级,没有至少一个广告组,广告系列将没有定向或出价设置,无法正常投放。请先创建广告系列,然后[添加至少一个广告组](ads-manager-create-ad-group)以激活它。 ::: ## 创建广告组 \{#create-ad-groups\} 要创建新的 Apple Ads 广告组: 1. 在侧边栏菜单中进入 **Ads Manager**。在任意标签页中,点击表格上方的 **+**,然后选择 **Create ad group**。 2. 选择要添加广告组的应用。 3. 选择要添加广告组的广告系列。 4. 配置广告组设置: - **Ad group name**:为广告组指定的标签,用于在看板中识别和搜索该广告组。 - **Default max CPT bid**:每次广告点击所愿意支付的最高金额。此出价适用于广告组中的所有关键词,除非您为单个关键词单独设置出价。 - **CPA cap (limits impressions)**(可选):指定每次点击转化(例如下载或其他目标操作)所愿意支付的最高金额,并为广告组中所有关键词设置出价上限。 出价上限的计算方式为:将您设定的 CPA 上限乘以点击转化率:`CPA Cap × CR (Tap-Through)`。如果关键词的最高 CPT 出价低于此值,则以较低的最高 CPT 出价为准。 例如,若您的 CPA 上限为 $5,点击转化率为 65%,则该广告组所有关键词的最高出价为 $3.25。若最高 CPT 设为 $4,实际应用的最高出价仍为 $3.25。 - **Search Match**:开启后可自动将广告与相关搜索匹配,无需手动指定关键词。启用后,Apple Ads 可能会根据您应用的元数据和类别展示广告。 - **Audience**:目标受众设置,决定哪些用户能看到您的广告。 - **All eligible users**:向所有符合条件的用户展示广告。 - **Specific audiences**:通过以下配置定向特定用户群体: - **Devices**:定向 iPad、iPhone 或两者兼顾。 - **Customer type**:定向所有用户、新用户或回访用户。 - **Gender**:按性别定向或面向所有用户。 - **Age range**:定向特定年龄段或所有用户。 - **Ad scheduling**(可选,选择 **Specific audiences** 后可用):设置广告投放时间: - **Start date and time**:广告组开始投放广告的时间。 - **End date**(可选):广告组停止投放广告的时间。 5. 点击 **Create**。 6. 如果您的广告活动版位类型为 **Search results**,现在可以[添加关键词](ads-manager-manage-keywords)以开始投放广告。其他版位类型则无需额外操作。 :::note 在 **Max Conversions** 广告系列中,广告组有一个 **Bidding strategy** 切换项:**Standard** 或 **Automated**。没有 **Default max CPT bid** 字段。选择 **Automated** 后,您设置目标 CPA,Apple 会自动为您优化出价。 ::: ## 编辑广告组 \{#edit-ad-groups\} 要编辑已创建的广告组: 1. 通过以下任一方式打开广告系列设置: - 在 **Ads Manager > Ad groups** 中点击广告系列名称,然后点击右上角的 **Edit ad group**。 - 或勾选广告组名称旁边的复选框,然后点击 **Actions > Edit ad group settings**。 2. 调整广告组设置。应用、广告系列、受众类型以及开始日期和时间不可更改。在自动广告组中,只有名称可以编辑。 3. 点击 **Save changes**。 如需复制广告组,请在 **Ads Manager > Ad groups** 中选中该广告组,然后点击 **Actions > Duplicate ad group**。 :::note 直接在 Apple Ads 中对广告组所做的编辑会自动同步到 Adapty Ads Manager,但可能需要一些时间才能在 Adapty Ads Manager 中显示。 ::: ## 导出广告组 \{#export-ad-groups\} 如需将广告组表格导出为 CSV 文件,点击表格上方的下载图标,然后选择 **Export current page** 或 **Export all pages**。 **Export all pages** 会将所有页面的广告组数据合并到一个文件中下载。下载过程中会显示进度弹窗,可随时取消。该功能提供两个可选筛选项: - **Enabled only**:仅包含处于启用状态的广告组。 - **With spend ≥**:仅包含消耗金额超过指定阈值的广告组。 - **Group by country**:将每个广告组的数据按国家维度拆分展示。 导出的表格将按照您在看板中选择显示的列呈现。 ## 启动与暂停广告组 \{#launch--pause-ad-groups\} 在 Adapty 广告管理器中启动或暂停广告组的步骤如下: 1. 进入 **Ads Manager > Ad groups**,或打开某个推广活动页面查看其广告组。 2. 在 **Status** 列中,点击广告组名称旁边的切换按钮将其开启或关闭。 --- # File: ads-manager-manage-keywords --- --- title: "在 Adapty Ads Manager 中管理关键词" description: "在 Adapty Ads Manager 中添加和管理 Apple Ads 关键词、否定关键词和 SKAG 关键词。" --- Adapty Ads Manager 与 Apple Ads 实现了双向集成:你可以获得近实时的效果数据,还能直接在 Adapty 看板中创建和编辑关键词,比原生界面方便得多。 如果您在原生 Apple Ads 看板中创建了关键词,它将在 24 小时内自动显示在 Adapty Ads Manager 中。 除了[查看全面的数据分析](adapty-ads-manager-analytics)之外,您还可以管理所有关键词设置: - 向广告组添加关键词 - 添加否定关键词 - 将关键词添加为 SKAG(单关键词广告组) - 直接在表格中编辑关键词 - 对多个关键词执行批量操作 - 启动和暂停关键词 :::tip 要找出账户中表现不佳的关键词(例如有花费但没有转化的关键词),可以使用 [AI Agent](ads-manager-ai-agent)。 ::: ## 什么是关键词 \{#what-are-keywords\} 关键词是触发您的广告出现在 App Store 搜索结果中的搜索词。它们组织在[广告组](ads-manager-create-ad-group)内,而广告组隶属于[广告活动](ads-manager-create-campaign)。这种层级结构使您能够有效地组织和管理广告策略。 :::important 关键词仅适用于**搜索结果**版位类型的广告活动。对于其他版位类型(搜索标签页或产品页面)的广告活动,不使用关键词。 ::: ### 标准关键词 \{#standard-keywords\} 标准关键词是您出价以触发广告的主要词语。当用户在 App Store 中搜索这些词语时,您的广告可能会出现在搜索结果中。 ### 否定关键词 \{#negative-keywords\} 否定关键词可防止您的广告出现在与您的应用不相关的搜索中。通过添加否定关键词,您可以减少在无关搜索上的无效支出。 否定关键词可以在广告组级别添加,也可以作为跨组否定关键词同时应用于多个广告活动。 ### 关键词作为 SKAG(单关键词广告组)\{#keywords-as-skag-single-keyword-ad-group\} SKAG(单关键词广告组)是一种策略,即为每个关键词创建单独的广告组。这种方式可以让您: - 精确控制高价值关键词的出价 - 更好地在关键词级别分析效果 SKAG 对于识别表现最佳的关键词并通过专属广告组最大化其潜力特别有用。 ## 添加关键词 \{#add-keywords\} 要向广告组添加关键词: :::note **Maximize Conversions** 广告系列中的关键词不使用出价——出价由广告系列的 CPA 目标自动管理。**CPT bid** 字段对其不适用。 ::: 1. 从侧边栏菜单进入 **Ads Manager**。在任意标签页中,点击表格上方的 **+**,然后从下拉菜单中选择 **Add keywords**。 2. 在弹出窗口中,选择要添加关键词的广告系列和广告组。在一个广告系列中选择好广告组后,可以继续选择其他广告系列并将更多广告组添加到列表中。 3. 点击 **Select** 继续。 4. 在 **Add keywords** 对话框中,在 **Keywords list** 字段输入关键词。如果你有逗号分隔的关键词文件,可以直接粘贴其内容,Adapty Ads Manager 将批量上传所有关键词。 5. 为表格中的每个关键词进行配置: - **Match type**:选择 **Exact**(精确匹配)或 **Broad**(广泛匹配) - **CPT bid**:为该关键词设置每次点击的最高出价,或留空以使用广告组的默认最高 CPT 出价 6. 检查关键词后,点击 **Add X keywords**(X 为你要添加的关键词数量)。 :::important 关键词一旦保存,其匹配类型就无法更改。如需更改匹配类型,请删除该关键词并以所需匹配类型重新添加。 ::: ## 添加否定关键词 \{#add-negative-keywords\} 添加否定关键词的步骤: 1. 从侧边栏菜单进入 **Ads Manager**。在任意标签页中,点击表格上方的 **+**,然后从下拉菜单中选择 **Add negative keywords**。 2. 在 **Add negative keywords to** 弹窗中,选择要添加否定关键词的层级: - **Selected campaigns**:添加广告系列级别的否定关键词。 - **Selected ad groups**:添加广告组级别的否定关键词。 - **All ad groups in selected campaigns**:将广告组级别的否定关键词添加到所选广告系列的所有广告组中。 :::note 请注意以下几点: - 广告组级别的否定关键词优先级高于广告系列级别的否定关键词。 - 如果你将否定关键词添加到所选广告系列的所有广告组中,后续若向这些广告系列新增广告组,则需要手动为新广告组添加否定关键词。 ::: 3. 在 **Keywords list** 字段中输入否定关键词。如果你有一个以逗号分隔的关键词文件,可以直接粘贴其内容,Adapty Ads Manager 将批量上传所有关键词。 4. 在表格中,为每个关键词选择 **Match type**: - **Exact**:仅排除完全匹配的关键词或非常接近的变体。 - **Broad**:排除该关键词及相关搜索词。 或勾选关键词旁边的复选框,批量修改匹配类型。 5. 检查你的否定关键词,然后点击 **Add X keywords**(其中 X 为你要添加的关键词数量)。 :::note 跨组负面关键词在您想要一次性在多个广告系列中排除特定搜索词时尤为实用,既能节省时间,又能确保整体广告策略的一致性。 ::: ## 将关键词添加为 SKAG \{#add-keywords-as-skag\} 要将关键词添加为 SKAG(单关键词广告组): 1. 从侧边栏菜单进入 **Ads Manager**。在任意标签页中,点击表格上方的 **+**,然后从下拉菜单中选择 **Add keywords as SKAG**。 2. 选择要创建 SKAG 广告组的广告活动,可以同时选择多个。 3. 默认情况下,新广告组将使用面向所有用户的默认设置创建。如需修改,请选择 **Copy settings from ad group**,并选择一个已有广告组来复制其设置。 4. 配置新广告组的设置: - **Ad group name prefix**:可选前缀,添加到每个广告组名称前(例如,"SKAG_" 会生成 "SKAG_keyword1"、"SKAG_keyword2" 等)。点击 **Tag** 可将关键词、广告活动名称和国家动态添加到组名中。 - **CPT bid** 和 **CPA cap**:统一为所有关键词设置出价,或选择 **Set CPT bid and CPA cap for each word manually** 分别为每个关键词单独设置。 5. 在 **Keywords list** 字段中输入关键词。如果你有以逗号分隔的关键词文件,可以直接粘贴其内容,Adapty Ads Manager 将批量导入所有关键词。 6. 在表格中为每个关键词选择 **Match type**: - **Exact**:仅匹配完全相同的关键词或非常接近的变体 - **Broad**:匹配该关键词及相关搜索词 也可以勾选多个关键词旁的复选框,批量修改匹配类型。 7. 选择 **Check for duplicates in target campaign**,确保目标广告活动中没有重复的关键词。 8. 点击 **Create** 创建 SKAG 广告组。 每个关键词将被放置在所选广告系列中各自独立的广告组中,以便您单独管理和优化它们。 ## 编辑关键词 \{#edit-keywords\} 编辑已有关键词: 1. 前往 **Ads Manager > Keywords** 或 **Ads Manager > Negative keywords**,在表格中找到要编辑的关键词;也可以进入某个广告系列页面,再进入广告组页面找到该关键词。 2. 直接在表格中编辑对应值: - **CPT bid**:点击出价值,输入新的每次点击最高费用出价 - **Status**:使用切换开关来暂停或激活关键词 :::note 直接在 Apple Ads 中对关键词所做的修改会自动同步到 Adapty Ads Manager,但可能需要一些时间才能在 Adapty Ads Manager 中显示。 ::: ## 批量操作 \{#bulk-actions\} 你可以对多个关键词执行批量操作,以节省时间并更高效地管理关键词。 执行批量操作的步骤: 1. 进入 **Ads Manager > Keywords** 或 **Ads Manager > Negative keywords** 标签页。 2. 勾选关键词旁边的复选框,选中您要管理的多个关键词。 3. 点击 **Actions** 下拉菜单,选择以下操作之一: - **Add as keywords**:将选中的关键词添加为标准关键词 - **Add as negative keywords**:将选中的关键词添加为否定关键词 - **Add as SKAG**:为选中的关键词创建单关键词广告组 - **Activate**:激活选中的关键词 - **Pause**:暂停选中的关键词 - **Create segment from keywords**:根据选中的关键词创建受众市场细分 - **Copy keywords**:将选中的关键词名称复制到剪贴板 - **Edit CPT bids**:编辑选中关键词的 CPT 出价。可通过以下方式进行编辑: - **Set to**:将多个出价设置为指定金额。 - **Increase by/decrease by**:按指定美元金额或出价百分比增加或降低出价。可选择设置出价上限,以避免意外超支。 - **Set to average CPT**:使用 CPT(每次点击费用)指标对齐出价,并设置乘数系数。例如,当效果低于预期时将乘数设为 0.9,效果超预期时设为 1.1。 - **Set to average CPA**:使用 CPA(每次转化费用)指标对齐出价,并设置乘数系数。 **Negative keywords** 选项卡包含有限的一组操作: - **Add as keywords** - **Add as negative keywords** - **Add as SKAG** - **Delete keywords** :::tip 批量操作在以下场景中尤为实用: - 在不同类型(标准、否定、SKAG)之间转换关键词 - 快速为多个关键词添加其他匹配类型 - 筛选效果最佳的关键词并调整其出价 - 识别效果不佳的关键词并将其暂停 ::: ## 导出关键词 \{#export-keywords\} 如需将关键词表导出为 CSV,请点击表格上方的下载图标,然后选择 **Export current page** 或 **Export all pages**。 **Export all pages** 会将所有页面的关键词一次性下载到单个文件中。下载过程中会显示进度弹窗,您可以随时取消。此外,还提供两个可选筛选项: - **Enabled only**:仅包含已启用的关键词。 - **With spend ≥**:仅包含花费超过指定阈值的关键词。 - **Group by country**:将每个关键词按国家/地区进行细分。 表格将按照您在看板上看到的样式导出,包含您选择显示的列。 ## 探索关键词级别的数据图表 \{#explore-keyword-level-charts\} 你可以直接在 **Ads Manager > Keywords** 表格中,针对任意关键词打开对应的数据图表,从而对每个关键词进行精确的逐日效果分析。 点击表格中关键词旁边的图表图标即可显示数据图表。 默认情况下,图表将显示所选关键词的 **Spend** 数据图表。 你可以同时展示多个指标,以便发现相关性和随时间的变化趋势。点击 **+** 可添加新的指标。 点击 **Reset** 重新开始,或直接取消勾选数据图表复选框将其隐藏。 ## 出价历史 \{#bid-history\} 要查看某个关键词的出价历史,请点击表格中该关键词旁边的**图表图标**。面板打开后会显示两个选项卡:**Metrics** 和 **Bid History**。 - **指标**选项卡以图表形式展示各指标随时间的变化趋势。添加和删除指标的方式与关键词级别图表相同——点击 **+** 添加,或取消勾选以隐藏。图表上每个出价变更节点都会显示一个标记——将鼠标悬停在标记上,可查看该日期的具体出价金额。利用此功能可将出价变更与效果波动相关联:若某指标在变更后出现下滑或突增,标记可精准定位变更时间点。 - **出价历史**选项卡列出每次出价变更的详细信息,包括日期、类型、变更前后的值,以及触发原因——手动调整或自动化规则(并附带规则 ID)。 --- # File: ads-manager-manage-ads --- --- title: "在 Adapty 广告管理器中管理广告" description: "在 Adapty 广告管理器中创建和编辑 Apple Ads 广告。" --- Adapty 广告管理器与 Apple Ads 实现了双向集成:你可以获取近实时的效果数据,还能直接在 Adapty 看板中创建和编辑广告,体验比原生 UI 便捷得多。 如果您在原生 Apple Ads 看板中创建了广告,它将在 24 小时内自动出现在 Adapty Ads Manager 中。 ## 什么是广告 \{#what-are-ads\} 广告是分配到[广告组](ads-manager-create-ad-group)中的广告素材,广告组隶属于某个[广告系列](ads-manager-create-campaign)。每个广告组可以分配一个有效广告。 ## 创建广告 \{#create-ads\} 开始前,请确保你已创建以下内容: - **Ad group**。你可以[直接在 Adapty Ads Manager 看板中创建](ads-manager-create-ad-group)。 - **Custom product page**。你需要[直接在 Apple Ads 中进行设置](https://developer.apple.com/help/app-store-connect/create-custom-product-pages/configure-multiple-product-page-versions/)。在用于广告之前,该页面必须通过 App Store 的审核。 :::note 如果所选广告组中已有正在投放的广告,该广告将被暂停,以便运行新广告。 ::: 如需创建新的 Apple Ads 广告: 1. 从侧边栏菜单进入 **Ads Manager**。在任意标签页中,点击表格上方的 **+**,然后选择 **Create ad**。 2. 选择要投放广告的应用。 3. 选择一个或多个广告组。Adapty 会在每个已选广告组中创建该广告。 4. 输入广告名称。 5. 设置广告状态。关闭 **Status** 开关可稍后再开始投放。 6. 点击 **Select CPP**。页面将显示该应用已通过 App Store 审核的所有自定义产品页面,你只能选择其中一个。 7. 点击 **Create ad**。 ## 编辑广告 \{#edit-ads\} :::note 创建广告后,你可以编辑其名称和状态,但无法更改其 CPP 或将其移至其他广告组。 ::: 要编辑广告名称,可使用以下任意方式: - 在 **Ads Manager > Ads** 中点击广告名称,编辑后点击旁边的复选标记。 - 勾选广告名称旁的复选框,然后点击 **Actions > Edit ad**,修改广告名称或状态后点击 **Save changes**。 :::note 直接在 Apple Ads 中对广告进行的编辑会自动同步到 Adapty Ads Manager,但可能需要一些时间才能在 Adapty Ads Manager 中显示。 ::: ## 导出广告数据 \{#export-ads\} 如需将广告数据表导出为 CSV 文件,点击表格上方的下载图标,然后选择 **Export current page** 或 **Export all pages**。 **Export all pages** 会将所有页面的广告数据合并到一个文件中一次性下载。下载过程中会显示进度弹窗,你可以随时取消。此外,还提供两个可选筛选条件: - **Enabled only**:仅包含已启用的广告。 - **With spend ≥**:仅包含消费金额高于指定阈值的广告。 - **Group by country**:将每条广告数据按国家/地区分组展示。 表格将按照您在看板上的显示效果导出,包含您选择显示的列。 ## 启动与暂停广告 \{#launch--pause-ads\} 要从 Adapty Ads Manager 启动或暂停任何广告,可使用以下任一方式: - 在 **Ads Manager > Ads** 中,打开或关闭 **Status** 开关。 - 勾选广告名称旁的复选框,点击 **Actions > Edit ad**,切换 **Status** 开关,然后点击 **Save changes**。 --- # File: ads-manager-create-segments --- --- title: "在 Adapty Ads Manager 中基于 Apple Ads 归因创建市场细分" description: "在 Adapty Ads Manager 中,只需两次点击即可从广告活动、广告组和关键词创建市场细分。" --- 您可以直接在 [Adapty Ads Manager](adapty-ads-manager) 中创建用户[市场细分](segments)——选择广告系列、广告组或关键词,几步操作即可将其转化为市场细分。这样一来,您无需手动配置细分条件,就能轻松根据获客来源对付费墙和优惠进行个性化设置。 创建市场细分后,您可以用它来分配不同的产品和价格、运行 A/B 测试,以及自定义付费墙外观。 ## 使用场景 \{#use-cases\} 以下是一些基于 Apple Ads 数据创建的市场细分在实际中的使用示例: - **基于关键词的付费墙**。向来自高意向关键词的用户展示以功能为核心的付费墙,向来自泛发现类关键词的用户展示通用付费墙。 - **活动级别的优惠**。为来自特定 Apple Ads 活动的用户提供更长的试用期或专属定价,同时为其他用户保留标准优惠。 - **广告素材与付费墙的一致性**。将来自推广特定功能的广告组的用户,引导至优先展示这些功能的付费墙。 - **高 ROI 广告系列优化**。向来自持续带来更高生命周期价值广告系列的用户展示以高端内容为主、全价的付费墙。 ## 创建市场细分 \{#create-segments\} 在 Adapty Ads Manager 中创建市场细分: 1. 前往 **Ads Manager**,切换到 **Campaigns**、**Ad groups** 或 **Keywords** 标签页。勾选要使用的实体旁边的复选框。请注意,如果选择了多个实体,它们将被合并创建为一个市场细分,而不是每个实体单独创建一个。 2. 点击 **Actions > Create segment from campaigns/ad groups/keywords**。 3. 如需调整,请在 **Create segment** 窗口中更新市场细分详情: - **Adapty project**:您想在其中创建此市场细分的 Adapty 应用。 - **Build segment from campaign/ad group**:从广告活动或广告组创建市场细分时,可在此步骤调整所选的广告活动或广告组。 - **Segment name** - **Segment description** 4. 点击 **Create**。 5. 市场细分创建完成后,您可以开始准备使用它: - 将其添加到[版位](placements)中,与现有的付费墙或用户引导配合使用 - 设计一个新的[付费墙](adapty-paywall-builder)或[用户引导](onboardings),向该市场细分中的用户展示 - 运行[A/B 测试](ab-tests) --- # File: ads-manager-automations-keyword-rules --- --- title: "Adapty 广告管理器中的关键词规则" description: "根据广告系列效果,自动管理关键词生命周期——调整出价、启用或暂停关键词,以及在广告组之间移动关键词。" --- 关键词规则会根据完整漏斗的表现数据自动对关键词采取操作——涵盖从安装、试用、订阅到收入的全链路。你可以使用消耗、CPA、ROAS 和同期群等数据图表来定义触发条件,并设置条件满足时规则自动执行的动作。 规则按你设定的计划周期运行,无需人工干预即可响应表现变化。 ## 可用操作 \{#available-actions\} 每条关键词规则在满足条件时执行一个操作: | 操作 | 说明 | |--------|-------------| | **Change bid** | 提高、降低或设置 CPT 出价 | | **Enable keyword** | 重新启用已暂停的关键词 | | **Pause keyword** | 暂停活跃的关键词 | | **Add as keyword to…** | 将关键词复制到另一个广告组,并指定出价和匹配类型 | | **Add as negative keyword to…** | 将关键词作为否定关键词添加到指定广告组或广告系列中 | ## 创建关键词规则 \{#create-a-keyword-rule\} 您可以从模板创建关键词规则,也可以从头手动创建。 ### 从模板创建规则 \{#from-a-template\} Adapty 提供了针对常见优化场景的现成模板,常用模板包括: - **减少无转化关键词的浪费**:在支出 > X 且安装量或试用量 = 0 的情况下降低出价。 - **扩大高效关键词的投放**:在 ROAS > 目标值或 CPA < 目标值的情况下提高出价。 如需从模板创建规则: 1. 在左侧边栏中,转到 **Automations** 并点击 **Templates**。 2. 选择一个模板并点击 **Next**。 3. 查看并调整预填设置: - **Rule name**:自动设置为模板名称和当前日期(例如,"Scale Winning Keywords - [2025-11-12]")。 - **Apply to**:选择规则应用的广告活动组、应用、广告活动或广告组。 - **Conditions**:根据需要修改预配置的条件。 - **Action**:根据需要修改预设操作。 - **Schedule**:设置规则的运行频率。 4. 点击 **Save** 以激活规则。 ### 手动创建 \{#manually\} 从头创建自定义关键词规则: 1. 在左侧边栏中,进入 **Automations**,点击 **Create rule**,然后选择 **Keywords** 作为规则类型。 2. 输入一个描述性的 **Rule name**。 3. 在 **Apply to** 部分,选择规则应适用的广告系列组、应用、广告系列或广告组。 4. 点击 **Add condition**,然后从列表中选择一个[数据图表](adapty-ads-manager-metrics)。 数据图表按您账户货币计算所选时间范围内的数据,接近实时更新,因此规则始终使用最新的效果数据。 5. 设置时间段(例如过去 3 天或过去 7 天),选择比较运算符,并输入阈值。 6. 如需添加更多条件,点击 **Add condition**,并在左侧选择 **And** 或 **Or** 运算符。 7. 在 **Action** 部分,选择条件满足时触发的操作: **调整出价** - **操作类型**:选择**增加**、**减少**或**设为**。 - **值类型**:在 **$**(绝对值)和 **%**(相对于规则执行时的当前出价)之间切换。 - **出价上限**(可选):最高出价上限,防止规则在强信号下反复触发时出现超额出价。 **启用关键词** - 无需额外配置。该规则会重新启用满足条件的已暂停关键词。 **暂停关键词** - 无需额外配置。该规则会暂停满足条件的活跃关键词。 **作为关键词添加到…** - **目标广告组**:选择接收复制的关键词的广告组。 - **CPT 出价**:为复制的关键词设置初始出价。 - **匹配类型**:选择**精确**或**广泛**。 - **如果关键词已存在则跳过**:启用后,跳过目标广告组中已存在的词条。 **作为否定关键词添加到…** - **范围**:选择要添加否定关键词的广告组或广告系列。 - **匹配类型**:选择**精确**或**广泛**。 8. 在 **Schedule** 部分: - 选择执行频率:**Every day**、**Every 2 days**、**Every week** 等。 - 选择运行时间(所有时间均为 UTC)。 规则将在 UTC 时间的计划时刻运行。执行通常在几分钟内完成,之后你可以在日志和主看板中查看变更情况。 9. 点击 **Save** 创建规则。 ## 最佳实践 \{#best-practices\} - **从小范围开始**:先将新规则应用于少数几个广告系列或广告组,验证行为后再扩大范围。 - **对活跃广告系列使用较短的回溯窗口**:对于节奏较快的广告系列,过去 3–7 天的数据通常比 30 天更有参考价值。 - **结合花费与转化数据**:避免使用单一指标规则。将花费与安装量、试用量或 ROAS 结合使用,以获得更可靠的信号。 - **在"调整出价"规则中设置出价上限**:出价上限可以防止在强信号多次触发规则时出价失控。 - **结合同期群数据使用"启用关键词"**:某个关键词因初期 CPA 表现不佳而被暂停,但随着同期群数据的成熟,可能会在 D31 或 D61 展现出较强的 ROAS。设置同期群 ROAS 条件,当其超过目标值时自动重新启用。 - **使用"添加为关键词"构建测试到规模化的流水线**:当测试广告系列中某个关键词达到 CPA 目标时,自动将其复制到规模化广告系列中。 - **使用"添加为否定关键词"保持探索广告系列的整洁**:当某个关键词被确认为精确匹配关键词后,在探索或搜索匹配广告系列中将其设为否定关键词,避免竞争同一查询。 - **新晋升的关键词生效前请等待一段时间**:如果你使用[搜索词自动化](ads-manager-automations-search-terms)将词语提升到关键词广告系列中,请给这些关键词一两天时间来积累数据。 --- # File: ads-manager-automations-search-terms --- --- title: "Adapty 广告管理器中的搜索词自动化" description: "自动将优质搜索词提升为关键词并在源头否定,无需人工干预即可扩大发现流量" --- Discovery 和 Search Match 广告活动会生成搜索词数据。要将这些数据转化为结构化的关键词列表,通常需要下载报告、筛选词条,再手动添加到广告组——这个过程相当繁琐。搜索词自动化规则可以帮你省去这些步骤:当某个词条满足你设定的条件时,规则会按照你配置的动作自动处理它。 搜索词规则有两种动作类型: - **Add as keyword**:将该词提升为目标广告组中的精确匹配关键词,并可选择在来源广告系列中将其设为否定关键词,以避免重复消耗预算。 - **Add as negative keyword**:直接将该词设为否定关键词,不进行提升。用于从 Discovery 和 Search Match 广告系列中过滤掉不相关或低效的搜索词。 **Add as keyword** 的典型使用场景:让 Discovery 或 Search Match 广告系列收集真实用户查询词,然后使用规则检测超过效果阈值的词语,将其以精确匹配关键词的形式推送到 Probing 广告系列中——同时在来源处将其设为否定关键词。Probing 广告系列是 Apple Search Ads 中专门用于在受控出价下测试推广关键词的广告系列。 **Add as negative keyword** 的典型使用场景:如果某个词语出现频率高但从未带来转化(例如,展示量高但点击量为零),则自动将其设为否定关键词,以避免预算浪费。 ## 创建搜索词自动化规则 \{#create-a-search-term-automation-rule\} 您可以从模板创建搜索词自动化规则,也可以从头手动创建。 :::note 在创建规则之前,请确保您已有正在运行并收集搜索词数据的探索活动或搜索匹配活动。仅限精确匹配的活动不会生成搜索词报告,因此规则将无数据可操作。 ::: ### 从模板创建 \{#from-a-template\} 要从模板创建规则: 1. 在左侧边栏,进入 **Automations** 并点击 **Templates**。 2. 选择一个模板并点击 **Next**。 3. 查看并调整预填设置: - **Rule name**:自动设置为模板名称和当前日期。 - **Apply to**:选择规则应搜索词的广告系列组、应用、广告系列或广告组。 - **Conditions**:如需要,可修改预配置的条件。 - **Actions**:如需要,可调整目标广告组、CPT 出价和否定关键词范围。 - **Schedule**:设置规则的运行频率。 4. 点击 **Save** 以激活规则。 ### 手动创建 \{#manually\} 从头开始创建自定义搜索词自动化规则: 1. 在左侧边栏中,进入 **Automations**,点击 **Create rule**,然后选择 **Search terms** 作为规则类型。 2. 输入一个描述性的 **Rule name**,以便识别该规则的用途。 3. 在 **Apply to** 部分,选择该规则应在哪些广告系列组、应用、广告系列或广告组中查找搜索词。 4. 点击 **Add condition**,然后从列表中选择一个[数据图表](adapty-ads-manager-metrics)。 数据图表按所选时间范围以账户货币计算。数据接近实时更新,因此规则始终使用最新的效果数据。 5. 设置时间段(例如,过去 3 天或过去 7 天),选择比较运算符,并输入阈值。 6. 要添加更多条件,点击 **Add condition**,并在左侧选择 **And** 或 **Or** 运算符。 7. 在 **Action** 部分,选择当搜索词满足条件时执行的操作: **添加为关键词** 将匹配的搜索词作为精确匹配关键词添加到目标广告组中。 - **Target ad groups(目标广告组)**:选择接收推广关键词的广告组。如需构建探索流程,请选择 Probing 或其他结构化广告系列中的广告组。 - **CPT bid(每次点击费用出价)**:为每个推广关键词设置初始每次点击费用出价。可选项:广告组默认出价、搜索词当前 CPT,或指定自定义值。 - **Skip if keyword already exists(如关键词已存在则跳过)**:启用后,将跳过目标广告组中已存在的搜索词。 - **Add as negative(添加为否定关键词)**:将相同搜索词作为否定关键词添加,避免为同一流量重复付费。 - **Scope(范围)**:选择添加否定关键词的广告组。 :::tip 启用同一规则中的 **Add as negative** 功能——一步完成将词语提升至结构化广告系列并在来源处否定。这样可以保持您的 Discovery 广告系列整洁,并自动构建关键词漏斗。 ::: **Add as negative keyword** 否定匹配的搜索词,但不将其提升。 - **Scope**:选择添加否定关键词的广告组或广告系列。 - **Match type**:选择 **Exact** 或 **Broad**。 使用此操作可从 Discovery 和 Max Conversion 广告系列中排除不相关或低质量的搜索词。例如:如果某个词的展示次数超过 50 次但点击次数为 0,则自动将其屏蔽。 8. 在 **Schedule** 部分: - 选择频率:**Every day**、**Every 2 days**、**Every week** 等。 - 选择运行时间(所有时间均为 UTC)。 规则会在 UTC 计划时间运行,通常在几分钟内完成,之后即可在 Logs 和主看板中查看变更。 9. 点击 **Save** 创建规则。 规则运行后,前往 **Automations** → **Logs**,打开对应规则的记录条目。成功运行时,列表会显示每个已评估的搜索词及其来源广告系列、目标广告组、操作结果和否定结果。如果未显示任何词,说明条件未满足——请检查阈值或延长回溯时间窗口。 ## 最佳实践 \{#best-practices\} - **以 Discovery 或 Search Match 广告系列作为来源**:这类广告系列能收集真实的用户搜索词,为规则提供充足的搜索词池供评估。 - **将阈值与回溯窗口相匹配**:7 天内 2 次或以上下载是一个合理的起点。窗口越长(14–30 天),实际门槛越低——搜索词只需偶尔转化即可通过筛选。对于高流量应用,应缩短窗口并提高阈值。 - **推广时务必在来源处添加否定词**:如果你在 Probing 广告系列中将某个词添加为关键词,但未在 Discovery 中将其否定,两个广告系列将同时竞争同一搜索词。请在同一规则中启用 **Add as negative**。 - **有针对性地选择目标广告组**:将推广的搜索词定向到特定的 Probing 广告组,而非宽泛的广告系列。这样既能保持关键词结构清晰,也便于分析效果。 - **每次运行后查看日志**:在 Logs 标签页中确认哪些词被推广、推广到了哪里。在初期,设置完成后手动运行规则,验证其行为是否符合预期。如需了解如何读取日志,请参阅[自动化](ads-manager-automations#explore-logs)。 - **给推广关键词留出时间,再让关键词规则介入**:如果你在同一广告系列中使用关键词规则,请排除新推广的关键词,或等待一两天后再让规则作用于它们。关键词规则可能在一个尚无效果数据的新词上触发,并在它产生转化之前就削减出价。 - **对展示量高但点击量为零的词使用"Add as negative keyword"**:Discovery 和 Max Conversion 广告系列经常出现不相关的搜索词。设置"展示量 > 50 且点击量 = 0"的规则,可自动将其否定,避免继续积累无效展示。 ## 导出搜索词 \{#export-search-terms\} 如需将搜索词表格导出为 CSV,点击表格上方的下载图标,选择 **Export current page** 或 **Export all pages**。 **Export all pages** 会将所有页面的搜索词合并为一个文件下载。下载过程中会显示进度弹窗,你可以随时取消。 导出的表格与看板中显示的内容一致,仅包含你选择显示的列。 --- # File: ads-manager-automations-ad-group-rules --- --- title: "Adapty 广告管理器中的广告组规则" description: "根据广告活动效果,自动调整广告组的出价和 CPA 目标,以及启用或暂停广告组。" --- 当您希望广告组的设置能够根据其效果自动变更时,请添加**广告组规则**。与针对单个关键词的关键词规则不同,广告组规则会一次性作用于整个广告组。 每条规则都将条件与操作配对。例如:如果某个广告组在三天内花费超过 50 美元却没有带来试用,则将其出价降低 20%。Adapty 按照您设定的频率检查广告组——每小时、每天、每周等——并在满足条件时执行操作。由于 Adapty 会追踪试用、订阅和营收,您的条件可以基于实际收入来响应,而不仅仅是安装量。 ## 可用条件 \{#available-conditions\} 规则会监控一组广告组,并在其效果指标超过您设定的阈值时触发。首先,选择规则要监控的广告组范围: - **Ad groups in selected campaign groups** - **Ad groups in selected apps** - **Ad groups in selected campaigns** - **Selected ad groups** 然后设置触发规则的条件。每个条件由以下几个部分组成: | 部分 | 描述 | 示例 | | --- | --- | --- | | **数据图表** | Adapty Ads Manager 追踪的任意[数据图表](adapty-ads-manager-metrics)——包括花费、安装量、试用次数、订阅数、收入等。 | 花费 | | **时间窗口** | 衡量该数据图表的时间段。 | 过去 3 天 | | **比较方式** | 数据图表与目标值的比较逻辑。 | 大于 | | **阈值** | 用于比较的目标值。 | $50 | 使用 **And** 或 **Or** 组合多个条件,实现更精准的定向——例如:花费 > $50 **且** 试用次数 = 0。 ## 可用操作及其设置 \{#available-actions-and-their-settings\} 当某个分组满足您的条件时,Adapty 可以触发以下操作之一来更改其设置: | 操作 | 功能说明 | 使用场景 | 配置项 | | --- | --- | --- | --- | | **Change default bid** | 提高、降低或设置广告组的默认最高**单次点击出价 (CPT)**——即每次广告点击的最高支付金额。 | 对转化好的广告组加大力度,对转化差的适当收缩。 | **Action type**:Increase by、Decrease by 或 Set to。<br/>**Value type**:$(绝对值)或 %(当前出价的百分比)。<br/>**Limit**(可选):增加时设置上限,减少时设置下限——限制多次执行时出价的变动幅度。 | | **Change CPA goal** | 提高、降低或设置广告组的 **CPA 目标(上限)**——即目标单次转化成本。 | 在优化过程中收紧成本上限,或适当放宽以争取更多量。 | **Action type**:Increase by、Decrease by 或 Set to。<br/>**Value type**:$ 或 %。<br/>**Limit**(可选):增加时设置上限,减少时设置下限。 | | **Enable ad group** | 重新启用已暂停的广告组。 | 在试用期收益和订阅收入回流后,恢复表现回升的广告组。 | 无。 | | **Pause ad group** | 暂停正在投放的广告组。 | 停止持续消耗预算却无转化的广告组。 | 无。 | ## 创建广告组规则 \{#create-an-ad-group-rule\} 1. 前往 **Automations**,点击 **Create rule**,然后选择 **For ad groups**。 2. 输入 **Rule name**。 3. 在 **Apply to** 下,选择一个[范围](#available-conditions)并勾选对应的广告组。 4. 在 **Conditions** 下,添加一个或多个[条件](#available-conditions)。可以使用逻辑运算符组合多个条件。 5. 在 **Action** 下,选择一个[操作](#available-actions-and-their-settings)并配置相关选项。 6. 在 **Schedule** 下,设置规则的执行频率和开始时间(UTC),或选择 **Run immediately**。 7. 点击 **Save**。 保存后,该规则会出现在 **Automations** 选项卡中。**Date last run** 和 **Date next run** 列会跟踪其执行计划。如需确认各次运行的状态变更,请打开 **Automations > Logs**。有关如何暂停、复制、删除规则或立即执行规则的说明,请参阅 [Automations](ads-manager-automations)。 --- # File: ads-manager-automations-campaign-rules --- --- title: "Adapty 广告管理器中的广告系列规则" description: "根据效果自动调整广告系列每日预算,并启用或暂停广告系列。" --- 当你希望广告系列的预算或状态能根据其效果自动变更时,可以添加**广告系列规则**。与广告组规则只作用于单个广告组不同,广告系列规则会一次性影响整个广告系列。 每条规则都将一个条件与一个动作配对。例如:如果某个广告系列在三天内花费超过 200 美元却没有带来任何订阅,则将其每日预算降低 20%。Adapty 按照您设定的频率(每小时、每天、每周等)检查您的广告系列,并在满足条件时自动执行相应动作。由于 Adapty 会追踪试用、订阅和收入数据,您的条件可以基于实际收入来设定,而不仅仅是安装量。 ## 可用条件 \{#available-conditions\} 一条规则会监控一组广告活动,并在其表现达到您设定的阈值时触发。首先,选择该规则所监控的广告活动: - **Campaigns in selected campaign groups** - **Campaigns in selected apps** - **Selected campaigns** 然后设置触发规则的条件。每个条件由以下部分组合而成: | 部分 | 描述 | 示例 | | --- | --- | --- | | **数据图表** | Adapty Ads Manager 追踪的任意[数据图表](adapty-ads-manager-metrics)——花费、安装量、试用、订阅、收入等。 | 花费 | | **时间窗口** | 该数据图表的统计周期。 | 昨天 | | **比较条件** | 数据图表与您设定值的比较方式。 | 大于 | | **阈值** | 用于比较的参考值——可以是固定金额,也可以是该广告系列的 **Daily Budget**。 | Daily Budget | 使用 **And** 或 **Or** 组合多个条件,实现精准定向——例如,支出 > 每日预算 **and** ROAS < 100%。 ## 可用操作及其设置 \{#available-actions-and-their-settings\} 当某个推广活动满足您设置的条件时,Adapty 可以触发以下操作之一来更改其设置: | 操作 | 功能说明 | 使用场景 | 配置项 | | --- | --- | --- | --- | | **Change daily budget** | 增加、减少或设置广告系列的**每日预算**——即每天的最高花费上限。 | 对超支的广告系列削减预算;对盈利增长的广告系列提高预算。 | **Action type**:Increase by、Decrease by 或 Set to。<br/>**Value type**:$(绝对值)或 %(当前预算的百分比)。<br/>**Limit**(可选):增加时设置上限,减少时设置下限——限制多次运行后预算的最大变动幅度。 | | **Enable campaign** | 重新启用已暂停的广告系列。 | 在试用期和订阅收入陆续到账后,恢复表现回升的广告系列。 | 无。 | | **Pause campaign** | 暂停正在投放的广告系列。 | 停止持续花费却没有转化的广告系列。 | 无。 | ## 创建活动规则 \{#create-a-campaign-rule\} 要从预设开始,请点击 **Automations** 标题栏中的 **Templates**,选择 **Decrease budget for low-performing campaigns**,然后检查并保存。如需从头构建规则: 1. 进入 **Automations**,点击 **Create rule**,选择 **For campaigns**。 2. 输入 **Rule name**。 3. 在 **Apply to** 下,选择一个[范围](#available-conditions)并选定相应的广告系列。 4. 在 **Conditions** 下,添加一个或多个[条件](#available-conditions),可使用逻辑运算符组合多个条件。 5. 在 **Action** 下,选择一个[操作](#available-actions-and-their-settings)并配置相关选项。 6. 在 **Schedule** 下,设置规则的执行频率和开始时间(UTC),或选择 **Run immediately**。 7. 点击 **Save**。 保存后,该规则将显示在 **Automations** 标签页中。**Date last run** 和 **Date next run** 列会追踪其执行计划。要确认各次运行中的状态变更,请打开 **Automations > Logs**。有关如何暂停、复制、删除规则或立即运行的说明,请参阅 [Automations](ads-manager-automations)。 --- # File: ads-manager-market-intelligence --- --- title: "Adapty 广告管理器中的市场情报" description: "查看竞争对手在 50 多个国家投放 Apple 广告时使用的关键词,并直接将其添加到您的广告活动中。" --- Market Intelligence 显示你的竞争对手在 Apple Ads 中竞标了哪些关键词,覆盖 50 多个国家。数据汇总自过去 30 天,每日更新。 使用场景: - **跳过探索阶段**:直接查看竞争对手已经在竞价的关键词,而不是花钱摸索哪些关键词有效。第一天就能用上经过验证的关键词列表。 - **挖掘低竞争关键词**:找出竞争对手"声量份额"较低的长尾词——竞争少、每次点击费用低,CPA 表现也更好。 - **保护品牌阵地**:查看是否有竞争对手在竞价你的应用名称,以及涉及哪些国家,然后夺回这部分流量。 - **用数据开拓新市场**:在投放任何预算之前,先查看竞争对手在某个国家运行了哪些关键词。 - **发现被忽视的竞争对手**:按关键词搜索,查看哪些应用在你的品类中自然排名靠前,并将其纳入分析范围。 ## 运行市场智能分析 \{#run-a-market-intelligence-analysis\} ### 1. 选择您的应用 \{#1-select-your-app\} 在左侧边栏中,进入 **Market Intelligence**。从下拉菜单中选择您要分析的应用,然后点击 **Continue**。 ### 2. 选择竞品 \{#2-select-competitors\} 添加你想分析的竞品: - **建议竞品**:Adapty 会根据你的应用类别自动检测可能的竞品。查看列表并选择你想纳入分析的竞品。 - **按关键词或应用名称搜索**:在搜索框中输入关键词(例如"budget tracker")或应用名称。如有需要,可切换国家,然后从结果中选择应用。尝试不同的关键词,发现来自不同搜索意图的竞争对手。 - **已保存的列表**:点击 **Create list** 可保存最多 20 个竞争对手,以便在后续分析中复用。你也可以加载之前创建的列表。 选好竞品后,点击 **Run analysis**。 ### 3. 查看结果 \{#explore-results\} 结果分为四个标签页:**Overview**、**Most Contested**、**By App** 和 **By Country**。 #### 概览 \{#overview\} 默认标签页展示分析摘要: - **统计栏**:分析的竞争对手总数、有 Apple Ads 投放活动的国家/地区数量、在所有市场中发现的唯一关键词数量,以及竞争最激烈的单个关键词。 - **各国家/地区关键词数量**:一张柱状图,展示前 25 个市场中每个国家/地区的关键词数量。 - **按关键词覆盖率排名的前 10 名竞争对手**:一张排名表,列出每位竞争对手的关键词总数、平均声量份额(Avg SOV)以及其活跃的国家/地区。 #### 竞争最激烈 \{#most-contested\} 显示同时有最多竞争对手活跃的关键词。使用此标签页可发现您所在类目中需求最高的词条,并了解竞争最集中的领域。使用搜索框可按关键词筛选列表。 #### 按应用 \{#by-app\} 逐个展示每位竞争对手的关键词数据。使用此标签页深入分析特定应用在各国家/地区的关键词策略。点击 **Add filter** 可按应用、国家/地区或关键词进行筛选。如需将数据导出为 CSV 文件,请点击下载图标。 #### 按国家/地区 \{#by-country\} 按市场分组显示关键词数据。当您希望在进入某个国家/地区或扩展业务前,重点了解该市场的竞争格局时,可使用此标签页。点击 **Add filter** 按国家/地区、应用或关键词进行筛选。如需将数据导出为 CSV,请点击下载图标。 ### 4. 将关键词添加到广告系列 \{#add-keywords-to-campaigns\} 找到值得测试的关键词后,无需离开该工具即可将其添加到广告系列: 1. 在关键词表格中,勾选目标关键词旁边的复选框。如需选中所有可见关键词,请使用表头中的复选框。 2. 点击 **Add to campaign**。 3. 选择将其添加为关键词、否定关键词还是 SKAG,然后选择目标广告系列和广告组,设置匹配类型和 CPT 出价后确认。 ## 关注哪些指标 \{#what-to-look-for\} 在结果中,以下这些规律值得重点关注: - **长尾词且曝光份额低**:竞品在这类关键词上的曝光份额较低,竞争相对较弱。它们的单次点击费用通常更低,因为用户意图更明确,转化率也更高。 - **尚未覆盖的关键词**:寻找竞品在投放但你还未尝试的词。这些词已被证明能在你所在品类中带来 Apple Ads 流量。 - **品牌词防护**:搜索你自己的 App 名称,若竞品出现,说明他们正在抢占你的品牌词。将这些关键词加入你的广告系列并设置较高出价,以保护自有流量。 - **国家/地区覆盖空白**:查看竞品活跃的国家/地区。竞品活动较少甚至缺席的市场,进入难度更低,获得流量所需的预算也更少。 --- # File: ads-manager-cpp-ab-tests --- --- title: "Adapty 广告管理工具中的 CPP A/B 测试" description: "在 Apple Ads 中比较自定义产品页面,找出效果最佳的页面。" --- CPP A/B 测试让你在 Apple Ads 中对多个自定义产品页面(CPP)进行相互比较。你可以选择 2 到 4 个产品页面,[Adapty 广告管理工具](adapty-ads-manager)会在它们之间分配流量、收集表现数据,并告诉你哪个页面的转化效果最好。 您可以将**默认产品页面**作为其中一个实验变体,从而测试自定义页面是否比当前默认页面表现更好。 ## 前提条件 \{#prerequisites\} 在创建 CPP A/B 测试之前,请确认以下几点: - **已连接 Apple Ads Manager**:如尚未完成,请按照[设置指南](adapty-ads-manager-get-started)操作。 - **来源广告组有流量**:待测广告组必须至少运行 28 天,且在此期间有展示量、点击量和安装量。Apple Ads Manager 会根据这些历史数据估算测试时长和所需样本量。 - **至少有一个自定义产品页面**:请先在 App Store Connect 中创建 CPP。Apple Ads Manager 会自动读取这些页面。 ## 创建 CPP A/B 测试 \{#create-a-cpp-ab-test\} 要创建测试,在左侧边栏中进入 **CPP A/B Tests**,然后点击 **Create A/B Tests**。 向导共分四个步骤:**Ad Group(s)**、**Ad Creative(s)**、**Testing Method** 和 **Review**。 ### 1. 广告组 \{#1-ad-groups\} 输入**测试名称**,然后点击 **Select Ad Group** 选择要测试 CPP 的广告组。你最多可以从同一广告系列中选择四个广告组,但前提是计划在这些广告组中测试同一个广告素材。如需比较多个 CPP,请选择一个广告组。 ### 2. 广告素材 \{#2-ad-creatives\} 选择要对比的 CPP。你可以加入**默认产品页面**(标记为 **Control**)和最多三个**自定义产品页面**,共 2 到 4 个实验变体。 - **默认产品页面**:点击 **+ Add Default**,将现有的默认产品页面作为对照组变体。 - **自定义产品页面**:点击 **+ Select CPP**,从 App Store Connect 中选择自定义产品页面。 ### 3. 测试方法 \{#3-testing-method\} 配置测试的运行方式。Adapty 广告管理工具会自动计算**预计测试时长**、**开始时间**和**结束时间**——每当你修改以下三项设置之一时,相关数值都会自动更新。 #### 切换时间预设 \{#switch-time-preset\} 系统在各实验变体之间轮换的频率。如果所选频率对当前流量而言过高,系统会自动降档处理。 | 间隔 | 典型流量级别 | 时段时长 | 典型测试时长 | |--------------|----------------------------------------|---------------|---------------------| | **每小时** | 高(每天 5,000+ 次展示) | 7 小时 | 数天 | | **每天** | 正常 | 24 小时 | 数周 | | **每周** | 低(每天不足 400 次展示) | 7 天 | 数月 | **时段**是系统在考虑切换到下一个实验变体之前,当前实验变体运行的基本时间单位。 #### 期望精度 \{#desired-precision\} 测试能够可靠检测到的最小转化率差异。可选值:**1%**、**2%**、**3%**、**4%**、**5%**,默认值为 **5%**。精度设为 1% 时,可以发现微小的差异,但需要更多数据,运行时间也更长;精度设为 5% 时,测试完成得更快,但只能捕捉到较大的差异。 | 精度 | 使用时机 | |-----------|-----------------------------------------------------------------------------------| | 1–2% | 你预期各 CPP 之间差异较小,且广告组流量较高。 | | 3–4% | 适合大多数测试的均衡默认值。 | | 5% | 你预期会有明显的优胜者,并希望快速得出结果。 | #### 置信水平 \{#confidence-level\} 您希望结果是真实的而非随机噪声的置信程度。选项:**80%**、**85%**、**90%**、**95%**、**99%**。默认值:**90%**。置信水平越高,所需数据越多。 | 置信度 | 权衡取舍 | |------------|---------------------------------------------------------------------------------| | 80–85% | 完成更快,但结果为噪声的可能性更高。 | | 90% | 大多数测试的推荐默认值。 | | 95–99% | 最为保守。需要最多的数据和最长的测试时间。 | ### 4. 检查 确认摘要内容——已选择的广告组、素材、测试方式、时长、精度和置信水平——然后点击 **Start CPP A/B Tests**。 启动测试后,系统会为每个实验变体克隆广告组,激活第一个实验变体,测试状态将在几分钟内变为 **Running**。 ## 监控运行中的测试 \{#monitor-a-running-test\} 要查看测试列表,请点击左侧边栏中的 **CPP A/B Tests**。页面顶部的四个选项卡可按状态筛选测试: - **Live**:当前正在运行的测试。 - **Completed**:已完成的测试。 - **Draft**:尚未启动的测试。 - **Archive**:不再需要在主视图中显示的历史测试。 每张测试卡片会显示其名称、状态、切换间隔、目标精度以及运行时长。点击 **View metrics** 可展开实验变体表格。 ### 实验变体性能 \{#variant-performance\} 实验变体表格对比了测试中所有实验变体的表现: | 列名 | 说明 | |--------------------------|----------------------------------------------------------------------------------------------| | **Variant Name** | 正在测试的 CPP。实验变体 A 始终是你添加的第一个实验变体。 | | **Confidence Level** | 该实验变体距离所需样本量的完成百分比,范围为 0 到 100。 | | **Impressions** | Apple 展示该实验变体广告的次数。 | | **TTR** | 点击率:点击次数除以展示次数。 | | **Tap → Download CR** | 从点击到下载的转化率。 | | **CPT** | 平均每次点击费用。 | | **Avg CPA (Tap-Through)**| 基于点击下载计算的平均每次获客成本。 | | **Spend** | 归因于该实验变体的总花费。 | | **Revenue** | 归因于该实验变体的总收入。 | | **ROAS** | 广告支出回报率:收入除以花费。 | 在每个实验变体获得相近的展示量之前,Adapty Ads Manager 不会标注获胜者。数据仍在收集过程中时,表格上方会显示一条横幅:**Winner highlighting is paused — variants don't have comparable impressions yet.** ### 详细数据图表 \{#detailed-metrics\} 如需深入分析测试,请点击 **View metrics** 打开详细数据图表页面。该页面包含同期群留存曲线、ARPPU 对比,以及按以下两部分分组的数据图表表格: - **Top of funnel · Apple Search Ads**:各实验变体的 TTR、Download Rate、CPM、CPT 和 Avg CPA。 - **Bottom of funnel · Monetization**:各实验变体的付费用户数、Paid CR、Cost per Paid、ARPPU、Revenue 和 ROAS。 **Winner**(获胜方)列显示每个数据图表上领先的实验变体。只有当某个实验变体在主要指标上领先且置信度达到至少 95% 时,才会被标记为整体获胜方。 有关指标定义,请参阅 [Adapty Ads Manager 中的指标](adapty-ads-manager-metrics)。 ## 停止测试 \{#stop-a-test\} 你可以随时停止测试。测试状态将标记为 **Stopped**,原始广告组将被恢复,克隆的广告组将被暂停。 停止正在运行的测试: 1. 点击左侧边栏中的 **CPP A/B Tests**。 2. 在测试卡片上点击 **Stop A/B test**,或打开测试后点击 **Stop Test**。 3. 在 **Stop A/B Test?** 对话框中确认操作。 :::important 停止测试是不可逆的——无法恢复。已收集的结果仍可在 **Completed** 标签页中查看。 ::: ## 测试状态 \{#test-statuses\} 每个测试都会经历一组固定的状态: | 状态 | 含义 | |---------------|------------------------------------------------------------------------------------------| | **Draft** | 测试已创建但尚未启动,仍可编辑。 | | **Starting** | 正在配置——系统正在克隆广告组并创建广告。 | | **Running** | 测试已上线,实验变体轮流展示,数据正在收集中。 | | **Completed** | 预定时长已到期,或已达到置信度要求。原始广告组已恢复。 | | **Stopped** | 您手动停止了测试,原始广告组已恢复。 | | **Failed** | 配置失败或连续出错次数过多,可重新启动失败的测试。 | ## 工作原理 \{#how-it-works\} Adapty Ads Manager 采用 **Ad Group Switch** 方法: 1. 测试开始时,系统会为每个实验变体克隆一次源广告组。每个克隆指向不同的 CPP(其中一个可以是您的默认页面)。 2. 每次只有一个克隆处于运行状态。系统按照固定的周期(每小时、每天或每周)轮换活跃的克隆。 3. 测试运行期间,原始广告组会被暂停;测试结束后,系统会将其恢复到之前的状态。 4. Adapty Ads Manager 会收集每个实验变体的展示次数、点击次数和下载次数,并跟踪每个实验变体距离具有统计意义的样本量还有多远。 5. 一旦每个实验变体都积累了足够的数据,或者达到预设的测试时长,测试将自动结束。 ## 测试运行期间的注意事项 \{#what-to-expect-while-a-test-runs\} 以下是关于运行中的测试在看板上的行为,有几点值得了解: - **实验变体不会按固定时钟切换**:切换间隔只是一个基准值,Adapty Ads Manager 会动态调整时间,确保每个实验变体都能获得公平的曝光份额。如果某个实验变体的曝光量不足,它可能会比一个时间槽更长时间保持活跃状态。 - **结束时间可能会顺延**:如果在预定结束时间临近时某个实验变体的数据量不足,测试会自动延长以继续收集点击数据。新的结束时间会显示在测试卡片上。 - **测试结束后,原始广告组将被还原**:所有克隆的广告组都会暂停,源广告组恢复到测试前的状态。结果仍可在 **Completed** 标签页中查看。 --- # File: ads-manager-settings --- --- title: "Adapty Ads Manager 中的设置" description: "在 Adapty Ads Manager 中配置设置。" --- 在 Adapty Ads Manager 看板左下角点击 **Settings**,即可配置账户设置。 ## 推广组 \{#campaign-groups\} 在 **Campaign groups** 选项卡中,你可以查看所有已连接到 Adapty Ads Manager 的 Apple Ads 账号,也可以添加新账号。如果连接了多个 Apple Ads 账号,它们的所有数据分析将汇总到同一个 Adapty Ads Manager 看板中。 要添加新的 Apple Ads 账号,请点击 **Connect Apple Ads account**,然后按照[指南](adapty-ads-manager-get-started)操作。 ## 管理订阅 \{#manage-subscription\} 在 **Manage subscription** 标签页中,您可以查看当前的订阅计划并更新付款方式。 ## 用户设置 \{#user-settings\} 在 **User settings** 标签页中,您可以开启 **Hide Paused by Default** 开关。启用后,已暂停的广告系列、广告组和关键词将被隐藏,帮助您专注于活跃的效果数据。 如果您经常尝试启动和暂停不同的广告系列、广告组和关键词,请避免启用此选项,因为您可能随时需要访问已暂停的项目。 --- # File: adapty-user-acquisition --- --- title: "Adapty 归因" description: "无需 MMP,在同一平台完整核算整个应用经济。" --- <CustomDocCardList ids={['user-acquisition', 'ua-analytics', 'ua-integrations', 'ua-tracking-links', 'ua-deferred-data']} /> Adapty Attribution 是一套归因解决方案,通过整合广告平台、追踪链接和应用数据,将广告活动与应用安装及订阅收入关联起来。它提供统一的营销分析看板,将所有用户获取数据集中在一处。 - 计算所有渠道的 ROAS(广告支出回报率) - 在一处查看完整的应用营收全貌 - 获取准确的归因数据,辅助决策 - 分析同期群表现和用户随时间的行为变化 :::tip 想了解 Adapty 归因功能如何为您带来价值?欢迎[预约通话](https://calendly.com/tnurutdinov-adapty/30min)与我们交流。 ::: ## 为什么选择 Adapty 归因? \{#why-choose-adapty-attribution\} 衡量用户获取效果并非易事。数据往往分散在各个平台,隐私政策的变化让归因愈发困难,而自建解决方案又需要耗费大量时间。 Adapty Attribution 提供内置归因功能和统一分析,集成于单一营销看板中。所有获客数据——从广告支出到安装量再到订阅收入——均自动汇总并实时更新。无需再手动核对电子表格数据,也无需在多个工具之间来回切换。您可以专注于应用增长,而非数据系统的维护。 ## 工作原理 \{#how-it-works\} Adapty Attribution 通过整合广告平台、追踪链接和应用数据,将应用安装和订阅收入归因到对应的广告活动。 整体流程如下: - 广告平台提供广告活动结构和广告支出数据 - 在 Adapty Attribution 中生成的追踪链接,将广告活动上下文从网页端传递到应用安装环节 - Adapty SDK 从应用内发送安装事件和收入事件 归因流程如下: 1. **在 Adapty Attribution 中生成追踪链接并添加到广告活动中。** 该链接包含平台、广告活动、广告组和广告素材等活动参数。 2. **用户点击广告并从应用商店安装应用。** 用户通过追踪链接跳转,从 App Store 或 Google Play 安装应用。 3. **应用向 Adapty 发送安装事件。** 首次启动时,Adapty SDK 会发送安装事件。Adapty 提取与此次安装关联的活动参数。 4. **安装被归因至某个广告系列。** Adapty 使用跟踪链接中的系列参数,将该安装与生成它的广告系列关联起来。 5. **广告支出与收入相连接。** Adapty 从受支持的广告平台(目前包括 Meta Ads 和 TikTok for Business)拉取广告支出数据,并将订阅和购买事件关联至已归因的安装记录。 最终,Adapty 在统一分析看板中提供系列级别的数据指标,包括安装量、收入、LTV 和 ROAS。您可以分析同期群、追踪长期表现,并在无需手动整合不同来源数据的情况下,做出基于数据的优化决策。 :::tip 跟踪链接还可以包含自定义参数,让您的应用在处理安装事件时能够实现[延迟深度链接](ua-deferred-data)并响应系列数据。 ::: --- # File: user-acquisition --- --- title: "开始使用 Adapty Attribution" description: "接入 Adapty Attribution,将广告支出与订阅收入整合在一起,在一个地方全面了解应用经济。" --- Adapty Attribution 可帮助您将广告支出与订阅收入关联起来,适用于 web 转 app 的推广活动,让您在同一个地方全面了解应用的整体收益情况。 要在 Adapty Attribution 中查看收入数据,您需要先在 Adapty 看板中启用该集成。无需传入任何 API 密钥、令牌或标识符,只需更新并配置 Adapty SDK 即可。 :::important Adapty 归因功能适用于: - iOS、Android 和 Flutter SDK 3.9.1 或更高版本。 - React Native 和 Capacitor SDK 3.10.0 或更高版本。 - Unity SDK 3.12.0 或更高版本。 - Kotlin Multiplatform SDK 3.15.0 或更高版本。 ::: ## 开始之前 \{#before-you-start\} 要将您的收入数据与广告系列效果关联起来,请让 Adapty 追踪您的购买记录: - 如果您**已经通过 Adapty 实现了应用内购买**,此阶段无需进行任何其他操作。 - 如果您**尚未实现应用内购买且希望使用 Adapty**,请按照[快速入门指南](quickstart)完成相关步骤,将购买处理委托给 Adapty。 - 如果您**已经在不使用 Adapty 的情况下实现了应用内购买**,且不打算迁移至 Adapty,请[以观察者模式为您的平台安装 Adapty SDK](implement-observer-mode)。在此阶段,您只需将 SDK 添加到项目中,以启用观察者模式的方式激活它,并上报交易记录: 此设置可实现网页到应用的归因: - 用户安装应用后,Adapty SDK 会从链接参数中获取安装详情,从而让 Adapty 归因功能获取推广活动信息。 - Adapty SDK 能够感知应用内所有与收入相关的事件,并将其归因到网页推广活动。 ## 第一步:打开 Adapty Attribution \{#step-1-open-adapty-attribution\} :::important 如果点击 Adapty 标志后没有出现 **Attribution**,请在浏览器设置中清除 adapty.io 的 Cookie 和网站数据,然后重新加载页面。 ::: 点击页眉中的 Adapty 标志,选择 **Attribution**。 订阅事件会自动流入 Adapty Attribution。完成第二步并连接数据源后,推广活动数据才会显示。 要暂停事件推送,请在 Adapty 看板中打开 **Integrations > Adapty**,然后关闭该开关。 ### 支持的事件 \{#supported-events\} 默认情况下,Adapty 会向用户获取发送三组事件: - 试用 - 订阅 - 问题 您可以在[此处](events)查看支持的事件完整列表。 <img src="/assets/shared/img/events-ua.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 2 步:连接广告平台并添加跟踪链接 \{#step-2-connect-your-ad-platform-and-add-tracking-links\} Adapty 通过跟踪链接将应用安装与广告系列数据关联起来。 你必须将跟踪链接作为目标 URL,用于所有希望在 Adapty Attribution 中衡量效果的广告系列。 如果你在多个平台上投放广告,请分别为每个平台设置跟踪链接。 Adapty 与广告平台的对接方式有以下两种: - **原生集成(Meta Ads、TikTok Ads)。** Adapty 直接与广告平台对接。追踪链接自动生成,广告系列参数根据链接的使用位置动态填充。同一条链接可跨不同广告系列、广告组或创意素材使用,Adapty 会自动接收正确的广告系列数据和广告花费。 - **仅限追踪链接(所有其他广告平台)。** Adapty 不连接广告平台。追踪链接需手动创建,所有推广活动参数必须在创建链接时明确定义。这些平台不支持广告花费数据。 <Tabs> <TabItem value="meta" label="Meta Ads" default> 要为 Meta Ads 创建追踪链接: 1. 前往 Adapty Attribution 看板中的 [Integrations > Meta](https://app.adapty.io/ua/integrations/facebook/accounts),然后点击 **Continue with Facebook**。 2. 使用你的 Facebook 账号登录,然后点击 **Continue**。 3. 查看所请求的权限,然后点击 **Save**。 4. 切换到 **Web campaigns** 标签页,点击 **Create campaign**。选择应用并点击 **Save**。 5. 在 **General** 标签页中,展开 **iOS** 和/或 **Android** 部分,粘贴 App Store 和/或 Google Play 应用链接,然后点击 **Save**。 6. 复制**一个链接**或特定平台链接的 **Click link** 字段值。然后在 Meta Ads Manager 中,打开你的广告,将此链接粘贴为目标 URL。 :::important 在 **Website URL** 字段中,粘贴 `https://api-ua.adapty.io/api/v1/attribution/click`。将链接的其余部分粘贴到 **Tracking** 部分的 **URL parameters** 字段中。这样有助于你的 Meta 广告通过审核。查看更多[在 Meta Ads Manager 中设置广告的建议](meta-create-campaign)。 ::: 7. 现在,当您在 Meta Ads 中投放广告时,其数据将在 Adapty 归因看板中可用于分析。 </TabItem> <TabItem value="tiktok" label="TikTok for Business"> 要为 TikTok for Business 创建追踪链接: 1. 在 Adapty 归因看板中前往 [Integrations > TikTok Ads](https://app.adapty.io/ua/integrations/tiktok/accounts),然后点击 **Continue with TikTok**。 2. 使用您的 TikTok 账号登录,然后点击 **Continue**。 3. 查看所请求的权限,然后点击 **Save**。 4. 切换到 **Web campaigns** 标签,点击 **Create campaign**。选择应用并点击 **Save**。 5. 在 **General** 标签中,展开 **iOS** 和/或 **Android** 部分,粘贴 App Store 和/或 Google Play 应用 URL。然后点击 **Save**。 6. 复制 **Click link** 字段中**某一链接**或特定平台链接的值。然后在 TikTok Ads Manager 中创建广告时,将该值粘贴到 **Advanced Settings** 部分的 **Tracking URL** 字段中。这样 Adapty 就能将安装和购买行为与 TikTok 广告关联起来。请参阅[在 TikTok Ads 中设置推广活动的指南](tiktok-create-campaign)。 7. 在 TikTok for Business 上投放广告后,其数据将可在 Adapty 归因看板中进行分析。 </TabItem> <TabItem value="others" label="其他广告平台"> 为其他广告平台创建追踪链接: 1. 在 Adapty Attribution 看板中,从侧边菜单进入 **Tracking links**。点击 **Create link**。 2. 从列表中选择您的应用,然后点击 **Next**。 3. 填写链接参数,将其与您要追踪的广告活动和广告进行关联。 4. 默认情况下,您创建的是 One Link。它会自动检测用户所使用的平台,并在记录点击后将其重定向到 App Store 或 Google Play。 如果您希望为每个平台分别设置重定向 URL,请取消勾选 **One Link** 复选框,并手动填写各平台对应的应用商店链接。 5. 点击 **Create**。 6. 打开您的追踪链接页面,从以下某个板块复制 **Click link**: - **One link** – 使用此链接追踪点击,并自动将用户重定向到正确的应用商店。 - **iOS link** 或 **Android link** — 如需为每个应用商店分别设置链接,可使用这些特定平台版本。 7. 前往您的广告平台,将该链接作为广告目标 URL 粘贴到您的广告中。 </TabItem> </Tabs> ## 第三步:启动网页到应用活动并查看结果 \{#step-3-launch-your-web-to-app-campaign-and-view-results\} 活动上线且用户开始安装您的应用后,Adapty 将开始将安装和收入归因到您的活动。 在 [Adapty UA 分析看板](ua-analytics)中,您将看到活动级别的数据图表,例如: - 安装量和转化率 - 订阅和购买收入 - 按广告平台、活动、广告组和创意细分的效果 数据图表在从您的应用接收到安装和收入事件后即会显示。广告支出数据仅适用于具有原生集成的平台。 ## 了解更多 \{#learn-more\} 深入阅读 Adapty 归因分析文档,以及在主流广告平台上投放广告活动的实用指南: - [**Adapty 归因分析**](ua-analytics):了解如何有效使用归因数据看板。 - [**Adapty 归因数据图表**](ua-metrics):探索用户获取分析中可用的数据图表。 - [**集成**](ua-integrations):查看 Adapty 归因支持的广告平台和集成方式。 - [**在 Meta Ads Manager 中投放广告**](meta-create-campaign):了解如何在 Meta Ads Manager 中设置和启动广告系列。 - [**在 TikTok for Business 中投放广告**](tiktok-create-campaign):了解如何在 TikTok for Business 中设置和启动广告系列。 --- # File: ua-metrics --- --- title: "Adapty 归因中的数据图表" description: "了解 Adapty 归因中可用的数据图表。" --- Adapty 归因提供全面的**数据图表**,用于衡量广告系列效果和用户行为。这些数据图表以标准值的形式呈现,部分数据图表还提供**同期群数据图表**,用于对用户群进行基于时间维度的分析。 ## 标准数据图表 \{#standard-metrics\} | **数据图表** | 描述 | 同期群 | |-----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------| | **Spend** | 用户点击广告所产生的费用总和。 | 否 | | **Impressions** | 所选时间段内广告的展示次数。 | 否 | | **Clicks** | 统计周期内用户点击广告的次数。 | 否 | | **CPI** | **CPI(每次安装费用)** 是每次应用安装所需支付的金额。<br/>**公式**:`Spend / Installs` | 否 | | **CPC** | **CPC(每次点击费用)** 是每次广告点击所需支付的金额。<br/>**公式**:`Spend / Clicks` | 否 | | **CPM** | **CPM(千次展示费用)** 是每千次广告展示所需支付的金额。<br/>**公式**:`Spend / (Impressions / 1000)` | 否 | | **ICR** | **ICR(安装转化率)** 是广告点击中最终完成安装的百分比。<br/>**公式**:`(Installs / Clicks) × 100%` | 否 | | **IPM** | **IPM(千次展示安装数)** 表示每千次广告展示所带来的安装数量。<br/>**公式**:`(Installs / Impressions) × 1000` | 否 | | **CTR** | **CTR(点击率)** 是广告展示中产生点击的百分比。<br/>**公式**:`(Clicks / Impressions) × 100%` | 否 | | **Inline link clicks** | 用户点击广告素材或应用页面中内联链接的次数。 | 否 | | **Cost per inline link click** | 每次内联链接点击的平均费用。<br/>**公式**:`Spend / Inline Link Clicks` | 否 | | **Inline link click CTR** | 广告展示中产生内联链接点击的百分比。<br/>**公式**:`(Inline Link Clicks / Impressions) × 100%` | 否 | | **Installs** | 统计周期内安装应用的用户总数(含重新安装)。 | 否 | | **Revenue** | 所选时间段内与该广告系列关联的购买所产生的总收入(扣除应用商店佣金前)。 | 是 | | **ROAS** | **ROAS(广告支出回报率)** 是广告收入与广告支出的比值,以百分比表示。<br/>**公式**:`(Revenue / Spend) × 100%(Spend > 0 时),否则为 0%` | 是 | | **ARPU** | **ARPU(每用户平均收入)** 是同期群中每位用户的平均收入。<br/>**公式**:`Revenue / Users` | 是 | | **LTV** | **LTV(用户生命周期价值)** 是归因于单个用户在其整个生命周期内的平均收入。<br/>**公式**:`Revenue / Installs` | 否 | | **Cost per trial** | 每次试用开始所需支付的平均费用。<br/>**公式**:`Spend / Count trial started` | 否 | | **Cost per subscription** | 每次订阅产品购买所需支付的平均费用。<br/>**公式**:`Spend / Count subscription started` | 否 | | **Count subscription events** | 统计周期内订阅相关事件的数量指标组,包括:<br/>- Count subscription started<br/>- Count subscription renewed<br/>- Count subscription renewal cancelled<br/>- Count subscription renewal reactivated<br/>- Count subscription expired<br/>- Count [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Count subscription refunded | 是 | | **Count trial events** | 统计周期内试用相关事件的数量指标组,包括:<br/>- Count trial started<br/>- Count trial converted<br/>- Count trial expired<br/>- Count trial renewal reactivated | 是 | | **Count billing issue detected** | 统计周期内检测到的账单问题数量。 | 是 | | **Count entered grace period** | 因账单问题而进入宽限期的订阅数量。 | 是 | | **Count non-subscription events** | 统计周期内非订阅相关事件的数量指标组,包括:<br/>- Count non-subscription purchased<br/>- Count non-subscription refunded | 是 | | **Subscription events rate** | 统计周期内订阅相关事件相对于应用安装量的比率指标,包括:<br/>- Rate subscription started<br/>- Rate subscription renewed<br/>- Rate subscription renewal cancelled<br/>- Rate subscription renewal reactivated<br/>- Rate subscription expired<br/>- Rate [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Rate subscription refunded | 是 | | **Trial events rate** | 统计周期内试用相关事件相对于应用安装量的比率指标,包括:<br/>- Rate trial started<br/>- Rate trial converted<br/>- Rate trial expired<br/>- Rate trial renewal reactivated | 是 | | **Rate billing issue detected** | 统计周期内账单问题相对于应用安装量的比率。 | 是 | | **Rate entered grace period** | 统计周期内进入宽限期的订阅相对于应用安装量的比率。 | 是 | | **Non-subscription events rate** | 统计周期内非订阅相关事件相对于应用安装量的比率指标,包括:<br/>- Rate non-subscription purchased<br/>- Rate non-subscription refunded | 是 | ## 趋势预测指标 \{#predicted-metrics\} 趋势预测指标基于应用自身的历史数据,对同期群的未来表现进行预测。这些指标支持多个同期群周期,包括 D30、D60、D90、D180 和 D360,以及你可以自定义添加的天数周期。关于具体计算方式,请参阅[Adapty 归因中的趋势预测指标](ua-predicted-metrics)。 | **数据图表** | 描述 | 同期群 | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------| | **pRevenue** | 预测同期群在目标预测期内产生的总收入,基于该应用历史同期群的留存数据建模得出。 | 是 | | **pROAS** | 预测在相同预测期内的广告支出回报率。**计算公式**:`(pRevenue / Spend) × 100%` | 是 | | **pAdProfit** | 预测在该预测期内扣除广告支出后的净收入。**计算公式**:`pRevenue − Spend` | 是 | | **pARPU** | 预测在该预测期内每次安装的平均收入(即预测 LTV)。**计算公式**:`pRevenue / Installs` | 是 | | **pARPPU** | 预测在该预测期内每位付费用户的平均收入。**计算公式**:`pRevenue / paying users at d{N}`,其中 `d{N}` 与所选预测期对应。 | 是 | --- # File: ua-predicted-metrics --- --- title: "Adapty 归因中的趋势预测指标" description: "在 Adapty 归因中预测同期群的收入、ROAS、广告利润和 LTV。" --- :::important 本文介绍 Adapty 归因中的趋势预测功能。如需了解同期群分析页面上的预测 LTV 和营收,请参阅[同期群中的趋势预测](predicted-ltv-and-revenue)。 ::: Adapty 归因可对每个同期群的未来营收和单元经济进行预测,让你在广告活动尚未成熟之前就能对其进行比较。预测基于应用自身的历史同期群数据生成,每日更新。对于尚未完成完整订阅周期的近期同期群,预测尤为实用。 ## 趋势预测数据图表 \{#predicted-metrics\} | **数据图表** | 描述 | |---|---| | **pRevenue** | 预测同期群在目标时间范围内的总收入,基于应用的历史同期群留存数据建模。 | | **pROAS** | 预测在相同时间范围内的广告支出回报率。**公式**:`(pRevenue / Spend) × 100%` | | **pAdProfit** | 预测在该时间范围内扣除广告支出后的净收入。**公式**:`pRevenue − Spend` | | **pARPU** | 预测在该时间范围内每次安装的平均收入(即预测 LTV)。**公式**:`pRevenue / Installs` | | **pARPPU** | 预测在该时间范围内每位付费用户的平均收入。**公式**:`pRevenue / paying users at d{N}`,其中 `d{N}` 与所选时间范围匹配。 | `pRevenue` 是基础值。其他四项数据图表均基于它,结合实际同期群数据(花费、安装量和付费用户数)推导得出,而非单独运行模型计算。 每项趋势预测指标均支持多个同期群周期:D0、D3、D7、D30、D60、D90、D180 和 D360。你也可以自定义天数周期。周期定义了从同期群安装日期起,预测值向未来延伸的时间范围。 ## 趋势预测的计算方式 \{#how-predictions-are-calculated\} 趋势预测基于每款应用自身的历史同期群数据构建。模型会衡量过去各同期群在基准日之后的收入增长情况,然后以相同的增长轨迹对当前同期群进行预测。 ### 基准日 \{#baseline-day\} 趋势预测仅在同期群到达基准日后才会生效。基准日是指同期群初始收入中通常已收到 90% 的第一天。初始收入包含订阅开始、试用转化以及一次性购买,续订不计入此阈值。 基准日取决于应用的试用期长度和产品组合: - **无试用期的应用**:基准日通常在安装后的最初几天内。 - **短试用期的应用**:基准日通常在试用转化后不久。 - **长试用期的应用**:由于大部分初始收入要等试用结束后才会产生,基准日可能在安装后一周甚至更晚。 ### 按订阅类型划分的趋势预测 \{#projection-by-subscription-type\} 在基准日,同期群的初始收入被拆分为五个类别——月度订阅、年度订阅、周度订阅、季度订阅,以及一次性购买。每个类别都根据应用历史同期群所测量的轨迹独立向前预测。 该模型会对近期同期群以及具有相似经济特征(例如相近的每笔交易收入和相似的产品组合)的同期群赋予更高权重。因此,趋势预测反映的是该应用中最近且最相似的同期群的实际表现。 ## 趋势预测的显示条件 \{#when-predictions-are-available\} 只有当同期群拥有足够的数据时,才会显示趋势预测。如果无法生成预测值,相应列会显示破折号(`—`)。 趋势预测不可用的常见原因: - **同期群尚未到达基准日**:模型需要等待同期群的初始收入趋于稳定,才能进行后续预测。 - **应用历史数据不足**:如果该应用没有足够多的相关订阅类型历史同期群数据,模型就无法拟合出可靠的留存率。 趋势预测每天会根据最新的交易数据重新计算,因此同一同期群的数值可能会随着更多收入数据的积累而发生变化。 --- # File: ua-tracking-links --- --- title: "Adapty 归因中的追踪链接" description: "追踪您的营销活动并在任何地方衡量其效果。" --- 追踪链接让您能够衡量用户的来源,并将安装事件与广告活动关联起来。 当用户点击您的广告时,Adapty 会记录该点击,并在随后将其与 SDK 发送的安装事件进行匹配。这样,您就可以在[数据分析页面](ua-analytics)上看到哪些渠道、广告活动、广告组和广告带来了最多的收入。 您可以创建两种类型的追踪链接: - **One Link** — 一种通用链接,可自动检测用户的平台,记录点击,并将用户重定向到 App Store 或 Google Play。 - **特定应用商店链接** — 面向特定平台的链接,既可记录点击,又可自动将用户重定向到 App Store 或 Google Play。您还可以为其附加延迟深度链接参数。 ## 创建追踪链接 \{#create-tracking-links\} 创建追踪链接的步骤如下: 1. 在 Adapty Attribution 看板中,从侧边栏菜单进入 **Tracking links**,然后点击 **Create link**。 2. 从列表中选择您的应用,然后点击 **Next**。 3. 填写链接参数,将其与您要追踪的广告活动和广告进行匹配。 | 参数 | 说明 | |-------------------|-----------------------------------------------------------------------------------------------| | **Name** | 追踪链接的内部名称。 | | **Channel** | 流量来源,例如 Meta、Reddit 或 TikTok。用于在数据分析中对广告活动进行分组。 | | **Campaign ID** | 广告平台中广告活动的唯一标识符。 | | **Campaign name** | 广告活动的可读名称。 | | **Ad set ID** | 广告平台中广告组的唯一标识符。 | | **Ad set name** | 广告组的名称。 | | **Ad ID** | 单个广告素材的唯一标识符。 | | **Ad name** | 广告素材或变体的名称。 | 4. 默认情况下,您创建的是 One Link。它会自动检测用户的平台,并在记录点击后将其重定向到 App Store 或 Google Play。 如果您希望为每个平台使用单独的重定向 URL,请取消勾选 **One Link** 复选框,并手动提供各平台对应的应用商店链接。 5. 点击 **Create**。 6. 打开您的追踪链接页面,并从以下某个部分复制 **Click link**: - **One link** — 使用此链接追踪点击并自动将用户重定向到对应的应用商店。 - **iOS link** 或 **Android link** — 如果您希望为每个应用商店使用单独的链接,可使用这些可选的特定平台版本。 :::tip 您还可以设置其他链接参数以[使用延迟数据](ua-deferred-data)。例如,您可以实现延迟深度链接功能。 ::: 7. 前往您的广告平台,将该链接作为广告目标 URL 粘贴到您的广告中。 现在,应用安装将与对应的广告和广告活动进行匹配,您可以在 **Analytics** 页面上衡量广告活动的效果。 --- # File: ua-deferred-data --- --- title: "Adapty Attribution 中的延迟深链接" description: "在 Adapty Attribution 中配置延迟深链接。" --- 延迟深度链接允许你在用户点击广告后安装应用时,将自定义数据传递给应用。例如,用户安装并首次启动应用后,可以直接跳转到应用内的特定位置。 具体工作原理如下: 1. 用户点击广告时,Adapty 保存点击数据。 2. Adapty 注册安装事件时,从点击记录中获取延迟数据。 3. 用户安装应用并首次启动后,Adapty 检索已存储的数据,应用接收自定义参数,你可以在应用代码中针对不同参数值做出相应处理。 Adapty 支持以下延迟数据参数: - `ios_deferred_data` - `android_deferred_data` - `deferred_data_sub[1-10]` 如需添加延迟数据参数,请在广告系列设置中将其附加到您的链接: 1. 从 **Integrations -> Meta/TikTok Ads** 页面打开您的广告系列,或从 **Tracking links** 页面打开您的追踪链接。复制您将在广告系列中使用的点击链接。 2. 在你的广告平台(Meta、TikTok、Google Ads 等)中,将链接粘贴到广告目标 URL 字段,然后将延迟数据参数作为额外的查询参数追加到链接末尾——每个参数前面加上 `&`。例如,要在安装后将 iOS 用户引导至"欢迎"页面,请添加 `&ios_deferred_data=welcome`。最终的目标 URL 如下所示: ``` https://api-ua.adapty.io/api/v1/attribution/click?adpt_cid=__ADAPTY__ID__&ios_deferred_data=welcome&campaign_id=__CAMPAIGN_ID__&adset_id=__AID__&ad_id=__CID__&campaign_name=__CAMPAIGN_NAME__&adset_name=__AID_NAME__&ad_name=__CID_NAME__&redirect_url=__APP_LINK__ ``` 3. 在你的应用代码中处理参数。请注意,延迟数据参数位于 `payload` 参数中,且 `payload` 参数是经过转义的 JSON,因此你需要在应用代码中对其进行解析。 例如,以下是如何处理 `ios_deferred_data` 为 `welcome` 的安装情况: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadStr = details.payload, let data = payloadStr.data(using: .utf8), let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let deeplink = payload["ios_deferred_data"] as? String, deeplink == "welcome" else { return } DispatchQueue.main.async { print("Navigate to welcome screen") // navigate to your screen here } } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { details.payload?.let { runCatching { val json = JSONObject(it) if (json.optString("android_deferred_data") == "welcome") { println("Navigate to welcome screen") // navigate here } }.onFailure(Throwable::printStackTrace) } } }) ``` </TabItem> <TabItem value="rn" label="React Native" default> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { // Parse the payload JSON and navigate to welcome screen if needed try { if (details.payload) { const payload = JSON.parse(details.payload); if (payload.ios_deferred_data === 'welcome') { // Navigate to welcome screen // Replace with your app's navigation logic // For example, using React Navigation: // navigation.navigate('Welcome'); console.log('Navigate to welcome screen'); } } } catch (error) { console.error('Error parsing installation details payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; if (payload['ios_deferred_data'] == 'welcome') { print('Navigate to welcome screen'); } }); ``` </TabItem> </Tabs> --- # File: ua-attribution-data --- --- title: "在应用中接收归因数据" description: "在 Adapty 将安装匹配到某个广告活动后,在应用中访问广告活动的归因数据。" --- 当 Adapty 将安装匹配到某个推广活动时,它会在 `onInstallationDetailsSuccess` 回调中将归因数据返回给你的应用。利用这些数据,可以根据带来安装的渠道或活动来个性化用户体验。 归因数据以嵌套的 `attribution` 对象形式包含在 `payload` 字段中,包含以下字段: | 字段 | 描述 | |---|---| | `channel` | 获客渠道(例如 `facebook`、`tiktok`、`google`、`organic`) | | `campaign_id` | 广告系列标识符 | | `campaign_name` | 广告系列名称 | | `adset_id` | 广告组标识符 | | `adset_name` | 广告组名称 | | `ad_id` | 广告/素材标识符 | | `ad_name` | 广告/素材名称 | 所有字段均为可选项。对于自然流量安装或无法确定归因来源的情况,`payload` 字段中不包含 `attribution` 对象。 如需在应用中读取归因数据: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadDict = details.payload?.dictionary, let attribution = payloadDict["attribution"] as? [String: Any] else { return } let channel = attribution["channel"] as? String let campaignName = attribution["campaign_name"] as? String let adName = attribution["ad_name"] as? String print("Channel: \(channel ?? "organic")") } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { val payloadStr = details.payload ?: return runCatching { val payload = JSONObject(payloadStr) val attribution = payload.optJSONObject("attribution") ?: return val channel = attribution.optString("channel") val campaignName = attribution.optString("campaign_name") val adName = attribution.optString("ad_name") println("Channel: $channel") }.onFailure(Throwable::printStackTrace) } }) ``` </TabItem> <TabItem value="rn" label="React Native"> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { try { if (!details.payload) return; const payload = JSON.parse(details.payload); const attribution = payload.attribution; if (!attribution) return; const channel = attribution.channel; const campaignName = attribution.campaign_name; const adName = attribution.ad_name; console.log('Channel:', channel ?? 'organic'); } catch (error) { console.error('Error parsing payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; final attribution = payload['attribution'] as Map<String, dynamic>?; if (attribution == null) return; final channel = attribution['channel'] as String?; final campaignName = attribution['campaign_name'] as String?; final adName = attribution['ad_name'] as String?; print('Channel: ${channel ?? 'organic'}'); }); ``` </TabItem> </Tabs> --- # File: ua-facebook --- --- title: "将 Meta Ads 与 Adapty 归因集成" description: "将 Meta Ads 连接到 Adapty 归因,以跟踪和优化 Facebook、Instagram、Messenger 和 Audience Network 上的广告系列效果。" --- Adapty 的 Meta 归因集成让你能够跨 Facebook、Instagram、Messenger 和 Audience Network 追踪并优化广告系列表现。 :::tip 请参阅我们的[在 Meta Ads Manager 中设置广告的指南](meta-create-campaign)。 ::: ## 步骤 1. 连接您的 Facebook 账号 \{#step-1-connect-your-facebook-account\} 要将 Meta Ads 连接到 Adapty 归因,请从左侧侧边栏前往 **Integrations > Meta**。您有两种连接方式: - **Continue with Facebook**:通过 OAuth 连接。如果您使用个人或企业 Facebook 账号登录 Meta Ads Manager,请选择此方式。 - **Add system token**:使用永久系统用户令牌连接。如果您的组织通过 Meta Business 系统用户管理广告账号,请选择此方式。 <Tabs> <TabItem value="oauth" label="Continue with Facebook"> :::important 确保你的 Facebook 账号有权访问所需的广告系列和像素。 ::: 1. 点击 **Continue with Facebook**。 2. 使用你的 Facebook 账号登录,然后点击 **Continue**。 3. 查看所请求的权限,然后点击 **Save**。 </TabItem> <TabItem value="system" label="Add system token"> 在 Meta Business Settings 中生成[系统用户访问令牌](https://developers.facebook.com/documentation/ads-commerce/marketing-api/collaborative-ads/managed-partner-ads/api-guide/prerequisites/generate-access-token-system-user),然后将其添加到 Adapty Attribution 中。 :::important 开始之前,你需要在 Meta Business 账户中拥有一个[系统用户](https://www.facebook.com/business/help/503306463479099),并已将要追踪的广告账户分配给该用户。此外,还需要在账户中添加一个应用——生成令牌时需要选择此应用。 ::: **在 Meta Business Settings 中生成令牌:** 1. 进入 **Business Settings**。 2. 在 **Users** 下,选择 **System users**。 3. 选择您的系统用户,然后点击 **Generate new token**。 4. 从下拉菜单中选择您的应用。 5. 在权限列表中,启用 `ads_read`。这是 Adapty Attribution 读取广告系列和广告数据所需的唯一权限。 6. 点击 **Generate token**。 7. 复制令牌并妥善保存。Meta 只会显示一次。 :::note **Token expiration** 设置控制连接保持活跃的时长。设置了过期日期的 token 必须在到期前重新生成并重新连接,否则归因将停止。没有过期日期的 token 可以避免这一问题,但属于长期有效凭证,请妥善保管,一旦泄露应立即撤销。 ::: **在 Adapty Attribution 中添加 token:** 1. 点击 **Add system token**。 2. 粘贴 token 并点击 **Connect**。 </TabItem> </Tabs> 完成后,您的所有广告账户将被添加到 Adapty Attribution,接下来可以继续添加广告系列。 ## 第二步:添加广告系列 \{#step-2-add-campaigns\} 要将 Meta 广告系列添加到 Adapty Attribution 并在 Adapty 中追踪 Meta 广告效果,请按以下步骤操作: 1. 切换到 **Web Campaigns** 标签页,点击 **Create configuration**。 2. 在 **General** 标签页中,展开 **iOS** 和/或 **Android** 部分,粘贴 App Store 和/或 Google Play 应用链接。 3. 复制 **Click link** 字段的值。然后在 Meta Ads Manager 中打开你的广告,将此链接粘贴进去。这样 Adapty 就能将安装量和购买记录与 Meta 广告关联起来。 4. 要将转化事件回传给 Meta,你还可以在 Adapty Attribution 中将 Meta 的像素与广告系列关联。为此,在 **Pixel** 下拉列表中选择一个已有的像素。选择像素后,可点击 **Send test event** 验证连接是否正常。 ## 第三步:映射事件 \{#step-3-map-events\} 若要将转化事件回传给 Meta 以优化广告投放,您需要在 **Events names** 部分配置事件映射。这样 Adapty 就能在用户执行应用内操作时,自动将订阅事件发送到您的 Meta Pixel。 在 **Events names** 部分,打开您希望在 Meta Ads Manager 中追踪的事件开关。对于每个已启用的事件,从下拉菜单中选择对应的 Meta 事件,或自定义事件名称。默认情况下,Adapty 会将 Adapty 事件映射到 Meta 的标准事件。 点击 **Save** 保存事件映射配置。 ## 其他配置 \{#additional-configuration\} ### 附加参数 \{#additional-parameters\} **Additional parameter** 字段允许你添加自定义数据点,以便在 Adapty 之外进行分析。当你需要将特定的广告活动或用户数据传递给外部分析工具或归因合作伙伴时,这一功能非常有用。 在 **Additional parameter** 字段中,输入你希望包含在归因追踪中的任何自定义数据。该附加参数将包含在发送给 Meta 的所有归因数据中,可用于高级广告活动分析和优化。 例如,如果你正在运行同一广告系列的多个变体,可以添加 `variant=A` 或 `variant=B` 来区分不同的素材方案。 :::important 附加参数会改变你粘贴到 Meta Ads Manager 中的**点击链接**。如果你已经将该链接复制到 Meta Ads Manager,并在之后添加了自定义参数,请确保重新复制并粘贴包含该自定义参数的最新点击链接。 ::: <br/> ### 设置 \{#settings\} **Settings** 标签页控制 Adapty 如何将用户行为与你的 Meta Ads 广告活动进行匹配。这些设置决定了确定性归因匹配和概率性归因匹配的时间窗口。 要进行配置,请前往广告活动配置中的 **Settings** 标签页。你会看到以下两个主要设置: - **确定性匹配窗口**:使用精确的设备标识符(如 iOS 上的 IDFA 或 Android 上的 Advertising ID)将用户与广告系列进行高精度匹配。建议将此项设置为 168 小时(7 天)以获得最佳归因准确性——这也是默认推荐值。当用户点击您的 Meta 广告并在此窗口内安装应用时,Adapty 可以通过设备标识符明确将该安装归因到对应的广告点击。 - **Probabilistic matching window**:当无法进行确定性匹配时,系统会使用统计模型和设备指纹技术来匹配用户。对于大多数广告活动,建议将此项设置为 6 小时——这是默认值,适用于大多数场景。对于点击量较高的广告活动,可将其缩短至 1-2 小时。对于因隐私设置或其他原因无法进行确定性匹配的用户,Adapty 会在此较短时间窗口内使用概率匹配。 点击 **Save** 应用您的设置。 ### 收入覆盖 \{#revenue-override\} 如果您跟踪试用事件并希望 Meta 将收入归因于这些事件,请使用 **Revenue override** 部分。启用 **Trial started** 事件后,该部分将显示出来。 对于每个试用事件目标,设置要上报为收入的订阅价格百分比。例如,设置为 30% 时,Adapty 会将订阅价格的 30% 作为试用事件的转化值发送给 Meta。 要添加覆盖项: 1. 在 **Events names** 部分启用 **Trial started**。 2. 在 **Revenue override** 中,点击 **Add override**。 3. 选择目标事件并输入收入百分比(0–100)。 4. 点击 **Save**。 ### 发送所有事件 \{#send-all-events\} 默认情况下,Adapty 仅将归因于 Meta 广告系列的用户事件发送到您的像素。启用**发送所有事件**后,来自自然流量和未归因用户的事件也会转发到该像素。 启用后,无论广告系列归因如何,每次安装和交易事件都会发送到像素。这样可以为 Meta 提供更广泛的转化数据,用于受众建模和广告系列优化。 如需启用此选项,请在广告系列设置中选择**发送所有事件(将自然流量/未归因事件转发至此像素)**,然后点击 **Save**。 --- # File: ua-tiktok --- --- title: "将 TikTok for Business 与 Adapty Attribution 集成" description: "将 TikTok for Business 连接到 Adapty Attribution,以在 TikTok Ads Manager 中追踪和优化广告系列效果。" --- Adapty Attribution 的 TikTok for Business 集成,让你能够在 TikTok 中追踪和优化广告系列表现。 :::tip 请参阅我们的 [TikTok for Business 广告设置指南](tiktok-create-campaign)。 ::: ## 第一步:连接您的 TikTok 账号 \{#step-1-connect-your-tiktok-account\} 1. 在左侧边栏中进入 **Integrations > TikTok Ads**,然后点击 **Continue with TikTok**。 2. 使用您的 TikTok 账号登录,然后点击 **Continue**。 3. 查看所请求的权限,然后点击 **Save**。 完成后,您的所有广告账户将被添加到 Adapty Attribution。您可以继续添加广告活动。 ## 第二步:添加广告系列 \{#step-2-add-campaigns\} 要将 TikTok for Business 广告系列添加到 Adapty 归因并在 Adapty 中跟踪 TikTok 广告效果,请执行以下操作: 1. 切换到 **Web Campaigns** 标签页,点击 **Create configuration**。 2. 在 **General** 标签页中,展开 **iOS** 和/或 **Android** 部分,粘贴 App Store 和/或 Google Play 应用程序 URL。 3. 复制 **Click link** 字段的值。然后,在 TikTok Ads Manager 中创建广告时,将此值粘贴到 **Advanced Settings** 部分下的 **Tracking URL** 字段中。这样 Adapty 就能将安装和购买行为与 TikTok 中的广告关联起来。 4. (可选)如需将转化事件回传至 TikTok,您还可以在 Adapty 归因中将 TikTok 像素与广告系列关联。方法是在 **Pixel** 下拉菜单中选择一个已有像素。选择像素后,可点击 **Send test event** 验证连接是否正常。 ## 步骤 3:映射事件 \{#step-3-map-events\} 要将转化事件回传给 TikTok 以优化广告系列,您需要在 **Events names** 部分配置事件映射。这样,当用户在您的应用中执行操作时,Adapty 就能自动将订阅事件发送到您的 TikTok 像素。 在 **Events names** 部分,开启您想在 TikTok 广告管理器中追踪的事件。对于每个已启用的事件,从下拉菜单中选择对应的 TikTok 事件,或自定义一个。默认情况下,Adapty 会将 Adapty 事件映射到 TikTok 的标准事件。 点击 **Save** 以应用您的事件映射配置。 ## 其他配置 \{#additional-configuration\} ### 附加参数 \{#additional-parameters\} **Additional parameter** 字段允许你添加自定义数据点,用于 Adapty 之外的分析。当你需要将特定的广告系列或用户数据传递给外部分析工具或归因合作伙伴时,这一功能非常有用。 在 **Additional parameter** 字段中,输入你希望随归因追踪一同上报的任意自定义数据。该附加参数将包含在所有发送至 TikTok 的归因数据中,可用于高级广告系列分析与优化。 例如,如果你同时在跑同一个广告系列的多个变体,可以添加 `variant=A` 或 `variant=B` 来区分不同的创意方案。 :::important 附加参数会影响你粘贴到 TikTok Ads Manager 中的**点击链接**。如果你已经将该链接复制到 TikTok Ads Manager 中,并在此之后添加了自定义参数,请务必复制并粘贴包含该自定义参数的最新点击链接。 ::: <br/> ### 设置 \{#settings\} **Settings** 标签页用于控制 Adapty 如何将用户行为与您的 TikTok Ads 广告活动进行匹配。这些设置决定了确定性归因和概率性归因匹配的时间窗口。 如需配置,请进入广告活动配置中的 **Settings** 标签页,您将看到两个主要设置项: - **确定性匹配窗口**:此功能使用精确的设备标识符(如 iOS 上的 IDFA 或 Android 上的 Advertising ID)将用户与广告活动进行高精度匹配。建议将其设置为 168 小时(7 天)以获得最高归因精度——这是默认值,也是推荐值。当用户点击您的 TikTok 广告并在此窗口内安装应用时,Adapty 可以通过设备标识符明确地将该安装归因于对应的广告点击。 - **概率匹配窗口**:当无法进行确定性匹配时,系统会使用统计建模和设备指纹识别来匹配用户。对于大多数广告活动,建议将此项设置为 6 小时——这也是默认值,适用于大多数场景。对于点击量较高的广告活动,可以将其缩短至 1-2 小时。对于因隐私设置或其他原因无法确定性匹配的用户,Adapty 会在此较短的时间窗口内使用概率匹配。 点击 **Save** 应用您的设置。 ### 收益覆盖 \{#revenue-override\} 如果您需要追踪试用事件并希望 TikTok 将收益归因到这些事件,请使用**收益覆盖**部分。该部分在启用 **Trial started** 事件后显示。 对于每个试用事件目标,设置需上报为收益的订阅价格百分比。例如,设置为 30% 时,Adapty 会将订阅价格的 30% 作为试用事件的转化值发送给 TikTok。 添加覆盖规则的步骤如下: 1. 在 **Events names** 部分启用 **Trial started**。 2. 在 **Revenue override** 中,点击 **Add override**。 3. 选择目标事件并输入收入百分比(0–100)。 4. 点击 **Save**。 ### 发送所有事件 \{#send-all-events\} 默认情况下,Adapty 仅将已归因到 TikTok 广告系列的用户事件发送到您的 pixel。启用 **Send all events** 后,来自自然流量和未归因用户的事件也会转发到 pixel。 启用后,每次安装和交易事件都会发送到 pixel,无论广告系列归因如何。借此可以为 TikTok 提供更广泛的转化数据,用于受众建模和广告系列优化。 要启用此选项,请在广告系列设置中选择 **Send all events (forward organic/non-attributed events to this pixel)**,然后点击 **Save**。 --- # File: ua-funnelfox --- --- title: "将 FunnelFox 与 Adapty 归因集成" description: "将 FunnelFox 的 Web 转 App 漏斗连接到 Adapty 归因,全面追踪从网页触点到付费用户的完整获客路径。" --- [FunnelFox](https://funnelfox.com) 是一个用于构建 web2app 漏斗的平台,让你能够在 App Store 之外获取用户并向其收费,从而绕过 App Store 的手续费和其他限制。 连接后,FunnelFox 会将交易事件发送到 Adapty 归因,让你获得从 Web 触点到付费订阅用户的完整归因路径。 要设置集成,请使用 **Project ID** 将一个或多个 FunnelFox 项目关联到你的 Adapty 应用。 ## 工作原理 \{#how-it-works\} 当用户在 FunnelFox 漏斗中完成购买时,FunnelFox 会将交易事件发送至 Adapty Attribution。Adapty 使用 **Project ID** 来识别该交易属于哪个应用。事件随后会被存储并显示在 Adapty Attribution 的分析数据中。 每笔交易包含以下信息: - **订阅生命周期事件**:已开始、已续订、已取消、试用已转化、已退款等 - **归因数据**:广告系列、广告组和广告标识符;UTM 参数;平台点击 ID(fbclid、ttclid、gclid) - **漏斗与实验数据**:FunnelFox 漏斗名称和实验名称,方便你对比 A/B 测试的各个实验变体 Adapty 会根据交易中的点击 ID 自动判断**渠道**(Facebook、TikTok、Google 或自然流量),无需手动配置。 :::note FunnelFox 交易使用**首次付款日期**作为同期群日期,而非安装日期,因为网页购买没有应用安装事件。 ::: ## 配置集成 \{#configure-integration\} ### 步骤 1. 在 FunnelFox 中获取您的项目 ID \{#step-1-get-your-project-id-in-funnelfox\} 1. 在您的 FunnelFox 看板中,点击左侧边栏中的 **Settings**。 2. 在 **Project info** 部分,复制 **ID** 值。 ### 第 2 步:在 Adapty UA 中添加项目 \{#step-2-add-the-project-in-adapty-ua\} 1. 在 Adapty UA 中,前往 [**Integrations > FunnelFox**](https://app.adapty.io/ua/integrations/funnelfox)。 2. 粘贴从 FunnelFox 复制的 Project ID。 3. 点击 **Save**。 如需连接更多 FunnelFox 项目,请点击 **Add project** 并对每个额外项目重复上述两个步骤。 --- # File: ua-custom-s3 --- --- title: "Adapty 归因中的自定义 S3" description: "将用户获取数据导出到您的自定义 S3 兼容存储,以进行高级分析和报告。" --- Adapty Attribution 与自定义 S3 兼容存储的集成,让您能够将用户获取广告系列数据安全地保存在您自己的 S3 兼容存储中。您可以将广告系列效果数据、归因数据和用户获取事件以 .csv 文件的形式保存到自定义 S3 存储桶中。 要完成此集成配置,您需要在 S3 兼容存储控制台和 Adapty Attribution 看板中按照几个简单步骤操作。 :::note Adapty Attribution 每 **24 小时**在 UTC 时间 4:00 发送一次数据。 每个文件将包含前一个完整日历日(UTC)内创建的所有事件数据。例如,3 月 8 日 UTC 4:00 自动导出的数据,将涵盖 3 月 7 日 00:00:00 至 23:59:59(UTC)期间创建的全部事件。 ::: ## 设置自定义 S3 集成 \{#set-up-custom-s3-integration\} 要开始接收数据,请在 Adapty Attribution 中配置集成: 1. 前往 [**Integrations** -> **Custom S3**](https://app.adapty.io/ua/integrations/custom-s3) 2. 开启 **Export install events to custom S3** 开关。 3. 填写必填字段,以在您的自定义 S3 存储与 Adapty Attribution 用户画像之间建立连接。 | 字段 | 描述 | |:----------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Access Key ID** | 用于验证用户或应用程序访问 S3 兼容存储服务的唯一标识符。可在存储服务提供商的控制台中找到此 ID。 | | **Secret Access Key** | 与 Access Key ID 配合使用的私钥,用于验证用户或应用程序访问 S3 兼容存储服务。可在存储服务提供商的控制台中找到此密钥。 | | **S3 Bucket Name** | 用于标识存储环境中特定 S3 存储桶的全局唯一名称。S3 存储桶是一种简单的存储服务,允许用户在云端存储和检索文件、图片等数据对象。 | | **Region**(可选) | 从管理控制台获取您的 Region。 | | **Folder Inside the Bucket**(可选) | 您希望在所选 S3 存储桶内创建的文件夹名称。请注意,S3 使用对象键前缀来模拟文件夹,这些前缀本质上就是文件夹名称。 | | **Custom Endpoint URL** | S3 兼容存储服务的端点 URL,由存储服务提供商提供(例如 MinIO、DigitalOcean Spaces、Wasabi 等)。 | :::note 你也可以在 S3 存储桶名称字段中指定嵌套目录,例如 `adapty-ua-events/com.sample-app` ::: ## 手动数据导出 \{#manual-data-export\} 除了将事件数据自动导出到您的自定义 S3 存储之外,Adapty Attribution 还提供手动文件导出功能。通过此功能,您可以选择用户获取数据的日期,并手动将其导出到您的 S3 存储桶。这让您能够更灵活地控制导出哪些数据以及何时导出。 ## 表格结构 \{#table-structure\} 在自定义 S3 集成中,Adapty 归因提供了一张用于存储安装事件历史数据的表格。该表格包含用户画像信息、收入与收益、原始应用商店等多项数据。 :::warning 请注意,随着我们或合作第三方引入新数据,该结构可能会持续扩展。请确保你处理该数据的代码具有足够的健壮性,依赖特定字段而非整体结构。 ::: 以下是事件的表格结构: | 列名 | 描述 | |--------------------------|-------------------------------------------| | `adapty_profile_id` | Adapty 用户画像唯一标识符 | | `install_id` | 安装唯一标识符 | | `created_at` | 记录创建时间戳(ISO 8601) | | `installed_at` | 应用安装时间戳(ISO 8601) | | `store` | 应用商店(`ios`、`android`) | | `country` | 用户国家代码(ISO 3166-1 alpha-2) | | `ip_address` | 客户端 IP 地址 | | `idfa` | iOS 广告标识符 | | `idfv` | iOS 供应商标识符 | | `gaid` | Google 广告 ID(Android) | | `android_id` | Android 设备 ID | | `app_set_id` | Android App Set ID | | `bundle_id` | 应用包标识符(如 `com.example.app`) | | `device_brand` | 设备品牌(如 `Apple`、`Samsung`) | | `device_model` | 设备型号(如 `iPhone15,2`) | | `os_version` | 主要 OS 版本 | | `app_version` | Adapty SDK 上报的应用版本 | | `sdk_version` | Adapty SDK 版本 | | `channel` | 归因渠道 | | `campaign_id` | 广告系列标识符 | | `campaign_name` | 广告系列名称 | | `adset_id` | 广告组标识符 | | `adset_name` | 广告组名称 | | `ad_id` | 广告标识符 | | `ad_name` | 广告名称 | | `keyword_id` | 关键词标识符 | | `keyword_name` | 关键词名称 | | `asa_org_id` | Apple Search Ads 组织 ID | | `asa_keyword_match_type` | ASA 关键词匹配类型(`Exact`、`Broad`) | | `asa_attribution` | ASA 归因数据(JSON 字符串) | | `asa_conversion_type` | ASA 转化类型 | | `asa_country_or_region` | ASA 国家或地区 | | `asa_creative_set_name` | ASA 创意组名称 | | `fbclid` | Facebook 点击 ID | | `ttclid` | TikTok 点击 ID | | `utm_source` | UTM source 参数 | | `utm_medium` | UTM medium 参数 | | `utm_campaign` | UTM campaign 参数 | | `utm_term` | UTM term 参数 | | `utm_content` | UTM content 参数 | --- # File: ua-amazon-s3 --- --- title: "Adapty 归因中的 Amazon S3" description: "将用户获取数据导出到 S3,用于高级分析和报告。" --- Adapty Attribution 与 Amazon S3 的集成,让你能够将用户获取活动数据安全地存储在一个集中位置。你可以将活动效果数据、归因数据和用户获取事件以 .csv 文件的形式保存到你的 Amazon S3 存储桶中。 要设置此集成,你需要在 AWS Console 和 Adapty Attribution 看板中完成几个简单的步骤。 :::note Adapty Attribution 每 **24 小时**在 UTC 时间 4:00 发送一次数据。 每个文件将包含前一个完整日历日(UTC 时区)内产生的所有事件数据。例如,3 月 8 日 UTC 04:00 自动导出的数据,将涵盖 3 月 7 日 00:00:00 至 23:59:59(UTC)期间的所有事件。 ::: ## 如何设置 Amazon S3 集成 \{#how-to-set-up-amazon-s3-integration\} 要开始接收数据,您需要以下凭证: 1. Access key ID 2. Secret access key 3. S3 存储桶名称 4. S3 存储桶内的文件夹名称 :::note 嵌套目录 您可以在 Amazon S3 存储桶名称字段中指定嵌套目录,例如 adapty-ua-events/com.sample-app ::: ### 步骤 1. 创建 Amazon S3 凭证 \{#step-1-create-amazon-s3-credentials\} 本指南将帮助你在 AWS 控制台中创建所需的凭证。 #### 1.1. 创建访问策略 \{#11-create-access-policy\} 1. 在 AWS 控制台中进入 [IAM 策略看板](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) 2. 选择 **Create Policy** 选项 <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在策略编辑器中,粘贴以下 JSON,并将 `adapty-s3-integration-test` 替换为你的存储桶名称: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 完成策略配置后,您可以选择添加标签(可选),然后点击 **Next** 进入最后一步 5. 在此步骤中,为您的策略命名,然后点击 **Create policy** 按钮完成创建 <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 1.2. 创建 IAM 用户 \{#12-create-iam-user\} 要允许 Adapty Attribution 将原始数据报告上传到您的存储桶,您需要为拥有该存储桶写入权限的用户提供 Access Key ID 和 Secret Access Key。 1. 前往 IAM 控制台,选择 [Users 部分](https://console.aws.amazon.com/iamv2/home#/users) 2. 点击 **Add users** 按钮 <img src="/assets/shared/img/bb612c8-CleanShot_2023-03-21_at_11.12.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 为用户设置名称,选择 **Access key – Programmatic access**,然后继续配置权限 <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在下一步中,请选择 **Add user to group** 选项,然后点击 **Create group** 按钮 <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 接下来,您需要为用户组指定一个名称,并选择之前创建的策略 6. 选择策略后,点击 **Create group** 按钮完成操作 <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 成功创建群组后,请**选择它**并继续下一步 <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. 这是本部分的最后一步,直接点击 **Create User** 按钮即可。 <img src="/assets/shared/img/ea43722-CleanShot_2023-03-21_at_11.40.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. 最后,你可以选择**以 .csv 格式下载凭据**,或者直接从看板中复制并粘贴凭据。 <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 第 2 步:在 Adapty Attribution 中配置集成 \{#step-2-configure-integration-in-adapty-attribution\} 1. 前往 [**Integrations** -> **Amazon S3**](https://app.adapty.io/ua/integrations/s3) 2. 开启 **Export install events to Amazon S3** 开关。 3. 填写以下字段,以建立 Amazon S3 与 Adapty Attribution 用户画像之间的连接: | 字段 | 描述 | |:-----------------------------| :----------------------------------------------------------- | | **Access Key ID** | 用于验证用户或应用程序访问 AWS 服务的唯一标识符。可在下载的 [csv 文件](ua-amazon-s3#step-1-create-amazon-s3-credentials) 中找到此 ID。 | | **Secret Access Key** | 与 Access Key ID 配合使用的私钥,用于验证用户或应用程序访问 AWS 服务。可在下载的 [csv 文件](ua-amazon-s3#step-1-create-amazon-s3-credentials) 中找到此密钥。 | | **S3 Bucket Name** | 在 AWS 云中标识特定 S3 存储桶的全局唯一名称。S3 存储桶是一种简单的存储服务,允许用户在云中存储和检索文件、图片等数据对象。 | | **Folder Inside the Bucker** | 您希望在所选 S3 存储桶中创建的文件夹名称。请注意,S3 通过对象键前缀来模拟文件夹,这些前缀本质上就是文件夹名称。 | | **Region**(可选) | 在 AWS 管理控制台中,于您的 IAM 用户账户下获取您的区域信息。 | ## 手动导出数据 \{#manual-data-export\} 除了自动将事件数据导出到 Amazon S3 之外,Adapty Attribution 还提供手动文件导出功能。通过此功能,您可以选择特定日期的用户获取数据,并手动将其导出到您的 S3 存储桶。这让您能够更灵活地控制导出的数据内容及导出时机。 ## 表结构 \{#table-structure\} 在 AWS S3 集成中,Adapty Attribution 提供一张表用于存储安装事件的历史数据。该表包含用户画像、收入与实际所得、来源商店等多项数据信息。 :::warning 请注意,随着我们或第三方合作伙伴引入新数据,该结构可能会持续扩展。请确保处理该数据的代码足够健壮,只依赖特定字段,而不依赖整体结构。 ::: 以下是事件的表结构: | 字段 | 说明 | |--------------------------|-------------------------------------------| | `adapty_profile_id` | Adapty 用户画像唯一标识符 | | `install_id` | 安装唯一标识符 | | `created_at` | 记录创建时间戳(ISO 8601) | | `installed_at` | 应用安装时间戳(ISO 8601) | | `store` | 应用商店(`ios`、`android`) | | `country` | 用户国家代码(ISO 3166-1 alpha-2) | | `ip_address` | 客户端 IP 地址 | | `idfa` | iOS 广告主标识符 | | `idfv` | iOS 供应商标识符 | | `gaid` | Google 广告 ID(Android) | | `android_id` | Android 设备 ID | | `app_set_id` | Android App Set ID | | `channel` | 归因渠道 | | `campaign_id` | 广告系列标识符 | | `campaign_name` | 广告系列名称 | | `adset_id` | 广告组标识符 | | `adset_name` | 广告组名称 | | `ad_id` | 广告标识符 | | `ad_name` | 广告名称 | | `keyword_id` | 关键词标识符 | | `keyword_name` | 关键词名称 | | `asa_org_id` | Apple Search Ads 组织 ID | | `asa_keyword_match_type` | ASA 关键词匹配类型(`Exact`、`Broad`) | | `asa_attribution` | ASA 归因数据(JSON 字符串) | | `asa_conversion_type` | ASA 转化类型 | | `asa_country_or_region` | ASA 国家或地区 | | `asa_creative_set_name` | ASA 创意集名称 | | `fbclid` | Facebook 点击 ID | | `ttclid` | TikTok 点击 ID | | `utm_source` | UTM 来源参数 | | `utm_medium` | UTM 媒介参数 | | `utm_campaign` | UTM 广告系列参数 | | `utm_term` | UTM 词语参数 | | `utm_content` | UTM 内容参数 | --- # File: ua-google-cloud-storage --- --- title: "Adapty Attribution 中的 Google Cloud Storage" description: "将 Google Cloud Storage 与 Adapty Attribution 集成,实现安全的用户获取数据存储。" --- Adapty Attribution 与 Google Cloud Storage 的集成,让您可以将用户获取活动数据安全地存储在一个集中位置。您可以将活动效果数据、归因数据和用户获取事件以 .csv 文件的形式保存到您的 Google Cloud Storage 存储桶中。 要设置此集成,您只需在 Google Cloud Console 和 Adapty Attribution 看板中完成几个简单步骤。 :::note 计划 Adapty Attribution 每天 UTC 时间 4:00 将您的数据发送至 Google Cloud Storage。 每个文件包含前一整个自然日(UTC 时间)内产生的事件数据。例如,3 月 8 日 UTC 04:00 自动导出的数据,包含 3 月 7 日 00:00:00 至 23:59:59(UTC)的所有事件。 ::: ## 如何设置 Google Cloud Storage 集成 \{#how-to-set-up-google-cloud-storage-integration\} ### 第一步:创建 Google Cloud Storage 凭证 \{#step-1-create-google-cloud-storage-credentials\} 本指南将帮助你在 Google Cloud Platform Console 中创建所需的凭证。 为了让 Adapty Attribution 将原始数据报告上传到你指定的存储桶,需要提供服务账号的密钥,并授予对应存储桶的写入权限。通过提供服务账号密钥并授予存储桶写入权限,你可以让 Adapty Attribution 安全高效地将原始数据报告从其平台传输到你的存储环境。 :::warning 请注意,我们仅支持服务账号 HMAC 密钥授权,因此请务必确保您的服务账号 HMAC 密钥已添加"Storage Object Viewer"、"Storage Legacy Bucket Writer"和"Storage Object Creator"角色,以便正常访问 Google Cloud Storage。 ::: #### 2.1. 创建服务账号 \{#21-create-service-account\} 1. 前往您的 Google Cloud 账号的 [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) 部分,选择相关项目或新建一个项目 <img src="/assets/shared/img/30a81ef-CleanShot_2023-03-17_at_15.22.142x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 接下来,点击 "+ CREATE SERVICE ACCOUNT" 按钮,为 Adapty Attribution 创建一个新的服务账号。 <img src="/assets/shared/img/98f8ebf-CleanShot_2023-03-17_at_15.40.062x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 填写第一步中的各个字段,访问权限将在后续步骤中授予。如需了解该页面的更多详情,请参阅[文档](https://docs.cloud.google.com/iam/docs/service-accounts-create)。 <img src="/assets/shared/img/2190c50-CleanShot_2023-03-17_at_15.48.552x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 要创建并下载 [JSON 私钥](https://docs.cloud.google.com/iam/docs/keys-create-delete),请导航至 KEYS 部分,然后点击 "ADD KEY" 按钮 <img src="/assets/shared/img/8a45468-CleanShot_2023-03-17_at_15.58.092x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在 DETAILS 部分,找到与刚创建的服务账户关联的 Email 值并复制。后续步骤中,授权该账户并允许其写入存储桶时需要用到这条信息。 <img src="/assets/shared/img/6ccd0f0-CleanShot_2023-03-17_at_16.03.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 2.2. 配置 Bucket 权限 \{#22-configure-bucket-permissions\} 6. 前往 Google Cloud Storage 的[存储桶](https://console.cloud.google.com/storage/browser)页面,选择一个现有存储桶或新建一个存储桶,用于存储来自 Adapty Attribution 的用户获取数据报告。 7. 导航至 **PERMISSIONS** 部分,选择[授予访问权限](https://docs.cloud.google.com/identity/docs/how-to?hl=en)选项。 <img src="/assets/shared/img/3cdd937-CleanShot_2023-03-17_at_16.14.232x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. 在 PERMISSIONS 部分,输入第五步中获取的服务账户 Email,然后选择 Storage Object Creator 角色 9. 最后,点击 SAVE 以保存更改 <img src="/assets/shared/img/62801f4-CleanShot_2023-03-17_at_16.17.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. 请记录存储桶的名称,以备后续使用 11. 完成上述步骤后,您已成功完成在 Google Cloud Console 中的必要配置!最后一步是输入存储桶名称,并下载 JSON 文件以在 Adapty 归因中使用 <img src="/assets/shared/img/c967e16-CleanShot_2023-03-17_at_16.23.332x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 第二步:在 Adapty Attribution 中配置集成 \{#step-2-configure-integration-in-adapty-attribution\} 1. 前往 [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/ua/integrations/google-cloud-storage) 2. 打开 **Export install events to Google Cloud Storage** 开关 3. 填写必填字段,以建立 Google Cloud Storage 与 Adapty Attribution 之间的连接: | 字段 | 描述 | |:------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Google Cloud service account key file** | 下载的私有 [JSON 密钥文件](ua-google-cloud-storage#step-1-create-google-cloud-storage-credentials)。 | | **Google Cloud bucket name** | 您希望在 Google Cloud Storage 中存储数据的存储桶名称。该名称在 Google Cloud Storage 环境中必须唯一,且不能包含空格。 | | **Folder inside the bucket** | 存储桶内用于存储数据的文件夹名称。该名称在存储桶内必须唯一,可用于整理数据。此字段为选填项。 | ## 手动数据导出 \{#manual-data-export\} 除了自动将事件数据导出到 Google Cloud Storage 之外,Adapty Attribution 还提供手动文件导出功能。借助此功能,您可以选择特定日期的用户获取数据,并手动将其导出到您的 GCS 存储桶。这让您能够更灵活地控制导出的数据内容及导出时机。 ## 表结构 \{#table-structure\} 在 Google Cloud Storage 集成中,Adapty 归因提供了一张表,用于存储安装事件的历史数据。该表包含用户画像、收入与收益、来源商店等多个数据点的信息。 :::warning 请注意,随着我们或第三方合作伙伴引入新数据,此结构可能会随时间不断扩展。请确保处理该数据的代码具有足够的健壮性,仅依赖特定字段,而不依赖整体结构。 ::: 以下是事件的表结构: | 列名 | 描述 | |--------------------------|-------------------------------------------| | `adapty_profile_id` | Adapty 用户画像唯一标识符 | | `install_id` | 安装唯一标识符 | | `created_at` | 记录创建时间戳(ISO 8601) | | `installed_at` | 应用安装时间戳(ISO 8601) | | `store` | 应用商店(`ios`、`android`) | | `country` | 用户国家代码(ISO 3166-1 alpha-2) | | `ip_address` | 客户端 IP 地址 | | `idfa` | iOS 广告标识符 | | `idfv` | iOS 供应商标识符 | | `gaid` | Google 广告 ID(Android) | | `android_id` | Android 设备 ID | | `app_set_id` | Android App Set ID | | `channel` | 归因渠道 | | `campaign_id` | 营销活动标识符 | | `campaign_name` | 营销活动名称 | | `adset_id` | 广告组标识符 | | `adset_name` | 广告组名称 | | `ad_id` | 广告标识符 | | `ad_name` | 广告名称 | | `keyword_id` | 关键词标识符 | | `keyword_name` | 关键词名称 | | `asa_org_id` | Apple Search Ads 组织 ID | | `asa_keyword_match_type` | ASA 关键词匹配类型(`Exact`、`Broad`) | | `asa_attribution` | ASA 归因数据(JSON 字符串) | | `asa_conversion_type` | ASA 转化类型 | | `asa_country_or_region` | ASA 国家或地区 | | `asa_creative_set_name` | ASA 创意组名称 | | `fbclid` | Facebook 点击 ID | | `ttclid` | TikTok 点击 ID | | `utm_source` | UTM source 参数 | | `utm_medium` | UTM medium 参数 | | `utm_campaign` | UTM campaign 参数 | | `utm_term` | UTM term 参数 | | `utm_content` | UTM content 参数 | --- # File: adapty-mail --- --- title: "Adapty Mail" description: "AI 生成的邮件营销活动,将试用用户转化为付费订阅者。" --- <CustomDocCardList ids={['mail-get-started', 'mail-brand', 'mail-collect-emails', 'mail-send-data-via-api', 'mail-sending-domain', 'mail-create-campaign', 'mail-analytics']} /> Adapty Mail 将你的 Adapty 用户数据转化为 AI 生成的邮件序列,帮助将试用用户转化为付费订阅者。它直接使用 Adapty 项目中已有的用户画像数据来构建、发送和归因营销活动,无需额外的邮件平台。 ## 为什么选择 Adapty Mail?\{#why-adapty-mail\} 发送定向邮件营销活动需要文案、设计、发送基础设施和收入归因,每一项都是独立的难题。 Adapty Mail 一次性解决所有问题。品牌档案从你的商店 URL 及其他来源自动构建,完整的邮件序列可在 2 分钟内生成——从你自己的域名发送,附带个性化结账链接和购买归因。 ## 工作原理 \{#how-it-works\} 1. **收集邮件地址**:你的应用通过 SDK 将用户邮件地址和 `customer_user_id` 传递给 Adapty。Adapty Mail 使用这些数据识别收件人,并将收入归因到促成每次购买的具体邮件。你也可以通过 [Adapty Mail API](mail-send-data-via-api) 从服务器发送这些数据。 2. **创建 Web 付费墙**:每封邮件都会链接到该结账页面。 3. **生成邮件序列**:AI 根据你的品牌档案生成 1–15 封邮件,包含文案、设计、主图及与应用类别和品牌风格相匹配的个性化结账链接。 4. **启动流程**:选择触发条件(从未购买、续订取消、账单问题、订阅到期或退款)和市场细分,然后关联你的营销活动。邮件将自动开始发送,由邮件驱动的购买收入将归因回促成转化的具体邮件。 ## 前置要求 \{#requirements\} 使用 Adapty Mail 需要: - 一个 Adapty 账号 - 在应用中完成邮件收集配置——参见[收集用户邮件地址](mail-collect-emails) - 在 Adapty SDK 中配置 `customer_user_id` - 一个你拥有控制权且可访问 DNS 设置的域名 - 一个 Web 支付服务商(Stripe、Paddle 或 PayPal) ## 快速开始 \{#get-started\} 按照[Adapty Mail 使用入门](mail-get-started)指南完成配置并启动你的第一个营销活动。 --- # File: mail-get-started --- --- title: "Adapty Mail 入门指南" description: "设置 Adapty Mail 并启动你的第一个电子邮件流程。" --- 本指南将带你完成 Adapty Mail 的设置,并启动你的第一个电子邮件流程。 :::note 您也可以从自己的服务器直接向 Adapty Mail 发送数据,无需使用 Adapty SDK。如果您已在后端保存了用户邮件和购买记录,或者需要从其他来源导入订阅者,请参阅[通过 Adapty Mail API 发送邮件和交易数据](mail-send-data-via-api)。 ::: 该设置共分为六个部分: 1. [配置您的 Adapty SDK](#1-configure-your-adapty-sdk) 2. [设置发送域名](#2-set-up-your-sending-domain) 3. [创建网页付费墙](#3-create-a-web-paywall) 4. [用 AI 生成营销活动](#4-generate-a-campaign-with-ai) 5. [启动流程](#5-launch-a-flow) 6. [开启发送](#6-enable-sending) :::tip 如果您是通过 Adapty 注册 Adapty Mail 的,系统会自动根据项目的商店 URL 创建您的**品牌档案**。随时打开 **Brand** 查看或完善它——详见[品牌](mail-brand)。如果您是独立注册的,请在生成营销活动或网页付费墙之前,先在同一页面上设置您的品牌。 ::: ## 开始之前 \{#before-you-start\} 在开始之前,请确认以下内容已就绪: - **DNS 访问权限**:你可以为根域名添加记录。 - **Web 支付服务商**:你已拥有 Stripe、Paddle 或 PayPal 账户,并已配置好订阅产品。 ## 1. 配置 Adapty SDK \{#1-configure-your-adapty-sdk\} :::important Adapty Mail 是一款**独立产品**。即使你的付费墙、订阅或数据分析并非由 Adapty 管理,也可以直接使用——无需迁移整个技术栈。 若想获得准确的营收数据,最低配置要求是以观察者模式安装 Adapty SDK,并启用 App Store 服务器通知。 ::: Adapty Mail 需要从你的应用中获取以下三类信息:购买数据(用于将营收归因到促成转化的邮件)、稳定的用户标识符,以及用户邮箱地址。 1. **让 Adapty 追踪您的收入。** 第一步取决于您是否已实现应用内购买: - 如果您**已通过 Adapty 实现了应用内购买**,此阶段无需进行任何其他操作。 - 如果您**已在不使用 Adapty 的情况下实现了应用内购买**,且不打算迁移到 Adapty,请以观察者模式为您的平台安装 Adapty SDK。此阶段只需将 SDK 添加到项目中,以观察者模式激活,并上报交易记录。各平台指南:[iOS](implement-observer-mode)、[Android](implement-observer-mode-android)、[React Native](implement-observer-mode-react-native)、[Flutter](implement-observer-mode-flutter)、[Unity](implement-observer-mode-unity)、[Kotlin Multiplatform](implement-observer-mode-kmp)、[Capacitor](implement-observer-mode-capacitor)。 - 如果您**尚未实现应用内购买,并希望使用 Adapty**,请按照[快速入门指南](quickstart)完成相关步骤,将购买处理交由 Adapty 管理。 然后[在 Adapty 中启用 App Store 服务器通知](enable-app-store-server-notifications),以便直接从 App Store 接收收入相关的更新。 2. **设置用户标识。** 传入一个稳定的 ID —— 可以是你的后端用户 ID、Firebase UID 或类似标识 —— 通过调用 `Adapty.identify()` 或在 SDK 启动时将 `customerUserId` 传入 `.activate()` 来完成。`customer_user_id` 是 Adapty Mail 将营销活动、点击行为和购买记录关联到正确用户画像的依据。 平台指南:[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)、[Kotlin Multiplatform](kmp-identifying-users)、[Capacitor](capacitor-identifying-users)。 3. **收集用户邮箱。** 当用户在应用中提供邮箱(例如注册或结账时),通过调用 `updateProfile` 并传入邮箱属性将其同步至 Adapty。每位活动收件人都需要填写该值。 平台指南:[iOS](setting-user-attributes)、[Android](android-setting-user-attributes)、[React Native](react-native-setting-user-attributes)、[Flutter](flutter-setting-user-attributes)、[Unity](unity-setting-user-attributes)、[Kotlin Multiplatform](kmp-setting-user-attributes)、[Capacitor](capacitor-setting-user-attributes)。 如果您的应用尚未收集电子邮件,请参阅[电子邮件收集策略](mail-collect-emails#email-collection-strategies)。 ## 2. 设置发件域名 \{#set-up-your-sending-domain\} 打开 Adapty Mail:点击顶部导航栏中的 Adapty 图标,选择 **Mail**。 Adapty Mail 使用您自己的域名发送邮件。只需添加一次 DNS 记录,所有营销活动均共用同一个已验证的域名。 1. 在 Adapty Mail 中,前往 **Settings → Email Domains**。 2. 输入您的根域名(例如 `yourapp.com`),然后点击 **Preview**。系统仅接受顶级域名,`app.yourapp.com` 等子域名会在输入时被拒绝。 3. Adapty 会生成两个发送子域名(`mail.yourapp.com` 和 `email.yourapp.com`)。点击 **Confirm** 以显示所需的 DNS 记录。 4. 在您的域名注册商处,添加所示的 10 条 DNS 记录(每个子域名 5 条): - 每个子域名 3 条 CNAME 记录(DKIM) - 每个子域名 1 条 MX 记录(Mail-From) - 每个子域名 1 条 TXT 记录(SPF,`v=spf1 include:amazonses.com ~all`) 5. 可选操作:在根域名上添加 DMARC TXT 记录(推荐)。 6. 返回 **Settings → Email Domains**,点击 **Check Verification**。 验证时间说明: - **自动轮询**:提交后约 5 分钟进行第一次检查,之后间隔逐渐延长至每小时一次,直到找到记录。 - **手动检查**:随时点击 **Check Verification** 触发即时检查。 - **DNS 传播**:通常需要几分钟,极少数情况下最长可达 48 小时。 - **验证窗口**:7 天。若超时,DNS 记录仍会保留——在 **Settings → Email Domains** 中重新输入您的域名即可开始新的验证窗口。 有关每种记录类型和域名预热的详细信息,请参阅[设置发送域名](mail-sending-domain)。 ## 3. 配置发送域名 \{#set-up-your-sending-domain\} Adapty Mail 使用你自己的域名发送邮件。只需添加一次 DNS 记录,所有营销活动都将使用同一个已验证的域名。 1. 在 Adapty Mail 中,前往 **Settings → Email Domains**。 2. 输入你的根域名(例如 `yourapp.com`),然后点击 **Preview**。系统只接受顶级域名——输入 `app.yourapp.com` 这类子域名时会被拒绝。 3. Adapty 会自动生成两个发送子域名(`mail.yourapp.com` 和 `email.yourapp.com`)。点击 **Confirm** 查看所需的 DNS 记录。 4. 在您的域名注册商处,添加显示的 10 条 DNS 记录(每个子域名各 5 条): - 每个子域名添加 3 条 CNAME 记录(DKIM) - 每个子域名添加 1 条 MX 记录(Mail-From) - 每个子域名添加 1 条 TXT 记录(SPF,`v=spf1 include:amazonses.com ~all`) 5. 可选:在根域名上添加一条 DMARC TXT 记录(推荐)。 6. 返回 **Settings → Email Domains**,点击 **Check Verification**。 验证时间一览: - **自动轮询**:提交后约 5 分钟进行首次检查,之后间隔逐渐延长至每小时一次,直到记录验证通过。 - **手动检查**:随时点击 **Check Verification** 触发立即检查。 - **DNS 传播**:通常只需几分钟,极少数情况下最长可达 48 小时。 - **验证窗口期**:7 天。若超时,DNS 记录仍会保留——在 **Settings → Email Domains** 中重新输入域名即可开启新的验证窗口。 关于各记录类型和域名预热的详细说明,请参阅[设置发件域名](mail-sending-domain)。 ### 选项 A:用 AI 生成 \{#option-a-generate-with-ai\} 页面会显示一份**前提条件**清单,每项都带有内联按钮——请按顺序逐项完成,然后回来生成。清单涵盖登录付费墙编辑工具、连接 Stripe、添加产品以及查看结果等步骤。完整操作流程请参阅[设置结账](mail-checkout)。 所有前提条件变为绿色后,点击 **Generate** 打开生成对话框: - **Environment**:选择 **Production** 或 **Sandbox**。Sandbox 使用你的 Stripe 测试模式产品,是开发和本地环境的安全默认选项。 - **Plans**:最多选择 **3 个 Stripe 方案**(每个方案对应一个产品 + 价格)。这些是生成的付费墙在结账时向用户展示的优惠内容。 点击 **Generate** 开始构建。构建完成后,打开编辑器进行审查并发布。 :::important 付费墙必须先发布,才能处理结账流量。未发布的付费墙在用户点击邮件结账链接时会返回错误。 ::: ### 选项 A:使用 AI 生成 \{#option-a-generate-with-ai\} 1. 选择 **Generate with AI**。 2. 点击 **Log in to the paywall builder**。网页付费墙编辑工具将在新标签页中打开。如果你尚未登录,请使用 Adapty 账号登录。 3. 在编辑工具中,启用你的支付服务商集成(Stripe、Paddle 或 PayPal)。详情请参阅[网页付费墙配置](web-paywall-configuration)。 4. 返回 Adapty Mail,点击 **Proceed to generation**。 5. 检查生成的付费墙,然后保存并发布。 ## 4. 用 AI 生成营销活动 \{#generate-a-campaign-with-ai\} AI 会为你生成完整的邮件序列——包括文案、设计、主视觉图片以及个性化结账链接,全部根据你的品牌量身定制。 1. 在 Adapty Mail 中,进入 **Campaigns** 并点击 **Create**。 2. 设置营销活动名称。 3. 在 **Web paywall** 下拉菜单中,选择你在上一步添加的网页付费墙。 4. 点击 **Generate emails**。 5. 填写生成对话框——语气、语言、可选的自定义提示词(最多 2,000 个字符)以及邮件数量(1–15 封,默认 4 封)。各字段的说明请参阅[创建营销活动](mail-create-campaign)。 6. 点击 **Generate**。生成通常需要几分钟。如果系统在 5 分钟内无法完成,将会超时——遇到这种情况请重试。 7. 预览每封邮件。预览头部有一个 **Theme toggle**(Auto、Light、Dark),用于控制预览的渲染方式——生成的内容在各模式下完全相同。你可以重新生成单封邮件、编辑文案,或打开 HTML 编辑器进行精细调整。 8. 点击 **Create** 保存营销活动。 活动将保存为**草稿**,尚未开始发送——活动只有在关联到流程后才会生效(下一步操作)。活动编辑器中没有单独的"发布"操作。 ## 5. 启动流程 \{#5-launch-a-flow\} 流程将一个**触发器**(如订阅到期等事件)与一个**市场细分**相关联,并向该市场细分发送你选择的**活动**。Adapty Mail 内置五种固定触发器,每种触发器都有其专属的流程视图。 1. 在 Adapty Mail 中,进入 **Flows**,然后打开你想配置的触发器: - **Never purchased** —— 已注册但尚未完成购买的用户。 - **Renewal cancelled** —— 已关闭自动续期但订阅仍在有效期内的用户。 - **Billing issue** —— 付款失败、银行卡被拒或已过期,或处于宽限期。 - **Expired** —— 订阅已到期且访问权限已失效。 - **Refunded** —— 购买后申请退款的用户。 关于每个触发器的目标定位与文案风格建议,请参阅[流程](mail-flows)。 2. 点击 **Create** 打开对话框。 3. 在对话框中: - 选择一个**市场细分**(例如,选择 **All Users** 以定向所有触达此触发器的用户,或根据用户画像属性创建新的市场细分)。 - 将内容类型保持为 **Campaign**(A/B 测试选项详见 [A/B 测试](mail-ab-testing))。 - 选择你在第 4 步中保存的 **Campaign**。 4. 点击 **Save**。 流程会立即生效——无需单独的启动步骤。从此时起,符合该市场细分条件的用户一旦触达触发事件,就会开始收到该营销活动的消息。 :::note 你可以在同一个触发器中添加多个"市场细分 → 活动"行,它们按优先级顺序执行。**All Users** 行(如果使用)必须排在最后(优先级最低),用于捕获所有未被更具体市场细分匹配到的用户。 ::: ## 6. 启用发送 \{#enable-sending\} 到目前为止,你的推送活动已配置完毕,但尚未真正触发——负责将订阅事件同步到 Adapty Mail 的 **Adapty 集成** 仍处于关闭状态。启用它是最后一步:事件开始流入,市场细分开始匹配,邮件开始发送。 此步骤仅在完成第 5 步后才可操作。在你启动任意流程之前,**Settings → Integrations** 中的 **Enable** 按钮处于禁用状态,悬停提示为 *"Set up at least one flow before enabling Adapty integration."* 1. 在 Adapty Mail 中,进入 **Settings → Integrations**。 2. 点击 **Enable Adapty integration**(如果之前已设置过集成,则点击 **Enable**)。 启用后,Adapty 会将所有订阅事件——新订阅、续订、试用、转化、退款、账单问题——发送到 Adapty Mail。这些事件将驱动市场细分的成员资格、营销活动路由,以及在用户完成转化时暂停序列的停止条件。 :::note **Settings** 中的 **Adapty integration** 开关与登录 Adapty Mail 的 Adapty 合作伙伴工作区**不同**。合作伙伴工作区负责创建您的账户(以及通过 Adapty 注册时的品牌)。此处的集成开关控制的是事件同步——需要为每个项目单独开启。 ::: ## 故障排除 \{#troubleshooting\} | 问题 | 解决方案 | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | | DNS 验证卡住 | 检查记录是否完全匹配——无末尾点号,CNAME 目标正确。等待 5–10 分钟后,再次点击 **Check Verification** | | 验证窗口已过期 | 您的记录仍然有效。在 **Settings → Email Domains** 中重新输入您的域名以开始新的验证窗口 | | 生成失败或超时 | 检查您的网络连接并重试。如果问题持续存在,请联系 Adapty 支持团队 | ## 了解更多 \{#learn-more\} - **[收集用户邮箱](mail-collect-emails)**:如果你的应用尚未收集邮箱,可参考这些策略提升覆盖率。 - **[设置发送域名](mail-sending-domain)**:DNS 记录详情、预热层级及故障排查。 - **[设置结账流程](mail-checkout)**:结账漏斗结构与个性化配置。 - **[营销活动分析](mail-analytics)**:追踪送达率、互动数据和收入。 - **[A/B 测试](mail-ab-testing)**:测试多个序列版本。 --- # File: mail-collect-emails --- --- title: "为 Adapty Mail 收集用户邮箱" description: "将用户邮箱和稳定标识符传递给 Adapty,以便营销活动能够触达您的用户。" --- Adapty Mail 需要为每位用户提供一个稳定的 `customer_user_id` 和邮箱地址,才能完成投递。在启动营销活动之前,请在应用代码中完成这两项配置。 ## 收集用户邮箱 \{#collect-user-emails\} 每位用户需要向 Adapty 传递两个值:用于识别用户身份的稳定 `customer_user_id`,以及邮箱本身。身份识别必须优先完成——没有它,Adapty 就无法找到对应的用户画像来绑定邮箱。 1. **识别用户。** 传入一个稳定的 ID——你的后端用户 ID、Firebase UID 或类似标识——可以在 SDK 启动时通过 `.activate()` 的 `customerUserId` 参数传入,也可以稍后(例如在登录时)调用 `Adapty.identify()` 完成。无论哪种方式,该 ID 都必须在展示任何付费墙之前设置好。 平台指南:[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)、[Kotlin Multiplatform](kmp-identifying-users)、[Capacitor](capacitor-identifying-users)。 2. **传递邮箱。** 用户提供邮箱后,立即通过 `updateProfile` 的 `email` 参数将其发送给 Adapty。 平台指南:[iOS](setting-user-attributes)、[Android](android-setting-user-attributes)、[React Native](react-native-setting-user-attributes)、[Flutter](flutter-setting-user-attributes)、[Unity](unity-setting-user-attributes)、[Kotlin Multiplatform](kmp-setting-user-attributes)、[Capacitor](capacitor-setting-user-attributes)。 :::important - 请始终传入**稳定的** `customer_user_id`,不要使用匿名标识符。如果用户卸载并重新安装了应用,Adapty 会通过该 ID 将重新安装与现有用户画像关联,并将购买记录归属到正确的用户。 - 在收集并向 Adapty 发送邮箱地址之前,请务必获得用户的明确授权。您需要自行确保符合 GDPR、CAN-SPAM 及您目标市场中类似法规的要求。 ::: <Details> <summary>验证您的邮箱覆盖率</summary> 完成收集功能的接入后,请在 Adapty 中检查覆盖率: 1. 前往 **Customers → Profiles**。 2. 筛选已设置邮箱的用户画像。 在发起首次活动之前,请确保活跃用户中至少有 30–50% 的邮箱覆盖率。不必等到 100%——达到 30% 即可启动。后续提供邮箱的用户,一旦满足条件,会自动加入正在进行的活动。 </Details> ## 邮箱收集策略 \{#email-collection-strategies\} 大多数应用默认不收集邮箱。根据你的应用现状选择合适的方式。 | 策略 | 适用场景 | 工作原理 | | ---------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **现有身份验证** | 任何有登录功能的应用 | 你已经拥有邮箱地址——在用户完成身份验证后将其传递给 Adapty。请参阅下方的身份验证方式参考,了解从哪里读取邮箱。 | | **付费墙前的邮箱入口** | 无身份验证的应用——健康、养生、占星、图片编辑器等 | 在用户引导和付费墙之间增加一个邮箱输入页面。由于用户已经投入了时间,转化率通常在 70–90% 之间。 | | **Web 付费墙编辑工具结账** | SDK 工作量最小;邮箱在 Web 端采集 | Web 付费墙编辑工具的第一个页面会收集邮箱并将其传递给 Adapty——适用于在应用内入口上线之前通过点击推广活动进入的用户。 | | **用户引导步骤** | 基于问答的用户引导(健身、营养、教育) | 在用户引导的第 2–3 步放置邮箱输入。用价值传递的方式进行引导(例如"我们将通过邮件发送您的个性化计划"),并避免让该步骤可跳过。 | | **Adapty Mail API** | 从服务器发送邮件,无需 Adapty SDK | 将用户画像发送到 Adapty Mail API 的 [保存用户画像](api-mail/operations/saveProfile) 端点。请参阅[通过 Adapty Mail API 发送邮件和交易](mail-send-data-via-api)。 | ## 限制条件 \{#limitations\} - **匿名用户**:没有稳定 `customer_user_id` 的用户无法接收推广活动。请在用户创建账户或登录时对其进行识别——从那时起,他们提供的任何邮箱都会与其 Adapty 用户画像进行匹配。 - **没有邮箱的用户**:没有邮箱的用户画像会被排除在推广活动之外,也不会出现在推广活动分析数据中。一旦他们提供了邮箱,便可参与后续推广活动。 --- # File: mail-send-data-via-api --- --- title: "通过 Adapty Mail API 发送邮件和交易数据" description: "无需 Adapty SDK,直接从服务器向 Adapty Mail 发送用户画像和交易数据。" --- Adapty Mail API 让你无需通过 Adapty SDK 中转,直接从服务器向 Adapty Mail 发送用户画像和交易数据。以下场景适合使用它: - 在 Adapty Mail 中尚未有用户列表时添加订阅者。 - 复用来自其他应用的订阅者列表。 - 以你的后端作为数据源,通过服务器到服务器的方式向 Adapty Mail 推送数据。 :::note **用 API 还是 SDK?** 大多数应用通过 Adapty SDK 向 Adapty Mail 发送数据,SDK 会自动收集邮箱和购买信息。如果你的应用没有集成 Adapty SDK、数据已存储在服务器上,或者需要从其他来源导入订阅者,请选择 API 方式。 ::: ## 开始之前 \{#before-you-start\} :::warning 在发送数据之前,请先完成 Adapty Mail 的配置——包括创建营销活动、市场细分(如有需要)、网页付费墙以及已上线的流程。Adapty Mail 仅向配置完成后创建的用户画像发送邮件;此前已发送的用户画像不会收到任何邮件。请先参阅[Adapty Mail 入门指南](mail-get-started)完成配置,再回到此处继续操作。 ::: 你还需要准备好 API 密钥和基础 URL: - **Secret API key(密钥)**:在 Adapty Mail 中,进入 **Settings** 并复制你的 Secret API key。该密钥与项目绑定,API 通过它识别数据所属的项目。 - **Base URL**:所有请求均发送至 `https://api-mail.adapty.io`。 - **Authentication(身份验证)**:在 **Authorization** 请求头中以 `Bearer {your_secret_api_key}` 的格式传入密钥。 :::important 在收集用户邮箱并将其发送至 Adapty Mail 之前,请务必获得用户的明确授权。您有责任遵守 GDPR、CAN-SPAM 及所在市场的其他相关法规。 ::: ## 发送用户画像 \{#send-user-profiles\} 用户画像包含用户的邮箱和属性。要创建或更新用户画像,请向 `/api/v1/profile/save/` 发送 POST 请求。 以下三个字段为必填项: - 由你的应用或后端维护的稳定 `external_profile_id` - Adapty Mail 用于发送营销活动邮件的 `email` - `external_created_at` —— 用户创建时间,可用于市场细分 :::important 请始终传入稳定的 `external_profile_id`,不要使用匿名 ID 或每次安装时生成的值。Adapty Mail 依靠它将邮件、点击和购买行为关联到同一个用户画像。 ::: ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "external_profile_id": "user_12345", "external_created_at": "2026-06-01T10:30:00Z", "email": "jane@example.com", "country": "US", "custom_attributes": { "plan": "trial" } }' ``` 请参阅 [保存用户画像](api-mail/operations/saveProfile) 参考文档,了解所有可用字段。 ## 发送交易事件 \{#send-transaction-events\} :::note 拥有邮箱的用户画像即可进入 **never purchased** 流程。其他所有流程还需要交易事件。 ::: 除 **never purchased** 之外的所有流程都依赖购买历史。在处理购买、续订和取消订阅时,同步发送用户画像的交易事件,这样 Adapty Mail 才能将其归入正确的流程。交易事件同时也支持收入归因。只有在你仅运行 **never purchased** 营销活动时,才可以跳过这一步。 要记录一笔交易,请向 `/api/v1/profile/transaction-event/save/` 发送 POST 请求。使用与用户画像相同的 `external_profile_id`,这样 Adapty Mail 才能将该交易关联到正确的用户。 ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/transaction-event/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "event_type": "subscription_started", "event_id": "evt_abc123", "event_datetime": "2026-06-10T14:20:05Z", "external_profile_id": "user_12345", "store": "app_store", "store_product_id": "premium_monthly", "store_transaction_id": "1000000123456789", "store_original_transaction_id": "1000000123456789", "purchased_at": "2026-06-10T14:20:00Z", "originally_purchased_at": "2026-06-10T14:20:00Z", "price_usd": "9.99" }' ``` 请参阅 [保存交易事件](api-mail/operations/saveTransactionEvent) 参考文档,了解所有可用字段。 ### 将事件映射到流程 \{#map-your-events-to-flows\} 发送与实际发生情况对应的 `event_type`。Adapty Mail 会根据用户画像的事件历史推断其当前状态,并将其路由到匹配的流程。 | `event_type` | 发送时机 | 流程 | | --- | --- | --- | | `subscription_started` | 用户开始新订阅。 | 活跃 — 无再触达流程 | | `subscription_renewed` | 订阅自动续期。 | 活跃 — 无再触达流程 | | `subscription_renewal_reactivated` | 用户重新开启自动续订。 | 活跃 — 无再触达流程 | | `non_subscription_purchase` | 用户完成一次性购买。 | 活跃 — 无再触达流程 | | `subscription_renewal_cancelled` | 用户关闭自动续订(订阅在到期前仍有效)。 | 续订已取消 | | `billing_issue_detected` | 续订付款失败。 | 账单问题 | | `entered_grace_period` | 付款失败,但用户仍处于宽限期内。 | 账单问题 | | `subscription_expired` | 订阅到期,访问权限终止。 | 已过期 | | `subscription_refunded` | 订阅购买已退款。 | 已退款 | | `non_subscription_purchase_refunded` | 一次性购买已退款。 | 已退款 | --- # File: mail-brand --- --- title: "Adapty Mail 中的品牌信息" description: "查看并完善驱动邮件生成和网页付费墙的品牌档案。" --- **品牌**是 Adapty Mail 基于你的应用公开来源自动构建的综合档案,包括 App Store 或 Google Play 页面、落地页、服务条款与隐私政策页面以及社交主页。它决定了邮件文案、语气风格、视觉呈现、网页付费墙内容以及演示效果。每个项目对应一个品牌档案,所有下游功能均读取同一份档案。 从 Adapty Mail 侧边栏的 **Brand** 条目打开品牌。 - **如果你是通过 Adapty 注册 Adapty Mail 的**:你的品牌已根据 Adapty 项目的 Store URL 自动创建。品牌页面会直接打开完整的资料,你可以随时查看和调整。 - **如果你是独立注册的**:品牌页面会打开设置界面——请参阅[从头开始设置](#set-up-from-scratch)。 ## 品牌资料包含哪些内容 \{#whats-in-a-brand-profile\} 一个品牌共有 13 个部分。Adapty Mail 会直接将这些内容用于生成邮件和网页付费墙。 - **身份标识**:应用名称、简短描述、宣传语。 - **视觉标识**:配色方案(主色、背景色、辅助色、强调色、文字色、行动按钮色)、排版、风格备注、Logo 链接。 - **目标受众**:人口统计信息、语言、市场。 - **功能特性**:每项功能包含名称、核心价值,以及可选的详细描述。 - **产品洞察**:独特卖点、观察发现、常见异议及应对话术、标签。 - **品牌调性**:语气、正式程度、词汇风格、情感基调。 - **语音样本**:示例标题、示例行动号召语,以及邮件生成时使用的语气预设。 - **社会认同**:用户数量、评分、评分数量、媒体提及、核心数据指标。 - **社交媒体链接**:Twitter、Instagram、TikTok、YouTube、Facebook、LinkedIn。 - **法律链接**:服务条款链接、隐私政策链接、客服邮箱。 - **用户评价**:从应用商店抓取的用户评论——内容、作者、评分、来源。 - **用户痛点**:基于评论或社交信号提炼出的痛点描述。 - **常见问题**:用于邮件内容和网页付费墙模块的问答内容。 ## 手动编辑某个区块 \{#edit-a-section-manually\} 品牌视图中的每个区块都有一个 **Edit** 按钮,点击后即可打开该区块的内联编辑器。 1. 点击要修改的区块上的 **Edit**。 2. 直接在原位更新字段。Adapty Mail 会将未保存的修改记录为草稿。 3. 点击页面顶部草稿横幅中的 **Save** 以应用更改,或点击 **Discard** 放弃修改。 每次只能打开一个部分。如果尝试打开第二个部分,第一个部分会提示你完成或取消当前操作。 :::important 当数据源正在处理时,编辑会暂停——处于完成阶段的数据源可能会覆盖你正在进行的更改。处理开始时,所有已打开的编辑器会自动关闭,草稿横幅中的 **Save** 按钮在处理完成之前保持禁用状态。 ::: ## 使用 AI 优化 \{#refine-with-ai\} 右下角的 **Refine with AI** 按钮会在品牌面板旁边打开一个对话面板。用自然语言描述你想做的修改,AI 会给出优化草稿,你可以选择保存或放弃。 1. 点击 **Refine with AI**。 2. 描述你的修改需求。对话范围仅限于品牌编辑——它不会回答通用问题,也不会重写品牌资料以外的内容。 3. 在主视图中查看 AI 提出的草稿。Adapty Mail 会高亮显示有变动的部分。 4. 点击草稿横幅中的 **Save** 应用修改,或点击 **Discard** 保留已保存的品牌设置。 常用提示词: - "让语气更加活泼有趣。" - "添加离线模式功能。" - "强化独特价值主张。" - "针对美国市场重新撰写受众描述。" ## 添加更多来源 \{#add-more-sources\} 商店来源为用户画像提供基础数据。其他来源类型则用于完善特定部分——落地页提升视觉形象和文案,条款与隐私页面改善法律链接,社交主页丰富语气风格和社会认同素材。 在品牌视图中,**Sources** 面板列出了所有来源,下方有一个 **Add** 表单。选择类型,粘贴 URL,然后点击 **Add**。 - **App Store**:`https://apps.apple.com/...` - **Google Play**:`https://play.google.com/store/apps/details?id=...` - **落地页**:您的营销网站,例如 `https://yourapp.com`。 - **条款/隐私**:指向您的条款或隐私页面的直接链接。 - **社交主页**:Twitter、Instagram、TikTok、YouTube、Facebook 或 LinkedIn 的链接。 每种类型只能添加一个来源。某种类型的来源一旦处于处理中或已完成状态,该类型的选择器将被禁用。如需替换来源,请删除该品牌并重新接入——单个来源无法单独移除。 ## 从头开始设置 \{#set-up-from-scratch\} 如果你的项目还没有品牌(通常是独立注册 Adapty Mail 的新用户),**Brand** 页面会打开一个设置向导。渠道来源——App Store 或 Google Play——是基础配置,其他来源类型可在之后添加。 1. 选择渠道(**App Store** 或 **Google Play**),然后粘贴应用详情页 URL。 2. 点击 **Build my brand**。Adapty Mail 会抓取页面、解析评论并推断您的品牌风格。处理过程通常不超过一分钟。 3. 处理完成后,品牌视图将打开,所有 13 个部分均已填充完毕。 如果来源加载失败(URL 无效、页面无法访问或解析器报错),页面会显示错误信息和 **Try again** 按钮。请修正 URL 后重新提交。 ## 品牌的使用范围 \{#where-the-brand-is-used\} 品牌信息会被所有需要了解应用风格与外观的下游功能所引用: - **邮件生成**:文案、语气、视觉效果、发件人信息和头图均从品牌中读取。详见[创建营销活动](mail-create-campaign)。 - **网页版付费墙编辑工具**:品牌是生成付费墙的前提条件——若未完成 `brand_saved`,生成功能将被锁定。详见[设置结账](mail-checkout)。 - **用户引导**:当品牌信息存在时,用户引导清单中的 **Set up brand** 步骤将自动标记为已完成。 ## 删除品牌 \{#delete-a-brand\} **删除品牌**操作位于品牌视图底部的 **Danger zone** 区域。 1. 在 Danger zone 区域点击 **Delete**。 2. 在弹出的对话框中确认操作。 删除后,品牌资料及所有来源数据将被移除,且无法撤销。如需恢复,请在起始页面粘贴 App Store 或 Google Play 的链接,重新完成品牌引导流程。 :::warning 已有的营销活动仍会保留生成时的品牌快照,但在重新完成品牌引导之前,新的营销活动和付费墙生成功能将被禁用。 ::: ## 限制 \{#limitations\} - **一个项目对应一个品牌**:每个 Adapty Mail 项目只对应一个品牌。如需针对其他应用,请新建一个项目。 - **每种类型只能有一个来源**:一个品牌最多可添加一个 App Store、一个 Google Play、一个落地页、一个条款/隐私政策和一个社交主页来源。 - **不支持单独删除来源**:无法从界面中单独删除某个来源。如需替换来源,请使用 **Delete brand**。 - **处理期间暂停编辑**:当某个来源处于 `pending` 或 `processing` 状态时,所有分区编辑、精修对话保存及品牌删除操作均会被锁定。 - **失败的来源仍会保留显示**:状态为 `failed` 的来源会继续显示在面板中,并附带错误信息。重新提交同类型来源即可重试——新任务调度成功后,失败条目将被替换。 --- # File: mail-sending-domain --- --- title: "为 Adapty Mail 设置发送域名" description: "添加 DNS 记录、验证您的域名并了解预热流程,以便 Adapty Mail 能够代表您发送邮件。" --- Adapty Mail 使用你自己的域名发送营销活动,而非共享地址,因此发件人信誉始终由你掌控。只需配置一次,所有营销活动都会使用同一个已验证的域名。如需最简化的配置步骤,请参阅[开始使用 Adapty Mail](mail-get-started#2-set-up-your-sending-domain) 中的域名配置部分。本文将介绍完整的配置流程、验证机制,以及自动预热行为。 ## 前提条件 \{#requirements\} - **顶级域名**:请提交根域名(例如 `yourapp.com`),而非子域名。输入 `app.yourapp.com` 等形式会在验证时被拒绝。 - **有效的 NS 记录**:域名必须能正常解析。Adapty Mail 在设置过程中会执行 DNS 查询,无效 NS 记录的域名将被拒绝。 - **每个 Adapty 项目只能绑定一个域名**:域名不能在多个项目间共享。如果该域名已被任何项目注册(无论是您自己的还是他人的),设置将会失败。 ## 设置发件域名 \{#set-up-your-sending-domain\} 设置向导共分三个步骤:输入域名、确认生成的子域名、添加 DNS 记录。所有操作均在 **Settings → Email Domains** 中完成。 1. **输入你的域名。** 在 **Domain** 字段中输入顶级域名,然后点击 **Preview**。Adapty Mail 会验证格式(ASCII、两段标签、首尾无连字符、顶级域名至少 2 个字符),并检查 DNS 能否正常解析。 2. **确认子域名。** Adapty Mail 会生成两个带固定前缀的发送子域名——`mail.yourapp.com` 和 `email.yourapp.com`——每个都有独立的 SES 身份标识。同时还会在每个子域名下创建一个 Mail-From 子域名(`hello.mail.yourapp.com` 和 `hello.email.yourapp.com`)。确认无误后点击 **Confirm**。 3. **添加 DNS 记录。** 最后一个页面会列出所有需要添加的记录——共 10 条,每个发送子域名各 5 条,加上根域上的一条可选 DMARC 记录。点击 **Download CSV** 导出完整列表,或逐条复制到您的域名注册商处。记录添加完成后,点击 **Done**。 <Details> <summary>DNS 记录参考</summary> 对每个发送子域名(`mail.yourapp.com` 和 `email.yourapp.com`),添加以下记录: **DKIM — 3 条 CNAME 记录。** 加密签名,用于证明邮件在传输过程中未被篡改。 | 字段 | 格式 | | ----- | ----------------------------------- | | 类型 | CNAME | | 名称 | `{token}._domainkey.{subdomain}` | | 值 | `{token}.dkim.amazonses.com` | **Mail-From — 1 条 MX 记录。** 用于处理退回邮件。 | 字段 | 格式 | | -------- | -------------------------------------------------------- | | Type | MX | | Name | `hello.{subdomain}`(例如 `hello.mail.yourapp.com`) | | Priority | `10` | | Value | `feedback-smtp.{region}.amazonses.com` | **SPF — 1 条 TXT 记录。** 授权 Adapty 代表您发送邮件。 | 字段 | 格式 | | ---- | -------------------------------------- | | Type | TXT | | Name | `hello.{subdomain}` | | Value | `"v=spf1 include:amazonses.com ~all"` | 在根域名上,添加可选的 DMARC 记录: | 字段 | 格式 | | ---- | --------------------- | | Type | TXT | | Name | `_dmarc.{domain}` | | Value | `v=DMARC1; p=reject` | 令牌、区域及其他所有值均在设置时由 AWS SES 提供。请始终从 Adapty Mail 的 DNS 记录页面复制,而非参考本文档中的示例值。 </Details> ## 验证机制说明 \{#how-verification-works\} DNS 记录配置完成后,Adapty Mail 会自动轮询 DNS,你也可以手动触发验证。 - **自动轮询**:提交后 5 分钟开始轮询,之后每轮间隔翻倍——10 分钟、20 分钟、40 分钟——最长不超过 60 分钟。轮询持续进行,直到找到记录或 7 天窗口关闭为止。 - **手动检查**:点击 **Check Verification** 可立即触发一次检查。两次手动检查之间有 60 秒冷却时间——触发过快会返回 *"Verification check is on cooldown."* - **状态说明**:每个子域的 DKIM 和 Mail-From 状态独立跟踪,分别为 **Pending**、**Success** 或 **Failed**。只有全部四项状态均显示 **Success**,该域名才视为完全验证通过。 - **7 天截止期限**:如果验证在 7 天内未完成,身份标识将被标记为 **Failed**。你的 DNS 记录仍保留在域名注册商处——在 **Settings → Email Domains** 中重新输入域名,即可开启新的验证窗口。 - **验证完成后**:若日后删除或修改 DNS 记录,AWS SES 最终会降级该身份标识。只要你计划继续发送邮件,就请保留这些记录。 - **DNS 传播**:通常只需几分钟,极少数情况下最长可达 48 小时。 ## 域名预热 \{#domain-warm-up\} 新域名在 Gmail、Yahoo 等邮件服务商眼中没有任何信誉记录,因此从全新域名大量发送邮件很容易被归入垃圾邮件。Adapty Mail 会自动处理预热流程,通过 14 个阶段逐步提升每日发送上限,无需任何手动配置。 ### 分级机制说明 \{#how-tiers-work\} 您的域名从**第 1 级**(每天 200 封)开始,当送达率指标保持健康时会自动晋级。如果退信率上升或投诉率攀高,晋级将暂停,并可能在声誉恢复之前出现降级。 | 等级 | 每日上限 | | ---- | ----------- | | 1 | 200 | | 2 | 400 | | 3 | 800 | | 4 | 1,500 | | 5 | 2,500 | | 6 | 4,000 | | 7 | 6,000 | | 8 | 8,000 | | 9 | 10,000 | | 10 | 13,000 | | 11 | 16,000 | | 12 | 20,000 | | 13 | 25,000 | | 14 | 30,000 | 您当前的等级和每日上限显示在 **Settings → Email Domains** 中。 ### 目标受众规模对发送的影响 \{#impact-on-launch-by-audience-size\} | 目标受众规模 | 发送效果 | | ------------ | -------- | | 200 人以下 | 第一天即可触达全部受众 | | 200–2,000 人 | 分多天逐步发送 | | 2,000 人以上 | 分 1–2 周逐步发送 | :::tip DNS 验证完成后请立即启动首个活动。越早开始发送,域名越快晋升至更高层级,从而达到更高的每日发送上限。 ::: ## 限制 \{#limitations\} - **每个项目只能有一个域名**:每个 Adapty 项目只能配置一个发送域名。如需更换域名,请联系支持团队——看板中没有"更改域名"的操作入口。 - **跨项目唯一性**:已注册到其他项目的域名无法重复使用。如果看到 *"Domain is already registered to another project"* 的提示,请换一个域名或联系支持团队。 - **已验证的域名无法删除**:一旦任意子域名状态变为 **Success**,看板将禁止删除操作。待验证的域名可以删除,但仍需手动前往域名注册商处移除相应的 DNS 记录。 - **子域名前缀固定**:`mail.`、`email.` 以及 `hello.` 这个 Mail-From 前缀均为硬编码,无法自定义。如果这些子域名在你的 DNS 中已被占用,配置时将产生冲突。 - **仅支持顶级域名**:子域名输入、末尾带点的格式以及单标签主机名均不被接受。 - **不支持国际化域名**:不支持 Punycode 和 IDN,域名必须为纯 ASCII 字符。 ## 故障排查 \{#troubleshooting\} | 问题 | 解决方案 | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | "Enter a valid domain (e.g. example.com)" | 检查输入:仅限顶级域名、仅限 ASCII 字符、顶级域名至少 2 个字符,且不能以连字符开头或结尾。 | | "Domain does not have valid DNS records" | 顶级域名本身必须能够解析。请在重试前确认 NS 记录已生效。 | | "Domain is already registered to another project" | 请选择其他域名,或如果您认为该注册有误,请联系客服支持。 | | "Verification check is on cooldown" | 手动检查之间请等待 60 秒。后台自动轮询仍会继续运行。 | | 验证卡在待处理状态 | 检查 DNS 记录是否完全匹配——不要有多余的点号,CNAME 目标需正确。DNS 传播最长可能需要 48 小时。 | | "Cannot delete domain: one or more identities have been successfully verified" | 已验证的域名无法从看板中删除。如需协助,请联系客服支持。 | | 邮件进入垃圾邮件 | 确认已发布 DMARC 记录。新域名需要预热时间——请参阅[域名预热](#domain-warm-up)。 | | 退信率偏高 | 确认您的收件人列表中包含有效且已授权订阅的地址。退信率过高会导致等级提升速度减慢或暂停。 | --- # File: mail-checkout --- --- title: "为 Adapty Mail 配置结账流程" description: "搭建网页付费墙并连接支付提供商,为你的邮件营销活动提供个性化的网页结账体验。" --- Adapty Mail 发送的每封邮件都包含一个专属于该收件人的唯一结账链接。用户点击后,会进入一个网页结账漏斗,该漏斗会通过用户画像识别用户身份、展示你的优惠内容并完成支付处理。结账漏斗保存在 Adapty Mail 的 **Web Paywalls** 中,通过内置的 **web paywall builder** 进行编辑。 ## 前提条件 \{#requirements\} - 需要一个已配置好订阅产品的网页支付服务商。**Generate with AI** 仅支持 Stripe,并在编辑工具中直接连接。**Use your own hosted paywall** 可接受任意服务商——Stripe、Paddle、PayPal 或其他——因为支付流程由你自行处理。 您无需为网页付费墙编辑器单独注册账户。它已内置于 Adapty Mail 中:首次登录时系统会自动为您创建工作区,并使用您的 Adapty 凭据登录编辑器。这与您在主 Adapty 看板付费墙页面上配置的任何网页付费墙无关——Adapty Mail 的网页付费墙是独立的实体,完全由 Adapty Mail 管理。 ## 设置结账流程 \{#set-up-your-checkout-funnel\} 在 Adapty Mail 中,前往 **Web Paywalls → Create**。你有两种方式: - **Generate with AI**:Adapty Mail 内置的网页付费墙编辑工具会自动为你生成漏斗。仅支持 Stripe——如需使用 Paddle 或 PayPal,请选择第二种方式。 - **Use your own hosted paywall**:接入你自己托管的付费墙,支持任意支付服务商。 ### 使用 AI 生成 \{#generate-with-ai\} 创建页面顶部会显示一个**前提条件**面板,其中包含内联操作按钮,引导你完成每个前提条件——品牌准备就绪、编辑工具登录、Stripe 连接、产品配置,以及最终的审核与发布步骤。逐一完成后,面板会随每个步骤的完成自动刷新。 所有前提条件变为绿色后,点击 **Generate** 打开生成对话框。需要做两个选择: - **Environment**:选择 **Production** 或 **Sandbox**。Sandbox 使用你的 Stripe 测试模式产品,适合开发和本地环境,其账户与生产环境完全隔离,测试交易不会影响线上数据。 - **Plans**:最多选择 **3 个 Stripe 方案**。每个方案是产品与价格的组合,付费墙会将这些方案作为结账时的可选项呈现给用户。如果选择的方案少于 3 个,付费墙只会展示你已选的方案。 点击 **Generate** 开始构建。构建完成后,在编辑工具中打开预览结果并发布,然后点击 **Save**。 :::important 付费墙必须先发布,才能处理结账流量。未发布的付费墙在用户点击邮件结账链接时会返回错误。 ::: 有关付费墙编辑工具中支付提供商的详细配置(Stripe 账号、测试与正式模式、产品设置),请参阅[网页付费墙配置](web-paywall-configuration)。 ### 使用自己托管的付费墙 \{#use-your-own-hosted-paywall\} 1. 在创建页面,选择 **Enter URL manually**。 2. 粘贴你托管的付费墙 URL。该 URL 必须包含 `{email}` 和 `{external_profile_id}` 占位符作为查询参数——Adapty Mail 会为每位收件人填充这些参数,以便页面识别访客身份。示例: ``` https://example.com/paywall?email={email}&profile={external_profile_id} ``` 3. 保存。 此方式适用于任何支付提供商——Adapty Mail 仅负责跳转和参数替换,支付与个性化逻辑完全由你自己处理。 ## 结账页面的外观 \{#what-the-checkout-looks-like\} 当用户点击结账链接时,会进入**主转化页面**。完成支付操作后,将看到**支付成功**或**支付失败**页面——每次尝试只显示其中一个。 **主转化页面** 一个完整的销售展示页面。AI 会为每个板块自动生成文案和图片: | 版块 | AI 生成内容 | |---|---| | 标题 | 醒目、突出利益点的加粗标题 | | 副标题 | 辅助说明价值主张 | | 优惠徽章 | 紧迫感徽章(不生造价格——使用模糊的促销文案) | | CTA 按钮 | 行动导向文案,2–5 个词 | | 核心优势 | 3–6 张带表情符号的优势卡片 | | 功能特性 | 3–8 条功能说明,含标题和副标题 | | 订阅方案 | 方案选择标题及优惠倒计时文案 | | 社会证明 | 社区口碑文案及 3–5 条真实感用户评价 | | 常见问题 | 3–6 个常见问题及解答 | | 保障承诺 | 退款或满意度保障文案 | **付款成功** 附带后续步骤说明的祝贺消息,以及一张 AI 生成的图片。 **付款失败** 友好提示用户重试的消息。结账状态保持不变。 ## 个性化的工作原理 \{#how-personalization-works\} 每封邮件都包含一个唯一的结账 URL,其中嵌入了收件人的 `customer_user_id` 和邮箱地址作为参数: ``` https://your-funnel.com/?cid={{customer_user_id}}&email={{email}} ``` Adapty 在发送每封邮件时会自动生成这些 URL,无需在网页付费墙编辑工具中进行任何配置。用户点击后,编辑工具会读取参数来识别该用户。购买完成后,Adapty 会将收入归因到促成本次转化的具体邮件。相关数据将显示在[营销活动分析](mail-analytics)中。 ## 故障排查 \{#troubleshooting\} | 问题 | 解决方案 | |---|---| | 结账链接无法打开 | 确认付费墙已在 Web 付费墙编辑工具中发布 | | 结账时未识别用户 | 确认在发送邮件之前已使用正确的用户 ID 调用了 `Adapty.identify()` | | 购买未归因到邮件 | 检查结账 URL 中是否包含 `cid` 参数——如果参数缺失,请联系支持团队 | --- # File: mail-email-campaigns --- --- title: "Adapty Mail 中的邮件营销活动" description: "设计多封邮件序列,选择合适的语调,精准定位目标用户。" --- Adapty Mail 中的营销活动是一套完整的多封邮件序列——包括文案、设计、主视觉图片和发送间隔——一次性为你的应用生成。营销活动本身不会直接发送:它会保存为**草稿**,只有在你将其绑定到某个[流程](mail-create-flow)后才会开始投递,而流程负责将其与触发条件和目标受众关联起来。 使用以下指南创建营销活动、选择合适的语调,并精准定位目标用户。 <CustomDocCardList ids={['mail-create-campaign', 'mail-suppression']} /> --- # File: mail-create-campaign --- --- title: "在 Adapty Mail 中创建营销活动" description: "根据您应用的应用商店元数据生成完整的电子邮件序列,并在附加到流程之前进行优化。" --- Adapty Mail 可根据您应用的应用商店元数据,自动生成完整的电子邮件序列——包括文案、设计、主图、主题行和发送延迟。无需任何文案撰写或设计工作。营销活动将保存为草稿,仅在您将其附加到[流程](mail-create-flow)后才会开始发送。 ## 开始之前 \{#before-you-start\} - **已保存的网页付费墙**:每个营销活动都必须关联一个网页付费墙,否则后端会拒绝创建。如果还没有,请参阅[创建网页付费墙](mail-get-started#4-create-a-web-paywall)。 - **在 Settings 中填写 App Store 或 Google Play 链接**:AI 会从该链接读取应用元数据(名称、类别、描述、截图),以便定制邮件序列。如果尚未设置,请前往 **Settings → App metadata** 添加。 ## 1. 生成序列 \{#1-generate-the-sequence\} 1. 在 Adapty Mail 中,进入 **Campaigns** 并点击 **Create**。 2. 设置推广活动名称。 3. 在 **Web paywall** 下拉菜单中,选择你希望邮件链接到的网页付费墙。 4. 点击 **Generate emails**。 5. 填写生成对话框: - **Tone**:从列表中选择。选项是根据你的应用类别专门生成的——不同的应用会看到不同的选项。你的选择会影响每封邮件的主题行、标题、正文和 CTA,但不会影响版面布局或主图。 - **Language**:选择邮件语言。 - **Custom prompt**(可选):自由填写,最多 2,000 个字符。可用于说明促销活动、特殊场合、受众特点、必须包含的内容,或预设选项无法涵盖的额外语气要求。 - **Number of emails**:默认情况下,AI 会根据最佳实践和你的应用情况自动决定数量。如需手动设置,点击 **Set number manually** 并选择一个值(**1–15**,默认为 **4**)。 一旦点击 **Generate**,语气就会锁定为该营销活动的设置。如需尝试其他语气,请新建一个营销活动——每次生成都可以产生不同的选项组合。 6. 点击 **Generate**。生成通常需要几分钟。如果后端在 5 分钟内无法完成,请求会超时——遇到这种情况请重试。 ## 2. 审查与优化 \{#review-and-refine\} 生成完成后,完整的序列将以预览形式展示。每封邮件都包含以下内容: - **主题行变体**:每封邮件提供三个主题选项。Adapty Mail 在投递时进行测试,并持续发送表现最佳的那个——详见 [A/B 测试](mail-ab-testing)。 - **标题、正文和 CTA**:主要内容区块。 - **主视觉图片**:根据邮件内容和品牌风格自动生成的配图。 - **布局与发送间隔**:邮件的排版方式,以及距上一封邮件的发送延迟时间。 预览标题栏右上角有一个**主题切换**按钮(自动、浅色、深色),用于控制预览窗格的渲染方式,不影响生成的内容。利用它可以在不重新生成的情况下查看每封邮件在各种配色方案下的显示效果。 你可以: - **重新生成单封邮件**:AI 会重新撰写文案并为单封邮件生成新的主图。版式位置、发送时间和整体设计规范(颜色、字体、深色模式)保持不变,仅更新该封邮件的内容。 - **直接编辑 HTML**:打开 HTML 代码编辑器,对 AI 未能准确处理的细节进行精细调整。 :::note 邮件会自动适配。多列布局在屏幕宽度低于 620 px 时会折叠为单列,所有布局均经过以下客户端测试:Gmail(网页版和移动版)、Apple Mail(macOS 和 iOS)、Outlook 桌面版、Yahoo Mail 和三星邮件——亮色和暗色模式均已覆盖。 ::: ## 3. 保存为草稿 \{#save-as-a-draft\} 点击 **Create** 将活动保存为草稿。此时不会发送任何邮件——活动编辑器没有单独的"发布"或"启动"操作。 活动的状态反映其是否关联到正在运行的流程: - **draft**:未关联到任何流程。 - **live**:已关联到流程,当前正在为用户路由。 - **inactive**:曾经关联过,但流程的 A/B 测试包装器已结束。 - **archived**:已从看板中删除。 :::important 草稿活动不会自行发送。要开始发送邮件,你需要: - 将营销活动直接关联到[流程](mail-create-flow),或 - 将其加入 [A/B 测试](mail-ab-testing),再将 A/B 测试关联到流程。 在此之前,营销活动将保持 `draft` 状态,不会触达任何收件人。 ::: --- # File: mail-suppression --- --- title: "Adapty Mail 中的退订与抑制" description: "Adapty Mail 停止向用户发送邮件的方式——通过退订、SES 退信、投诉以及停止条件机制。" --- Adapty Mail 在以下两种不同情况下会停止向用户发送邮件: - **抑制**:用户将被排除在该项目所有后续推送之外(包括取消订阅、退信、投诉、拒绝或限流)。 - **停止条件**:用户当前的序列因已转化而取消。用户不会被抑制,仍可参与其他营销活动。 两种机制均按项目生效。在一个 Adapty 项目中设置的抑制不会影响其他项目。 ## 取消订阅 \{#unsubscribe\} Adapty Mail 发出的每封邮件页脚都包含一个取消订阅链接。 1. 用户点击该链接,Adapty Mail 会打开一个确认页面。 2. 用户确认后,后端将该用户画像标记为 `suppression_reason = 'unsubscribe'`,取消剩余的邮件序列,并将该用户画像从项目后续的邮件发送中排除。 取消订阅 URL 中的令牌包含 `profile_id` 和 `scheduled_email_id`,因此无需登录即可完成操作。 :::note Adapty Mail 在发送邮件时,会同时附带 `List-Unsubscribe: <URL>, <mailto:>` 请求头以及 `List-Unsubscribe-Post: List-Unsubscribe=One-Click`。Gmail 和 Yahoo 要求批量发件人遵循此规范(RFC 8058)。支持该请求头的邮件客户端会在收件箱中直接显示一键退订按钮,无需跳转确认页面。 ::: ## 自动抑制 \{#automatic-suppression\} Adapty Mail 通过 SNS 监听 AWS SES 的投递事件,并在发生以下任意情况时立即抑制该用户: | 事件 | 原因代码 | 含义 | | --------- | ----------- | ------------------------------------------------------------------------------- | | Bounce | `bounce` | 电子邮件地址无效、邮箱已满或域名不存在。 | | Complaint | `complaint` | 用户将该邮件标记为垃圾邮件。 | | Reject | `reject` | SES 在发送前拒绝了该邮件。 | | Throttle | `throttle` | 发送速率超出了域名安全限制。 | 对于每个事件,结果都相同:用户被加入抑制名单,剩余序列被取消,并在该项目中排除在未来发送之外。 :::important Adapty Mail **不区分**硬退信和软退信。任何退信——包括邮箱已满等临时情况——都会立即抑制该用户,不存在重试窗口。 ::: ## 停止条件 \{#stop-condition\} 当用户在序列进行中完成转化时,Adapty Mail 会以 `stop_condition` 为原因取消其剩余的待发邮件。转化是指用户的订阅状态变为 **Subscribed**,或一次性购买状态变为 **Purchased**。 停止条件与抑制不同: - **抑制**:将用户从该项目的所有后续发送中排除。 - **停止条件**:仅取消当前序列。用户仍可参与其他营销活动——例如,面向活跃订阅者的续订流程或赢回优惠流程。 停止条件取消与抑制一同显示在营销活动分析中。 ## 管理抑制名单 \{#managing-suppression\} Adapty Mail 目前没有用于查看或移除被抑制用户的看板界面。如需解除对某个用户画像的抑制——例如某人误将测试邮件标记为垃圾邮件——请联系 Adapty 客服支持。 ## Adapty Mail 负责的合规事项 \{#what-adapty-mail-handles-for-compliance\} Adapty Mail 内置以下合规功能: - **退订链接**:每封邮件页脚均包含退订链接,用户确认后立即生效。 - **List-Unsubscribe 标头**:每封邮件均附带该标头,支持在收件箱一键退订(RFC 8058)。 - **自动屏蔽**:在 SES 退回、投诉、拒绝和限流事件触发时自动生效。 以下部分由您负责: - **实际邮寄地址**:CAN-SPAM 法规要求在邮件页脚注明。Adapty Mail 不会自动插入,请在设计活动时手动添加。 - **明确的订阅同意**:在将用户邮箱传入 Adapty 之前,请先收集用户的明确授权。详见[收集用户邮箱](mail-collect-emails)。 - **GDPR 数据删除请求**:Adapty Mail 未提供"删除我的数据"接口。如有用户行使数据删除权,请联系 Adapty 支持团队协助处理。 --- # File: mail-flows --- --- title: "Adapty Mail 中的流程" description: "流程如何在正确的时机将营销活动路由给合适的用户——触发器、市场细分与优先级规则。" --- <CustomDocCardList ids={['mail-create-flow']} /> **流程**将已保存的营销活动转化为定时发送任务。它将触发器事件(用户的订阅状态)与市场细分(哪些用户符合条件)以及他们将收到的营销活动绑定在一起。每当匹配的事件触发时,Adapty Mail 就会评估所有流程——无需轮询、无需定时任务、无需手动启动。 ## 触发器 \{#triggers\} Adapty Mail 内置五个固定触发器,每个触发器在 **Flows** 下都有独立的流程视图: - **Never purchased**:已注册但尚未完成购买的用户。目标:激活用户并促成首次转化。试用用户不在此列——开始试用即视为拥有有效订阅。 - **Renewal cancelled**:已关闭自动续费但订阅仍有效的用户。涵盖已付费订阅者以及在转化前取消的试用用户。留存窗口最佳——他们仍有使用权限。如需差异化沟通,可通过市场细分过滤器区分付费用户和试用用户。 - **Billing issue**:付款失败——信用卡被拒或已过期,或续费未成功后的宽限期。目标:提供紧急、有帮助的提醒,而非销售推送。尽快帮助他们恢复——他们本来就有付款意愿。 - **Expired**:订阅已到期且访问权限已失效。涵盖付费订阅到期以及未转化即到期的试用。目标:赢回用户。市场细分过滤器可针对试用到期和付费到期用户分别定制文案。 - **Refunded**:购买后申请退款的用户。目标:了解问题所在,并提供更合适的方案。语气应保持谦逊和好奇,而非强硬的再销售。 触发器不可配置——你无法创建自定义触发器或扩展触发器列表。 ## All Users 市场细分 \{#the-all-users-segment\} Adapty Mail 内置一个 **All Users** 市场细分,没有任何过滤条件——项目中的所有用户均符合条件。它在流程中最常用作兜底行,为未被上方更具体市场细分匹配的任何用户提供服务。All Users 不可编辑或删除。完整说明请参阅[市场细分](mail-segments)。 ## 优先级 \{#priority\} 每个触发器视图中包含一个**市场细分 → 营销活动**(或市场细分 → A/B 测试)行的列表,按优先级排序。当用户触发事件时,Adapty Mail 将: 1. 从上到下遍历各行。 2. 在第一个市场细分匹配的行中发送营销活动。 3. 停止。不再为该用户评估后续行。 顺序很重要。将较宽泛的市场细分放在较精准的细分之上,会抢占本应匹配精准行的所有用户。 如需重新排序,可拖动任意行左侧的手柄——后端将根据保存的顺序重新分配优先级编号 1、2、3…… :::important 如果存在 **All Users** 行,则必须将其放在最后(最低优先级)。后端会拒绝 All Users 不在最后位置的保存操作——否则它会在更具体的市场细分有机会匹配之前吞噬所有用户。 ::: ## 内容类型 \{#content-types\} 每行可以发送单个营销活动或 A/B 测试: - **Campaign**:向所有匹配该市场细分的用户发送同一个营销活动。 - **A/B Test**:将两个或多个营销活动以可配置的权重组合,随机将用户分配到各实验变体,并追踪每个变体的数据图表。详见 [A/B 测试](mail-ab-testing)。 ## 生命周期 \{#lifecycle\} 流程行没有草稿状态。保存后该行即刻生效——从此时起,触发事件且符合市场细分条件的用户将被路由到其对应的营销活动。 - **创建行**:保存后立即开始发送。 - **编辑行**:更改将应用于此后触发事件的用户。已在序列中的用户将继续沿用之前的配置。 - **删除行**:新用户停止进入该序列。已在序列中的用户可能会继续收到其计划中的邮件——系统不会自动取消。 A/B 测试行遵循自己的生命周期(**草稿 → 上线 → 完成**),与行本身独立控制。详见 [A/B 测试](mail-ab-testing)。 --- # File: mail-create-flow --- --- title: "在 Adapty Mail 中创建和管理流程行" description: "在流程中添加、重新排序、编辑和删除行,以便向用户推送营销活动。" --- 每个[流程](mail-flows)都是一个固定触发器视图内按优先级排列的**市场细分 → 营销活动**行列表。本指南介绍如何添加、重新排序、编辑和删除这些行。关于触发器、优先级和内容类型的概念说明,请参阅[流程](mail-flows)。 ## 添加一行 \{#add-a-row\} 1. 在 Adapty Mail 中,进入 **Flows** 并打开要配置的触发器。 2. 点击 **Create** 打开对话框。 3. 在弹窗中: - **Segment**:选择一个市场细分,或选择 **All Users** 作为兜底。 - **Content type**:**Campaign** 表示单个活动,**A/B Test** 用于对比多个活动——详见 [A/B 测试](mail-ab-testing)。 - **Campaign**:选择要发送的活动。 4. 点击 **Save**。 该行立即生效。触发条件满足且匹配该市场细分的用户,从此刻起将开始收到对应活动的邮件。 ## 重新排列行顺序 \{#reorder-rows\} 拖动行左侧的拖拽手柄即可调整优先级。Adapty Mail 会根据保存时的顺序自动分配 `priority: 1, 2, 3…`。 **All Users** 行必须保持在最后一位——保存时系统会阻止将其拖到其他行的上方。 ## 编辑行 \{#edit-a-row\} 点击某一行上的 **Change content**,即可重新打开对话框,其中已预填当前值。你可以修改市场细分、内容类型和活动,然后点击 **Save** 应用更改。 使用 A/B 测试的行,只能在测试处于 **draft** 状态时进行编辑。测试启动后,其内容将被锁定,直到你结束该测试为止。 ## 删除行 \{#delete-a-row\} 打开行的操作菜单,点击 **Delete**。删除操作不会弹出确认对话框——行会立即被移除。 - **Campaign 行**:随时可以删除。 - **包含进行中 A/B 测试的行**:无法删除。请先通过 **Finish A/B test** 完成测试,再删除该行。 :::note 删除行后,新用户将不再进入该序列。已经在序列中的用户可能会继续收到已计划的邮件——系统不会自动取消。 ::: --- # File: mail-segments --- --- title: "Adapty Mail 中的市场细分" description: "根据用户画像和购买数据创建可复用的受众切片,用于定向流程和 A/B 测试。" --- **市场细分**是一种可复用的受众切片。你在 **Segments** 中定义一次,即可在流程和 A/B 测试中引用。市场细分是过滤条件的定义,而非快照:每当流程触发器触发时,系统会按需对其进行评估,因此成员资格始终反映最新的用户画像数据。 ## 创建市场细分 \{#create-a-segment\} 1. 在 Adapty Mail 中,进入 **Segments** 并点击 **+ Create**。创建页面将打开,标题为 **New Segment**。 2. 填写市场细分的 **Name**(必填)以及可选的 **Description**。 3. 在 **Filters** 下,为每条[规则](#available-filter-fields)点击 **Add filter**。每个筛选器会折叠成一张卡片,依次标记为 **Filter 1**、**Filter 2** 等。 4. 对于每个筛选器,选择字段、选择运算符,然后输入比较值。 5. 保存市场细分。 :::important 过滤条件之间使用 **AND** 逻辑——用户必须满足所有过滤条件才能归入该市场细分。不支持 OR 逻辑和嵌套分组。每个字段在一个市场细分中只能出现一次;如需对同一字段比较多个值,请拆分为独立的市场细分。 ::: ## 从 Adapty 导入市场细分 \{#import-a-segment-from-adapty\} 除了通过筛选条件创建市场细分,你也可以直接从 Adapty 主看板导入已有的目标受众市场细分。 1. 在 **Segments** 页面,点击 **Import**。 2. 查看列表。**可导入的市场细分**会显示复选框及其筛选条件。**无法导入**的市场细分会以灰色显示,并注明具体原因,例如字段不支持(如 Apple Ads 归因数据)、运算符不支持,或订阅状态在 Adapty Mail 中没有对应项。 3. 勾选所需的市场细分,然后点击 **Import**。 :::important 导入操作会在导入时创建该市场细分筛选条件的独立副本——它不会与原始 Adapty 市场细分保持同步,再次导入同一市场细分会创建单独的副本,因为 Adapty Mail 不会检测重复项。 ::: 导入的市场细分初始状态为**草稿**,与手动创建的市场细分相同,因此你可以立即编辑其筛选条件。 ## 可用筛选字段 \{#available-filter-fields\} | 分组 | 字段 | 类型 | | -------------- | ------------------------- | ------- | | 用户画像 | 邮箱 | String | | 用户画像 | 年龄 | Integer | | 用户画像 | 国家 | String | | 用户画像 | 外部用户画像 ID | String | | 用户画像 | 创建时间 | Date | | 购买状态 | 总收入(USD) | Decimal | | 购买状态 | 订阅状态 | Enum | | 购买状态 | 订阅购买时间 | Date | | 购买状态 | 订阅到期时间 | Date | | 购买状态 | 一次性购买状态 | Enum | | 购买状态 | 一次性购买时间 | Date | **订阅状态值**:从未购买、已订阅、自动续订已关闭、账单问题、宽限期、已过期、已退款。 **一次性购买状态值**:从未购买、已购买、已退款。 各字段类型可用的运算符: - **字符串**:等于、不等于、已设置、未设置。 - **数字**:等于、不等于、小于、大于、小于或等于、大于或等于、介于、已设置、未设置。 - **日期**:等于、不等于、早于、晚于、等于或早于、等于或晚于、介于、已设置、未设置。 ## 全体用户系统市场细分 \{#the-all-users-system-segment\} Adapty Mail 内置了一个 **All Users** 市场细分,不设任何筛选条件——项目中的所有用户均符合条件。该市场细分无法编辑或删除。在流程中使用时,它充当兜底的最后一行(优先级规则详见[流程](mail-flows))。 ## 生命周期 \{#lifecycle\} 市场细分的状态根据其使用情况计算得出: - **草稿**:已创建,但未关联任何流程或 A/B 测试。 - **上线中**:已关联至活跃的流程或 A/B 测试。 - **未激活**:曾经关联过,但 A/B 测试已结束或流程行已被移除。 - **已归档**:已软删除,且从主列表中隐藏。 市场细分页面的工具栏中有状态筛选器,可用于将列表缩小至上述任意状态。 ## 编辑和删除市场细分 \{#edit-and-delete-a-segment\} - **名称和描述**:随时可编辑。 - **草稿市场细分的筛选条件**:可完整编辑。 - **已上线市场细分的筛选条件**:已锁定。一旦某个市场细分被正在运行的流程行或 A/B 测试引用,其筛选条件将变为只读。你只能修改名称或描述。如需更改定向条件,请创建新的市场细分并替换流程行。 - **删除**:软删除该市场细分。已上线的市场细分无法删除——请先将其从流程中移除(或结束 A/B 测试)。 ## 限制 \{#limitations\} - **不支持 OR 逻辑,不支持嵌套**:筛选条件仅以 AND 方式组合。 - **每个市场细分只能有一个字段**:同一字段不能添加两个筛选条件(例如,不能有两个国家/地区筛选)。 - **无规模预览**:编辑器不显示当前有多少用户符合筛选条件。 - **上线后筛选条件锁定**:已激活的市场细分除名称和描述外均为只读状态。 --- # File: mail-profiles --- --- title: "Adapty Mail 中的用户画像" description: "查看项目中的每位客户——包括其属性、购买状态、邮件互动情况及完整的活动记录。" --- **用户画像**代表项目中的一位客户。**Profiles** 页面列出 Adapty Mail 已知的所有用户,并展示每个人的购买结果、邮件互动情况及完整的活动记录。用户画像会自动同步:来源包括 Adapty SDK 收集的邮件地址,以及您通过 Adapty Mail API 发送的数据。 :::tip 要将用户画像分组为可复用的目标受众,用于流程和 A/B 测试,请参阅[市场细分](mail-segments)。 ::: ## 用户画像如何进入 Adapty Mail \{#how-profiles-get-into-adapty-mail\} Adapty Mail 会自动从两个来源创建用户画像: - **Adapty SDK**:SDK 从你的应用中收集邮箱和购买信息。详见[收集用户邮箱](mail-collect-emails)。 - **Adapty Mail API**:你的后端通过服务器间通信发送用户画像和交易数据。详见[通过 API 发送数据](mail-send-data-via-api)。 Adapty Mail 通过稳定的 `external_profile_id` 将每封邮件、每次点击和每次购买关联到对应的用户画像。用户画像页面为只读模式。你可以查看用户画像并取消其订阅,但无法创建、编辑或删除它们。这些数据由源应用或 API 负责管理。 ## 用户画像列表 \{#the-profiles-list\} 列表按时间倒序排列,每行显示一条用户画像记录。你可以通过搜索框,按邮箱、用户画像 ID 或外部用户画像 ID 查找特定用户画像。 | 列 | 显示内容 | | --- | --- | | Profile | 客户的电子邮件地址,以及向其发送邮件最多的营销活动。 | | Status | 用户画像的购买状态。请参阅[用户画像状态](#profile-status)。 | | Country | 客户所在国家/地区。 | | Open rate | 所有邮件的打开数除以发送数。若尚未发送任何邮件,则显示破折号。 | | LTV | 生命周期价值——该用户画像在所有渠道的总收入。 | | Joined | 用户画像首次进入 Adapty Mail 的时间。 | | Last activity | 用户画像最近一次与邮件互动(如发送、打开或点击)的时间。 | :::important **Joined** 是用户画像首次进入 Adapty Mail 的日期,用作"客户起始日期"。该日期为数据摄入时间,而非您应用中的原始注册日期。 ::: ### 用户画像状态 \{#profile-status\} **Status** 列显示用户画像的购买状态——即该用户在订阅和一次性购买方面的当前情况。 | 状态 | 含义 | | --- | --- | | Never purchased | 该用户画像未购买任何内容。 | | Purchased | 该用户画像完成了一次性购买。 | | Active subscriber | 该用户画像拥有有效订阅。 | | Cancelling | 自动续订已关闭;访问权限持续至当前周期结束。 | | Billing issue | 续订付款失败。 | | Grace period | 付款失败,但访问权限在商店的宽限期内继续有效。 | | Churned | 订阅已到期,访问权限已终止。 | | Refunded | 购买已退款。 | :::note 购买状态与邮件订阅状态是相互独立的。一个用户画像可以是**活跃订阅者**但**未订阅**您的邮件,也可以是**已流失**但仍**已订阅**邮件。邮件状态显示在用户画像页面上,用于控制 Adapty Mail 是否可以向该用户画像发送邮件。 ::: ## 用户画像详情 \{#profile-details\} 点击某个用户画像,即可打开其详情页。页面顶部显示邮箱、国家/地区、平台以及"成为客户的日期"。此外还有三个状态标签:购买状态、打开率,以及该用户画像是 **Subscribed**(已订阅)还是 **Unsubscribed**(已退订)。 顶部展示五项互动数据: - **Sent**:已发送给该用户画像的邮件数量。 - **Delivered**:邮件服务商成功接收的邮件数量。 - **Opened**:该用户画像已打开的邮件数量。 - **Clicked**:该用户画像点击了链接的邮件数量。 - **Revenue**:两项数值——归因收入和生命周期价值。 :::note **归因收入**是您的邮件带来的收入:用户与推广活动互动后所产生的购买金额。**终身价值(LTV)**是该用户画像在所有渠道的总收入,无论是否与邮件相关。页头先显示归因收入,再显示 LTV。 ::: **Profile** 卡片列出了客户的属性: - **Platform**:客户的设备平台,例如 iOS 或 Android。 - **Country**:客户所在的国家/地区。 - **Store country**:客户的 App Store 或 Google Play 账户所在的国家/地区。 - **Gender**:客户的性别(如已知)。 - **Age**:客户的年龄(如提供了生日信息)。 - **Profile ID**:Adapty Mail 为该用户画像分配的内部标识符。 - **External ID**:来自您的应用或后端的 `external_profile_id`。 - **Custom attributes**:您随用户画像发送的任意键值对。 ### 购买状态 \{#purchase-state\} **Purchase state** 卡片显示用户画像的收入与购买历史。顶部显示生命周期价值,下方最多包含两个区块: - **Subscription**:该用户画像的订阅价格、商店、开始日期、续订或到期日期,以及产品 ID。 - **One-time purchase**:最近一次一次性购买的价格、商店、购买日期和产品 ID。 如果用户画像尚未有任何购买记录,卡片将显示 **No purchase yet**。 **Segments** 卡片列出了该用户画像当前匹配的所有市场细分,如果没有匹配项则显示 **Not in any segment**。成员资格实时计算,始终反映最新的用户画像数据。关于如何创建市场细分,请参阅[市场细分](mail-segments)。 ## 活动历程 \{#the-activity-journey\} **Journey** 部分是该用户画像所有活动的时间线,从 **Profile created** 开始,然后穿插两类事件: - **邮件事件**:发送给该用户画像的每封邮件,包含送达、打开和点击情况。展开某封邮件可查看该用户画像点击的链接,以及该邮件带来的购买记录。 - **交易事件**:订阅和一次性购买的关键节点,例如开始、续订、取消、账单问题、到期和退款。 交易事件对应以下历程标签: | `event_type` | Journey 标签 | | --- | --- | | `subscription_started` | Subscription started | | `subscription_renewed` | Subscription renewed | | `subscription_renewal_cancelled` | Renewal cancelled | | `subscription_renewal_reactivated` | Renewal resumed | | `billing_issue_detected` | Billing issue | | `entered_grace_period` | Entered grace period | | `subscription_expired` | Subscription expired | | `subscription_refunded` | Subscription refunded | | `non_subscription_purchase` | One-time purchase | | `non_subscription_purchase_refunded` | Purchase refunded | 这些事件可以通过 Adapty SDK 自动发送到 Adapty Mail,也可以通过 API 手动发送。事件参考请查看[发送交易事件](mail-send-data-via-api#send-transaction-events)。 ## 取消订阅用户画像 \{#unsubscribe-a-profile\} 要停止向某个用户画像发送邮件,打开其页面,点击 **...**,然后选择 **Unsubscribe**。Adapty Mail 会将该用户画像标记为已取消订阅,并将其加入屏蔽列表,后续的推广活动和流程都会跳过它。 此操作具有幂等性:已取消订阅的用户画像不会发生任何变化。关于屏蔽机制以及用户画像如何自行取消订阅的完整说明,请参阅[取消订阅与屏蔽](mail-suppression)。 --- # File: mail-ab-testing --- --- title: "Adapty Mail 中的 A/B 测试" description: "通过将 A/B 测试附加到流程,对完整的电子邮件活动进行相互比较。" --- Adapty Mail 中的 A/B 测试会将两个或多个完整的邮件营销活动相互对比。每个实验变体都是一个完整、独立的营销活动。当用户符合测试的市场细分条件时,Adapty Mail 会根据配置的权重将其路由到某个实验变体,并分别追踪每个实验变体的送达率、互动情况和收入数据。 ## 什么是实验变体 \{#what-a-variation-is\} 每个实验变体都是一个完整的活动。实验变体之间可以在任何活动维度上有所不同——文案内容、主图、语气风格、邮件序列长度或发送间隔时间。A/B 测试本身不提供这些调节选项;你需要分别创建活动,然后将它们作为实验变体添加进来。 ## 创建 A/B 测试 \{#create-an-ab-test\} 1. 先在 **Campaigns** 中创建好活动,每个实验变体需要对应一个独立的活动。 2. 在 Adapty Mail 中,进入 **A/B Tests** 并点击 **Create**。 3. 将每个活动添加为实验变体,并设置其权重。所有权重之和必须为 **100%**。 4. 分配一个市场细分,以控制测试适用的用户范围。 5. 保存。 测试保存后状态为**草稿**,不会发送任何内容。要正式上线,需将其关联到一个流程。 ## 从流程中启动 \{#launch-from-a-flow\} A/B 测试无法从 A/B Tests 页面启动——启动和结束都在流程行内完成。 1. 在 Adapty Mail 中,进入 **Flows** 并打开要运行测试的触发器。 2. 在新行上点击 **Create**。在对话框中,将 **Content type** 设置为 **A/B Test**,选择已保存的测试,然后点击 **Save**。 3. 在该行上点击 **Launch A/B test**。 测试状态从 **draft** 变为 **live**,符合市场细分条件的新用户将开始被分配到各实验变体。更多关于流程行的说明,请参阅[创建流程](mail-create-flow)。 ## 路由的工作原理 \{#how-routing-works\} 当用户触发流程并符合 A/B 测试的市场细分条件时,Adapty Mail 会通过**加权随机**的方式选取一个实验变体——每个实验变体的权重决定其被抽中的概率。路由结果并非按用户固定分配。 ## 查看结果 \{#read-results\} 在 A/B 测试页面上,每个实验变体会显示其原始计数和派生比率: - **Delivery**:Sends、Deliveries、Bounces。 - **Engagement**:Opens、Clicks、Unsubs。 - **Revenue**:Purchases、Revenue。 各数据图表的统计口径和收入归因方式,请参阅[营销活动分析](mail-analytics)。 ## 完成测试 \{#finish-the-test\} 与启动操作一样,完成测试也是在流程行中操作,而不是在 A/B Tests 页面。 1. 打开正在运行测试的流程行。 2. 点击 **Finish A/B test**。 3. 在 **Finish A/B test** 对话框中,从 **Replace with campaign** 下拉菜单中选择获胜的活动——或者留空以将该市场细分从流程中完全移除。 4. 确认操作。 :::note 已经在某个实验变体序列中进行到一半的用户——无论是获胜还是未获胜的变体——都会继续收到已安排的邮件,不会被切换到获胜方案。 ::: ## 生命周期 \{#lifecycle\} A/B 测试会经历四个状态: - **草稿**:已创建,尚未关联到正式的流程行。 - **进行中**:已关联并启动,正在为新进用户分配流量。 - **已结束**:通过 **Finish A/B test** 操作停止。 - **已归档**:从列表中软删除。 --- # File: mail-analytics --- --- title: "Adapty Mail 中的数据分析" description: "按活动、市场细分、A/B 实验变体、消息或触发条件对活动效果进行拆分分析,同时查看送达率、互动数据和收入。" --- **Analytics** 页面从五个维度展示营销活动的表现:营销活动、市场细分、A/B 实验变体、消息和触发器。它将投递数据指标与每封邮件带来的收入结合在一起,方便你比较不同实验变体、找出表现最佳的市场细分,以及发现收入集中在哪里。 页面顶部是数据图表,下方是分类明细表格。点击任意行可深入查看单个实体的详细数据。 ## 选择时间范围 \{#pick-a-time-range\} 页面顶部的工具栏用于控制时间窗口及其分组方式: - **Date range**:预设选项(最近 7 / 14 / 30 / 90 天、本月、上月、最近 12 个月、年初至今),或使用 **Custom range** 自定义日期选择器。默认为最近 30 天。 - **Granularity**:**Daily**(按天)、**Weekly**(按周)或 **Monthly**(按月)分组。当时间范围增大时,粒度会自动调粗——超过 92 天时 **Daily** 自动切换为 **Weekly**,超过 366 天时两者均切换为 **Monthly**。 - **Chart style**:**Line**(折线图)、**Area**(面积图)或 **Bar**(柱状图)。 如果页面提示"时间范围过宽",请缩短日期范围、降低数据粒度或添加筛选条件。 ## 分组、拆分与筛选 \{#group-break-down-filter\} 工具栏下方的三个控件决定了数据图表和表格的展示内容: - **Group by**:将数据集拆分为多行的维度。可选项包括 **Campaigns**、**Segments**、**A/B variants** 和 **Triggers**。选择 **No grouping** 时,页面将所有数据汇总为一行 **All**。 - **Breakdown**:将每行进一步拆分为子行的第二个维度。同时设置 **Group by** 和 **Breakdown** 后,表格中的每一行都可以展开查看其子分组。Breakdown 可以使用任意维度(包括 **Messages**),但不能与 **Group by** 使用相同的维度。 - **Add filter**:将数据集限定为特定的 campaigns、segments、A/B variants 或 triggers。筛选条件同时作用于数据图表和表格。 :::note **消息**可作为细分维度使用,但不能作为顶级**分组依据**或筛选条件。如需分析单条消息,请按**营销活动**分组并使用**消息**细分,然后展开对应的营销活动行或从下钻视图中打开某条消息。 ::: ## 解读数据图表 \{#read-the-chart\} 数据图表会在所选时间范围内展示你选择的各项指标。 - **指标类别**:在 **Email actions**(已发送、已送达、已打开、已点击、已退回、已取消订阅、已转化)和 **Revenue** 之间切换。 - **指标筛选按钮**:选择要绘制的指标。在聚合模式下(无分组),可以在同一图表中绘制多个指标。开启分组后,图表只绘制单个指标——每个分组对应一条折线,以便在视觉上加以区分。 - **图例**:开启分组后,右侧图例会列出所有分组,你可以单独开启或关闭某条折线。 下方数据图表表格中的可见性复选框,同时也控制图表中显示哪些行。 ## 读取数据图表下方的指标表格 \{#read-the-metrics-table\} 数据图表下方的指标表格按组列出每行数据,顶部的汇总行会聚合表格中的所有其他行。 - **可排序列**:点击任意列标题,可按名称、发送量、送达量、送达率、打开量、打开率、点击量、点击率、转化量、收入、退回量或退订量进行排序。 - **可见性复选框**:切换该行是否显示在数据图表上。 - **展开行**:设置**分组维度**后,每行左侧的箭头图标可将其展开为子分组。 - **打开钻取视图**:点击某行的名称,打开该实体的聚焦视图。 下钻视图包含以下内容: - 与主页相同的数据图表和指标选择器,但范围限定于单个实体。 - 底部八张摘要卡片:**Sent**、**Delivered**(含送达率)、**Opened**(含打开率)、**Clicked**(含点击率)、**Bounced**、**Unsubscribed**、**Converted** 和 **Revenue**。 日期范围和粒度与主页保持一致。点击面包屑中的 **Back** 可返回上一级。 ## 跟踪的数据内容 \{#whats-tracked\} :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: 分析页面上的每一行数据——无论是在明细视图还是下方描述的内联视图中——都包含同一组原始计数: - **已发送**:已分发至 SES 的邮件。 - **已送达**:经 SES 确认送达收件箱的邮件。 - **已退回**:SES 报告的退信。硬退信和软退信不作区分——均计为一次**已退回**。 - **已打开**:像素加载次数。Apple Mail 隐私保护功能会在 iOS 15 及更高版本上预先获取图片,导致该数值偏高——建议以点击量和收入作为更可靠的参考指标。 - **已点击**:邮件正文中的链接点击次数。 - **已退订**:通过邮件底部链接或 `List-Unsubscribe` 标头发起的退订。 - **已转化**:在统计时间范围内,该分组中有归因购买记录的唯一用户画像数。转化按购买日期归入对应时段——3 月点击、4 月购买,则计入 4 月。同一用户画像多次购买仍只计为一次。 - **收入**:订阅开始、续订及一次性购买的归因收入总和(美元)。 ## 衍生比率 \{#derived-rates\} 每项比率均由上述原始数据计算得出: | 比率 | 计算公式 | | ------------- | -------------------- | | 送达率 | 已送达 / 已发送 | | 打开率 | 已打开 / 已送达 | | 点击率 | 已点击 / 已送达 | 向下钻取视图会在汇总卡片旁显示相同的三项比率。 ## 收入归因 \{#revenue-attribution\} 收入归因采用**最后点击**追踪链接的方式: 1. 当收件人点击邮件中的任意链接时,Adapty Mail 会将 `scheduled_email_id` 短暂存储到该用户画像中。 2. 如果随后发生购买事件且尚无归因记录,Adapty Mail 会将已存储的 `scheduled_email_id` 回填到该交易中——前提是购买时间戳晚于点击时间。 3. 没有先行追踪点击的购买将保持未归因状态。 跟踪参数为 `scheduled_email_id`。结账 URL 还通过 `{email}` 和 `{external_profile_id}` 占位符携带收件人的身份信息,以便 Web 付费墙能够个性化流程——这是独立于归因的另一套机制。详见[设置结账](mail-checkout)。 ## 流程与 A/B 测试中的内联分析 \{#inline-analytics-in-flows-and-ab-tests\} 相同的数据图表也会以内联方式显示在对应行旁边: - **Flows 页面**:触发器视图中的每个市场细分行都会显示其送达量、互动量和收入数据。 - **A/B 测试页面**:各实验变体并排列出,使用相同的指标集,便于直接对比不同变体的表现。 在跨活动对比或深入分析单个实体时,使用 Analytics 页面;在已进入特定流程行或 A/B 测试时,使用内联视图。上述数据图表定义、衍生比率和归因规则在所有三个视图中同样适用。 ## 限制 \{#limitations\} - **无软退信与硬退信区分**:所有退信——无论是临时性的还是永久性的——都合并为一个 **Bounced** 计数。 - **最终一致性,非实时**:计数由事件表聚合而来。新事件通常在几分钟内显示,但不保证实时性。 - **时间范围有上限**:较大的日期范围与精细的时间粒度组合使用时,可能超出数据图表的单元格上限。页面会显示"range too wide"警告——请缩小范围、降低粒度或应用筛选条件。 --- # File: configuration --- --- title: "配置第三方集成" description: "了解如何配置 Adapty 设置以优化订阅管理。" --- 通过 Adapty 集成,您可以将订阅事件和购买数据无缝传输到您偏好的平台或工作流。无论您是寻求用户行为洞察、客户互动策略,还是为营销团队提供更强大的产品分析,Adapty 都能轻松地将应用内购买事件转发到您选择的集成平台。 Adapty 可以轻松追踪应用内购买和订阅事件,例如试用、转化、续订和取消。这些[事件](events)会自动传递到您选择的集成平台。这使您能够根据客户所处的当前阶段与其互动,并分析应用内与收入相关的活动。 ## 集成设置 \{#integration-settings\} <img src="/assets/shared/img/20bf659-CleanShot_2023-08-22_at_13.26.562x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 集成提供以下配置选项,这些选项会影响通过该集成发送的所有事件: | 设置 | 描述 | |:--------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Reporting Proceeds** | 选择收入值的呈现方式:扣除 App Store 和 Play Store 佣金后的净额,或扣除前的总额。勾选"Send sales as proceeds"复选框,可将销售额显示为扣除 App Store / Play Store 佣金后的收益。 | | **Send Trial Price** | 若勾选,Adapty 将在 Trial Started 事件中传输订阅价格。 | | **Exclude Historical Events** | 选择排除用户在安装含 Adapty SDK 的应用之前发生的事件。这可防止事件重复,并确保报告的准确性。例如,若用户在 1 月 10 日激活了月度订阅,并在 3 月 6 日更新了含 Adapty SDK 的应用,则 Adapty 将忽略 3 月 6 日之前的事件,并保留此后的事件。 | | **Report User's Currency** | 选择以用户本地货币还是美元报告销售额。 | | **Send User Attributes** | 若您希望发送用户特定属性(如语言偏好),且您的 OneSignal 计划支持超过 10 个标签,请选择此选项。启用后,可在默认 10 个标签之外包含附加信息。请注意,超出标签限制可能会导致错误。 | | **Send Attributions** | 启用此选项以传输归因信息(例如 AppsFlyer 归因)并接收相关详情。 | | **Send Play Store purchase token** | 启用此选项以接收在需要时用于重新验证购买的 Play Store 令牌。它将向事件添加 `play_store_purchase_token` 参数。 | | **Delay events with future datetime** | **仅适用于 AppsFlyer 和自定义 Webhook**:启用后,续订和试用转化事件将在实际发生日期发送。禁用时(默认),这些事件会在检测到时立即发送,即使日期在未来也是如此。 | | **Data residency** | **仅适用于 Mixpanel 和 Amplitude**:选择数据驻留地,以确定事件的处理和存储位置。 | ## 配置事件 \{#configure-the-events\} 在凭据下方,有三组事件可供您从 Adapty 发送到所选集成平台。您应启用所需的事件。 需要注意的是,某些集成支持自定义事件名称,而其他集成的事件名称是固定的,无法修改。此外,对于某些集成(例如 [Airbridge](airbridge#configure-events-and-tags)),您可以灵活地将多个事件名称关联到单个 Adapty 事件。点击[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/c79f5cd-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 虽然我们建议使用 Adapty 的默认事件名称,但您也可以根据具体需求自由调整事件名称。 --- # File: events --- --- title: "发送给第三方集成的事件" description: "使用 Adapty 的分析工具跟踪关键订阅事件。" --- Apple 和 Google 通过 [App Store Server Notifications](enable-app-store-server-notifications) 和 [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) 将订阅事件直接发送到服务器。因此,移动应用无法可靠地将事件实时发送到分析系统。例如,如果用户订阅后再未打开应用,开发者在没有服务器的情况下将无法收到任何订阅状态更新。 Adapty 通过收集订阅数据并将其转化为易于理解的事件来弥补这一差距。这些集成事件以 JSON 格式发送。所有事件共享相同的结构,但字段会根据事件类型、商店及具体配置有所不同。您可以在各集成页面上找到每个事件所包含的具体字段。 如需了解如何判断事件是否已成功处理或是否出现问题,请查看[事件状态](event-statuses)页面。 ## 事件类型 \{#event-types\} 大多数事件会在创建后发送到所有已配置的集成(前提是相应集成已启用)。但 **Access level updated** 事件仅在配置了 [webhook 集成](webhook) 且该事件已启用时才会触发。该事件会显示在 [Event Feed](https://app.adapty.io/event-feed) 中,并发送到 webhook,但不会共享给其他集成。 如果未配置 webhook 集成或未启用此事件类型,**Access level updated** 事件将不会被创建,也不会出现在 [Event Feed](https://app.adapty.io/event-feed) 中。 | 事件名称 | 描述 | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | 当用户激活没有试用期的付费订阅时触发,即立即扣款。 | | subscription_renewed | 订阅续费并成功扣款时发生。该事件从第二次计费开始记录,无论是试用订阅还是非试用订阅。 | | subscription_renewal_cancelled | 用户已关闭订阅自动续费。用户在付费订阅周期结束前仍可使用高级功能。 | | subscription_renewal_reactivated | 当用户重新激活订阅自动续费时触发。 | | subscription_expired | 当订阅取消后完全到期时触发。例如,用户在12月12日取消订阅,但订阅在12月31日到期,则该事件在12月31日记录。 | | subscription_paused | 当用户激活[订阅暂停](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause)时发生(仅限 Android)。 | | subscription_deferred | 当订阅购买被[延期](https://adapty.io/glossary/subscription-purchase-deferral/)时触发,允许用户延迟付款同时保留对高级功能的访问权限。此功能通过 Google Play Developer API 提供,可用于免费试用或帮助面临经济困难的用户。 | | non_subscription_purchase | 任何非订阅购买,例如永久授权或消耗型商品(如游戏内货币)。 | | trial_started | 当用户激活试用订阅时触发。 | | trial_converted | 当试用期结束并成功向用户扣款(首次购买)时发生。例如,用户的试用期至1月14日,但在1月7日被扣款,则该事件在1月7日记录。 | | trial_renewal_cancelled | 用户在试用期间关闭了订阅自动续费。用户在试用期结束前仍可使用高级功能,但不会被扣款或开始订阅。 | | trial_renewal_reactivated | 当用户在试用期间重新激活订阅自动续费时发生。 | | trial_expired | 当试用期结束且未转化为订阅时触发。 | | entered_grace_period | 当付款尝试失败且用户进入宽限期(如已启用)时发生。用户在此期间保留高级访问权限。 | | billing_issue_detected | 当扣款尝试中出现账单问题时触发(例如,卡余额不足)。 | | subscription_refunded | 当订阅被退款时触发(例如,由 Apple 客服处理)。 | | non_subscription_purchase_refunded | 当非订阅购买被退款时触发。 | | access_level_updated | 当用户的访问等级更新时发生。 | 上述事件完整涵盖了用户的购买状态。下面来看一些示例。 ### 示例 1 \{#example-1\} _用户于 4 月 1 日激活了一个包含 7 天试用期的月度订阅。第 4 天,他取消了订阅。_ 在这种情况下,将发送以下事件: 1. 4 月 1 日发送 `trial_started` 2. 4 月 4 日发送 `trial_renewal_cancelled` 3. 4 月 7 日发送 `trial_expired` ### 示例 2 \{#example-2\} _用户于 4 月 1 日激活了一个包含 7 天试用期的月度订阅。第 10 天,他取消了订阅。_ 在这种情况下,将发送以下事件: 1. 4 月 1 日发送 `trial_started` 2. 4 月 7 日发送 `trial_converted` 3. 4 月 10 日发送 `subscription_renewal_cancelled` 4. 5 月 1 日发送 `subscription_expired` 有关每种场景下触发哪些事件的详细说明,请查看[事件流程](event-flows)。 --- # File: event-flows --- --- title: "事件流" description: "了解 Adapty 中订阅事件流的详细方案。学习订阅事件如何生成并发送到各集成渠道,帮助您追踪客户旅程中的关键节点。" --- 在 Adapty 中,您将在用户使用应用的整个历程中收到各种订阅事件。以下订阅流程涵盖了常见场景,帮助您了解 Adapty 在用户订阅、取消或重新激活订阅时生成的事件。 请注意,Apple 会在实际开始/续订时间前数小时处理订阅付款。为保持图表简洁,以下流程图将订阅开始/续订与付款扣除显示为同时发生。 此外,与同一操作相关的事件会同时发生,在 **Event Feed** 中的显示顺序可能不固定,与我们图示中的顺序也可能有所不同。 ## 订阅生命周期 \{#subscription-lifecycle\} ### 初次购买流程 \{#initial-purchase-flow\} 当用户首次购买订阅且没有试用期时,会触发以下事件: - **Subscription started** - **Access level updated**:授予用户访问权限 当订阅到达续期日期时,订阅将自动续期,并触发以下事件: - **Subscription renewal**:开始新一个订阅周期 - **Access level updated**:更新订阅到期日期,将访问权限延长至下一个周期 付款失败或用户取消续订的情况分别在[账单问题结果流程](event-flows#billing-issue-outcome-flow)和[订阅取消流程](event-flows#subscription-cancellation-flow)中描述。 <img src="/assets/shared/img_webhook_flows/Initial_Purchase_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 订阅取消流程 \{#subscription-cancellation-flow\} 当用户取消订阅时,系统会创建以下事件: - **Subscription renewal canceled**:表示订阅在当前周期结束前仍保持有效,之后用户将失去访问权限 - **Access level updated**:用于禁用该访问等级的自动续费功能 订阅到期后,系统会触发 **Subscription expired (churned)** 事件,标志着订阅的结束。 <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果退款申请获批,以下事件将替代 **Subscription expired (churned)**: - **Subscription refunded**:终止订阅并提供退款详情 <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow_with_a_Refund.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 对于 Stripe,订阅可以立即取消,跳过剩余的订阅周期。在这种情况下,所有事件会同时创建: - **Subscription renewal cancelled** - **Subscription expired (churned)** - **Access Level updated**(用于移除用户的访问等级) 如果退款申请获批,系统还会触发 **Subscription refunded** 事件。 <img src="/assets/shared/img_webhook_flows/Subscription_Immediate_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 订阅重新激活流程 \{#subscription-reactivation-flow\} 如果用户取消订阅后,订阅到期,之后又重新购买了同一订阅,系统将创建一个 **Subscription renewed** 事件。即使中间存在访问中断,Adapty 也会通过 `vendor_original_transaction_id` 将其视为同一交易链,因此此次重购被视为续订。 **Access level updated** 事件将被创建两次: - 在订阅结束时,撤销用户的访问权限 - 在订阅重新购买时,授予访问权限 <img src="/assets/shared/img_webhook_flows/Subscription_Rejoin_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 订阅暂停流程(仅限 Android)\{#subscription-pause-flow-android-only\} 此流程适用于用户在 Android 上暂停并随后恢复订阅的情况。 暂停订阅会产生延迟效果。如果用户在订阅续期前将其暂停,订阅仍保持有效,用户在当前计费周期剩余时间内继续享有付费访问权限。 1. 当用户暂停订阅时,会触发 **Subscription paused (Android only)** 事件。 2. 订阅周期结束时,Adapty 会触发 **Access level updated** 事件以撤销用户的访问权限。 3. 当用户恢复订阅时,将触发以下事件: - **Subscription renewed** - **Access level updated**(用于恢复用户的访问权限) 这些订阅将属于同一交易链,并通过相同的 **vendor_original_transaction_id** 关联。 <img src="/assets/shared/img_webhook_flows/Subscription_Paused_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 试用流程 \{#trial-flows\} 如果您在应用中使用试用功能,您将收到额外的试用相关事件。 ### 试用期成功转化流程 \{#trial-with-successful-conversion-flow\} 最常见的流程是:用户开始试用、绑定信用卡,并在试用期结束后成功转化为标准订阅。在此场景中,试用开始时会生成以下事件: - **Trial started**:标记试用开始 - **Access level updated**:授予访问权限 当标准订阅正式生效时,系统会生成 **Trial converted** 事件。 <img src="/assets/shared/img_webhook_flows/Trial_Flow_with_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 试用未成功转化的流程 \{#trial-without-successful-conversion-flow\} 如果用户在试用期转化为订阅之前取消,系统会在取消时创建以下事件: - **Trial renewal cancelled**:禁用试用期自动转化为订阅 - **Access level updated**:禁用访问等级续订 用户仍可使用至试用期结束,届时系统会创建 **Trial expired** 事件,标记试用期正式结束。 <img src="/assets/shared/img_webhook_flows/Trial_Flow_without_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 试用期到期后重新激活订阅的流程 \{#subscription-reactivation-after-expired-trial-flow\} 如果试用期因账单问题或取消而到期,用户后续购买订阅时,系统将创建以下事件: - **访问等级已更新**,为用户授予访问权限 - **试用已转化** 即使试用期与订阅之间存在时间间隔,Adapty 也会通过 `vendor_original_transaction_id` 将两者关联起来。此次转化被视为一条连续交易链的一部分,该链从零价格的试用期开始。这就是系统创建 **试用已转化** 事件而非 **订阅已开始** 事件的原因。 <img src="/assets/shared/img_webhook_flows/Subscription_Reactivation_Flow_after_Expired_Trial.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 产品变更 \{#product-changes\} 本节涵盖对活跃订阅所做的各类调整,例如升级、降级,或购买其他组合中的产品。 ### 立即生效的产品变更流程 \{#immediate-product-change-flow\} 用户变更产品后,系统可以在订阅结束前立即完成切换(通常发生在升级或替换产品的情况下)。此时,在产品变更的瞬间: - 访问等级发生变更,系统创建两个 **Access level updated** 事件: 1. 撤销第一个产品的访问权限。 2. 授予第二个产品的访问权限。 - 旧订阅结束,并退款(系统创建 **Subscription refunded** 事件,`cancellation_reason` = `upgraded`)。请注意,此时不会创建 **Subscription expired (churned)** 事件;**Subscription refunded** 事件将替代它。 - 新订阅开始(系统为新产品创建 **Subscription started** 事件)。 <img src="/assets/shared/img_webhook_flows/Immediate_Product_Change_Flow_Upgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果用户降级订阅,第一个订阅将持续到已付费周期结束,届时将被新的低级别订阅替换。在这种情况下,系统会立即创建 **Access level updated** 事件以禁用自动续订访问权限。所有其他事件将在订阅实际发生替换时创建: - 另一个 **Access level updated** 事件被创建,以授予对第二个产品的访问权限。 - **Subscription expired (churned)** 事件被创建,以结束第一个产品的订阅。 - **Subscription started** 事件被创建,以开始新产品的新订阅。 <img src="/assets/shared/img_webhook_flows/Delayed_Product_Change_Downgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 延迟产品变更流程 \{#delayed-product-change-flow\} 还有一种情况:用户在订阅续费时更改产品。这种情况与前一种非常相似:系统会立即创建一个 **Access level updated** 事件,以禁用旧产品的访问等级自动续费。所有其他事件将在用户更改订阅且变更生效于系统时创建: - 另一个 **Access level updated** 事件被创建,以授予对第二个产品的访问权限。 - **Subscription expired (churned)** 事件被创建,以结束第一个产品的订阅。 - **Subscription started** 事件被创建,以启动新产品的新订阅。 <img src="/assets/shared/img_webhook_flows/Product_Change_on_Renewal_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 账单问题结果流程 \{#billing-issue-outcome-flow\} 如果试用转换或订阅续费因账单问题失败,后续流程取决于是否启用了宽限期。 启用宽限期时,若付款成功,试用将完成转换或订阅将完成续费。若付款失败,应用商店会继续尝试向用户收取订阅费用,如仍失败,则应用商店将自行终止试用或订阅。 因此,在账单问题发生时,Adapty 中会创建以下事件: - **检测到账单问题** - **已进入宽限期**(如果已启用宽限期) - **访问等级已更新**,将访问权限延续至宽限期结束 如果后续付款成功,Adapty 会记录 **Trial converted** 或 **Subscription renewed** 事件,用户不会失去访问权限。 如果付款最终失败且应用商店取消了订阅,Adapty 将生成以下事件: - **Trial expired** 或 **Subscription expired (churned)**,附带 `cancellation_reason: billing_error` - **访问等级已更新**,撤销用户的访问权限 <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_with_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果没有宽限期,账单重试期(应用商店持续尝试向用户收费的时段)将立即开始。 如果在宽限期结束前付款始终未能成功,流程相同:当应用商店自动终止订阅时,会生成相同的事件: - **Trial expired** 或 **Subscription expired (churned)** 事件,其 `cancellation_reason` 为 `billing_error` - **Access level updated** 事件,用于撤销用户的访问等级 <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_without_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 跨用户账户共享购买的流程 \{#sharing-purchases-across-user-accounts-flows\} 当一个 <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration)、[Android](android-identifying-users#setting-customer-user-id-on-configuration)、[React Native](react-native-identifying-users#setting-customer-user-id-on-configuration)、[Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) 和 [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> 尝试恢复或续期已绑定到另一个 <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration)、[Android](android-identifying-users#setting-customer-user-id-on-configuration)、[React Native](react-native-identifying-users#setting-customer-user-id-on-configuration)、[Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) 和 [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> 的订阅时,Adapty 的 **Sharing paid access between user accounts** 设置将决定如何管理访问权限。具体流程会因所选选项而有所不同。 :::note 对于 Apple 家庭共享交易(`in_app_ownership_type=FAMILY_SHARED`),只会触发 **Access level updated** 事件——下方各产品的订阅事件不会触发。完整的事件矩阵请参阅 [Apple 家庭共享](apple-family-sharing)。 ::: :::note 如果用户点击 **Restore Purchases** 时,当前用户画像已拥有访问权限,则此次恢复操作无效,不会触发任何 Webhook 事件。本节中的事件仅在访问权限实际在用户画像之间转移时才会触发。 ::: 要快速了解第二个用户画像领取现有订阅时会触发哪些事件,请参阅下方矩阵。后续各节将展示每种流程的完整 JSON 载荷。 | 事件 | 已启用(默认) | 将访问等级转移至新用户 | 已禁用 | | --- | --- | --- | --- | | 新用户画像:**Access level updated**(`is_active=true`) | 触发 | 触发 | 不触发 | | 旧用户画像:**Access level updated**(`is_active=false`) | 不触发——两个用户画像均保留访问等级 | 当新识别设备传播交易时触发 | 不触发——原始用户画像保留访问等级 | | 新事件中的 `profiles_sharing_access_level` 字段 | 列出共享该访问等级的其他用户画像 | `null` | 不适用——不触发任何事件 | 已转移订阅的续订、退款和到期事件,将继续在当前持有该访问等级的用户画像上触发 `subscription_renewed`、`subscription_refunded` 和 `subscription_expired` 事件。转移事件本身不会触发 `subscription_started` 事件,因为没有记录新的交易——只有归因关系发生了变化。 有关各模式的详细约定,请参阅[实用参考](sharing-paid-access-between-user-accounts#practical-reference)。 ### 将访问等级转移给新用户的流程 \{#transfer-access-to-new-user-flow\} 推荐的做法是将访问等级转移给新用户。这样可以保留原始用户的交易记录,确保分析数据的一致性。整个过程只会产生 2 个 **Access level updated** 事件: 1. 移除第一个用户的访问等级 2. 授予第二个用户访问等级 <img src="/assets/shared/img_webhook_flows/Transfer_Access_to_New_User_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 以下是此场景中生成的事件里,与访问等级分配及转移相关字段的说明: - **用户 A:访问等级已更新(当用户 A 在应用内购买订阅时发送)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` - **用户 A:访问等级已更新(当应用重新安装并由用户 B 登录,撤销用户 A 的访问权限时发送)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": false, }, "profiles_sharing_access_level": null } ``` - **用户 B:访问等级已更新(当用户 B 登录并获得访问权限时发送)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000001", "customer_user_id": UserB, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` ### 用户间共享访问流程 \{#shared-access-between-users-flow\} 此选项允许多个用户共享同一访问等级,前提是他们的设备登录了相同的 Apple/Google ID。当用户重新安装应用并使用不同的邮箱登录时,仍可访问之前的购买内容,此选项非常适合这种场景。启用此选项后,多个已识别用户可以共享同一访问等级。在共享访问等级期间,所有交易记录均归属于原始 <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration)、[Android](android-identifying-users#setting-customer-user-id-on-configuration)、[React Native](react-native-identifying-users#setting-customer-user-id-on-configuration)、[Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) 和 [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>,以确保完整的交易历史记录和分析数据。 因此,只会创建 1 个事件:**Access level updated**,用于向第二个用户授予访问权限。 <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 以下是该场景中生成的事件里,与访问等级分配和共享相关的字段说明: **用户 B:Access level updated(当用户 B 登录并获得访问权限时发送)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": [ { "profile_id": "00000000-0000-0000-0000-000000000001, "customer_user_id": UserB } ] } ``` ### 用户之间访问权限不共享的流程 \{#access-not-shared-between-users-flow\} 使用此选项时,只有第一个获得该访问等级的用户画像能永久保留它。如果购买需要绑定到唯一的 <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration)、[Android](android-identifying-users#setting-customer-user-id-on-configuration)、[React Native](react-native-identifying-users#setting-customer-user-id-on-configuration)、[Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) 和 [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>,这是最理想的选择。 <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Disabled_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: event-statuses --- --- title: "集成事件状态" description: "" --- Adapty 根据 HTTP 状态码判断是否成功送达,将 `200-399` 范围之外的所有响应视为失败。 您可以在 Adapty 看板的 **Event List** 中跟踪集成事件的状态。无论特定集成是否启用了某种事件类型,系统都会显示所有已启用集成的状态。 - 黑色:事件已成功发送。 - <span style={{ color: 'grey' }}>灰色:</span>该事件类型在此集成中已禁用。 - <span style={{ color: 'red' }}>红色:</span>集成存在需要关注的问题。 如需查看失败事件的详细信息,请将鼠标悬停在集成名称上,即可看到包含具体错误信息的提示框。 <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Event Feed** 仅显示过去两周的数据以优化性能。此限制可提升页面加载速度,使用户能够更高效地浏览和分析事件。 --- # File: adjust --- --- title: "Adjust" description: "将 Adjust 与 Adapty 连接,以更好地追踪订阅数据和分析。" --- [Adjust](https://www.adjust.com/) 是领先的移动归因平台(MMP)之一,用于收集和呈现营销活动数据,帮助企业追踪广告投放效果。 Adapty 提供了一套完整的数据,让您可以在一个地方追踪来自各应用商店的[订阅事件](events)。借助 Adapty,您可以轻松了解订阅用户的行为规律、掌握他们的偏好,并据此进行精准有效的沟通。因此,本集成支持您在 Adjust 中追踪订阅事件,精确分析每个推广活动带来的收益。 Adapty 与 Adjust 的集成主要通过以下两种方式实现。 1. **Adapty 从 Adjust 接收归因数据** 完成 Adjust 集成配置后,Adapty 将开始从 Adjust 接收归因数据。你可以在用户的用户画像页面轻松查看这些数据。 <img src="/assets/shared/img/98769d9-CleanShot_2023-08-11_at_14.39.182x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Adapty 将订阅事件发送至 Adjust** Adapty 可以将所有在集成中配置的订阅事件发送至 Adjust,从而让你在 Adjust 看板中追踪这些事件。这一集成有助于评估广告活动的效果。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Adjust \{#connect-adapty-to-adjust\} 1. 打开 Adapty 看板,进入 [Integrations > Adjust](https://app.adapty.io/integrations/adjust)。 2. 将页面顶部的开关打开。 3. 填写各字段,并设置您的访问凭据。 <img src="/assets/shared/img/5064125-CleanShot_2023-08-11_at_14.43.382x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 如果您在 Adjust 平台上启用了 OAuth 授权,则在集成 iOS 和 Android 应用时必须提供 **OAuth Token**。 4. 接下来,提供您 iOS 和 Android 应用的 **app tokens**。打开 Adjust 看板,即可看到您的应用。 <img src="/assets/shared/img/adjust-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note 您在 iOS 和 Android 上可能有不同的 Adjust 应用,因此在 Adapty 中为此提供了两个独立的配置区域。如果您只有一个 Adjust 应用,直接填写相同的信息即可。 ::: 5. 从列表中选择您的应用,并复制 **App Token**。将该 token 粘贴到 Adapty 看板中对应的字段里。 <img src="/assets/shared/img/adjust-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 配置事件和标签 \{#configure-events-and-tags\} Adjust 的工作方式与其他平台略有不同。你需要在 Adjust 看板中手动创建事件,获取事件令牌,然后将其复制粘贴到 Adapty 中对应的事件里。 因此,第一步是找到你希望 Adapty 发送的所有事件的事件令牌。具体操作如下: 1. 在 Adjust 看板中,打开你的应用并切换到 **Events** 标签页。 <img src="/assets/shared/img/adjust-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. 复制事件 token 并粘贴到 Adapty 中。在凭据下方,有三组事件可从 Adapty 发送到 Adjust。点击[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/adjust-event-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 将通过服务器到服务器的集成方式向 Adjust 发送订阅事件,让你可以在 Adjust 看板中查看所有订阅事件,并将其与获客活动关联起来。 :::important 请注意以下几点: - Adjust 不支持 58 天以前的事件。如果某个事件超过 58 天,Adapty 仍会将其发送给 Adjust,但事件时间戳会被替换为当前时间。 - Adjust 不支持 IPv6。如果你在 **App settings** 或 SDK 激活时禁用了 IP 收集,后端可能只会发送 IPv6,导致追踪失败——请保持 SDK 的 IP 收集功能开启,以确保使用 IPv4。 ::: ### 将您的应用与 Adjust 连接 \{#connect-your-app-to-adjust\} 完成上述步骤后,在您的应用中添加以下两个方法,以建立应用与 Adjust 之间的通信: 1. **向 Adjust 发送订阅数据**:将 Adjust 设备 ID 传入 `setIntegrationIdentifier()` SDK 方法 2. **从 Adjust 接收归因数据**:通过 `updateAttribution()` SDK 方法更新归因数据 如使用 Adjust 5.0 或更高版本,请参考以下示例: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class AdjustModuleImplementation { func updateAdjustAdid() { Adjust.adid { adid in guard let adid else { return } // Adapty SDK 4.x Adapty.setIntegrationIdentifier(.adjustDeviceId(adid)) // Adapty SDK 3.x Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) } } func updateAdjustAttribution() { Adjust.attribution { attribution in guard let attribution = attribution?.dictionary() else { return } // Adapty SDK 4.x Adapty.updateAttribution(attribution, source: .adjust) // Adapty SDK 3.x Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adjust.getAdid { adid -> if (adid == null) return@getAdid Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> if (error != null) { // handle the error } } } Adjust.getAttribution { attribution -> if (attribution == null) return@getAttribution Adapty.updateAttribution(attribution, "adjust") { error -> // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adjust.getAdid(adid -> { if (adid == null) return; Adapty.setIntegrationIdentifier("adjust_device_id", adid, error -> { if (error != null) { // handle the error } }); }); Adjust.getAttribution(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "adjust", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); Adjust.getAdid((adid) => { if (adid) adapty.setIntegrationIdentifier("adjust_device_id", adid); }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } await Adapty().setIntegrationIdentifier( key: "adjust_device_id", value: adid, ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; await Adapty().updateAttribution(attribution, source: "adjust"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers // 1. To update ADID Adjust.GetAdid((adid) => { if (adid == null) { // handle the error return; } Adapty.SetIntegrationIdentifier("adjust_device_id", adid, (error) => { if (error != null) { // handle the error return; } }); }); // 2. To update Attribution // in your adjust configuration scope: adjustConfig.AttributionChangedDelegate = AttributionChangedCallback; public void AttributionChangedCallback(AdjustAttribution attributionData) { var attribution = new Dictionary<string, string>(); if (attributionData.TrackerToken != null) attribution["trackerToken"] = attributionData.TrackerToken; if (attributionData.TrackerName != null) attribution["trackerName"] = attributionData.TrackerName; if (attributionData.Network != null) attribution["network"] = attributionData.Network; if (attributionData.Adgroup != null) attribution["adgroup"] = attributionData.Adgroup; if (attributionData.Creative != null) attribution["creative"] = attributionData.Creative; if (attributionData.ClickLabel != null) attribution["clickLabel"] = attributionData.ClickLabel; if (attributionData.CostType != null) attribution["costType"] = attributionData.CostType; if (attributionData.CostAmount != null) attribution["costAmount"] = attributionData.CostAmount.ToString(); if (attributionData.CostCurrency != null) attribution["costCurrency"] = attributionData.CostCurrency; if (attributionData.FbInstallReferrer != null) attribution["fbInstallReferrer"] = attributionData.FbInstallReferrer; // you will probably need to install Newtonsoft.Json package, if not yet var attributionJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(attribution); Adapty.UpdateAttribution(attributionJsonString, "adjust", (error) => { if (error != null) { // handle the error } }); } ``` </TabItem> </Tabs> ## 事件结构 \{#event-structure\} Adapty 会将所选事件发送到 Adjust,具体配置在 [**Adjust 集成页面**](https://app.adapty.io/integrations/adjust) 的 **Events names** 部分完成。每个事件的结构如下: ```json { "event_token": "EVENT_TOKEN_FROM_CONFIG", "app_token": "APP_TOKEN_FROM_CONFIG", "s2s": 1, "environment": "production", "created_at_unix": 1709294400, "currency": "USD", "revenue": 9.99, "customer_user_id": "user_12345", "external_device_id": "user_12345", "ip_address": "192.168.100.1", "user_agent": "Mozilla/5.0 (Linux; Android 14; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", "android_id": "875646c2-4a56-4211-8931-168532479006", "gps_adid": "875646c2-4a56-4211-8931-168532479006", "callback_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}", "partner_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}" } ``` 位置 | 参数 | 类型 | 描述 | |:---------------------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------| | `app_token` | String | 集成设置中的 Adjust App Token。 | | `event_token` | String | 与特定 Adapty 事件映射的 Adjust Event Token。 | | `s2s` | Integer | 服务器到服务器事件标志。 | | `environment` | String | `sandbox` 或 `production`。 | | `created_at_unix` | Integer | 事件的时间戳(以秒为单位)。 | | `currency` | String | 交易的货币代码(例如 "USD")。仅在收入超过 0.001 时包含,因为 Adjust 要求收入和货币必须一起发送。 | | `revenue` | Float | 交易收入金额。仅在值超过 0.001 时包含。请注意,退款事件不包含收入属性,因为 Adjust 不支持负收入值。 | | `customer_user_id` | String | 用户的 Customer User ID。 | | `external_device_id` | String | 与 `customer_user_id` 相同。 | | `ip_address` | String | 用户的 IP 地址(仅限 IPv4)。 | | `user_agent` | String | 设备 User Agent 字符串。 | | `adid` | String | Adjust Device ID(如已知)。 | | `android_id` | String | **仅限 Android**。Google Advertising ID。 | | `gps_adid` | String | **仅限 Android**。Google Advertising ID。 | | `idfa` | String | **仅限 iOS**。广告主标识符(ID for Advertisers)。 | | `idfv` | String | **仅限 iOS**。供应商标识符(ID for Vendors)。 | | `callback_params` | String | 包含所有可用[事件字段](webhook-event-types-and-fields#for-most-event-types)的 JSON 字符串。仅包含非空字段。 | | `partner_params` | String | 与 `callback_params` 相同。 | ## 故障排查 \{#troubleshooting\} ### 收入数据不一致 \{#revenue-discrepancy\} 如果 Adapty 与 Adjust 之间存在收入数据差异,可能是因为并非所有用户都在使用包含 Adapty SDK 的应用版本。为确保数据一致性,您可以强制用户将应用更新至集成了 Adapty SDK 的版本。 --- # File: airbridge --- --- title: "Airbridge" description: "将 Adapty 与 Airbridge 连接,以跟踪营销和归因数据洞察。" --- [Airbridge](https://www.airbridge.io/) 通过整合从多个设备、平台和渠道收集的数据,为网站和移动应用提供一体化的营销效果分析。借助 Airbridge 的身份解析引擎,您可以将来自网页和应用交互的分散客户身份数据整合为统一的基于人员的身份,从而实现更精准的归因。 Adapty 提供了一套完整的数据,让您可以在一个地方跟踪来自各应用商店的[订阅事件](events)。通过 Adapty,您可以轻松了解订阅用户的行为,掌握他们的偏好,并利用这些信息以有针对性且高效的方式与他们进行沟通。 Adapty 与 Airbridge 的集成通过两种主要方式运作。 1. **从 Airbridge 接收归因数据** 完成 Airbridge 集成设置后,Adapty 将开始从 Airbridge 接收归因数据。您可以在用户页面上轻松访问和查看这些数据。 2. **向 Airbridge 发送订阅事件** Adapty 可以将集成中配置的所有订阅事件发送至 Airbridge。因此,您将能够在 Airbridge 看板中跟踪这些事件。此集成有助于评估广告活动的效果。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Airbridge \{#connect-adapty-to-airbridge\} 要集成 Airbridge,请前往 [Integrations > Airbridge](https://app.adapty.io/integrations/airbridge),将开关从关闭切换为开启,并填写相关字段。 首先,设置凭据以在您的 Airbridge 和 Adapty 账户之间建立连接。需要填写 Airbridge 应用名称和 Airbridge API 令牌。 <img src="/assets/shared/img/2b31d90-Untitled-1_1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 这两项信息均可在您的 Airbridge 看板的 [Third-party Integrations > Adapty](https://app.airbridge.io/app/testad/integrations/third-party/adapty) 部分找到。 <img src="/assets/shared/img/5a2f627-Screenshot_2023-02-21_at_11.19.29_AM.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty API 令牌字段由 Adapty 后端预先生成。您需要复制 Adapty API 令牌的值,并将其粘贴到 Airbridge 看板的 Adapty Authorization Token 字段中。 <img src="/assets/shared/img/ff422d1-CleanShot_2023-03-01_at_17.11.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 配置事件和标签 \{#configure-events-and-tags\} 在凭据下方,有三组您可以从 Adapty 发送到 Airbridge 的事件。 <img src="/assets/shared/img/eb4e3a9-CleanShot_2023-08-22_at_13.58.472x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 只需开启您需要的事件即可。 ### 将您的应用连接到 Airbridge \{#connect-your-app-to-airbridge\} 进行集成时,您需要将 `airbridge_device_id` 传递给 profile builder,并按照以下示例调用 `setIntegrationIdentifier`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "airbridge_device_id", value: AirBridge.deviceUUID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback<String>() { override fun onSuccess(result: String) { Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> if (error != null) { // handle the error } } } override fun onFailure(throwable: Throwable) { } }) ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final deviceUUID = await Airbridge.state.deviceUUID; try { await Adapty().setIntegrationIdentifier( key: "airbridge_device_id", value: deviceUUID, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const deviceId = await Airbridge.state.deviceUUID(); await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> 如需进一步了解 airbridgeDeviceId,请参阅 [Airbridge 文档](https://help.airbridge.io/en/developers/airbridge-device-id-faq)。 在订阅事件发生后,Adapty 最多可能需要 24 小时才能收到 Airbridge 归因数据。Adapty 将立即在看板上显示相关数据。 ## 事件结构 \{#event-structure\} Adapty 会根据 [**Airbridge 集成页面**](https://app.adapty.io/integrations/airbridge)上 **Events names** 部分的配置,将所选事件发送至 Airbridge。每个事件的结构如下: ```json { "user": { "externalUserID": "user_12345", "externalUserEmail": "user@example.com", "attributes": { "is_premium": true } }, "device": { "deviceUUID": "550e8400-e29b-41d4-a716-446655440000", "deviceModel": "iPhone 14 Pro", "osName": "iOS", "osVersion": "17.0.1", "locale": "en-US", "timezone": "America/New_York", "ifa": "00000000-0000-0000-0000-000000000000", "ifv": "00000000-0000-0000-0000-000000000000" }, "app": { "packageName": "com.example.app", "version": "1.2.3" }, "eventUUID": "d4f6f1f4-96fb-4a31-bafd-599fef77be90", "eventTimestamp": 1709294400000, "eventData": { "goal": { "category": "airbridge.subscribe", "customAttributes": { "isTrialConverted": true }, "semanticAttributes": { "transactionID": "GPA.3383-4699-1373-07113", "totalValue": 9.99, "currency": "USD", "period": "P1M", "isRenewal": true, "renewalCount": 2, "products": [ { "productID": "yearly.premium.6999", "name": "yearly.premium.6999", "position": 1 } ] } } } } ``` 其中: | 参数 | 类型 | 描述 | |:----------------------------------------------|:--------|:---------------------------------------------------------------| | `user` | Object | 用户信息。 | | `user.externalUserID` | String | 用户的 Customer User ID。 | | `user.externalUserEmail` | String | 用户的电子邮件地址(如有)。 | | `user.attributes` | Object | 自定义用户属性。 | | `device` | Object | 设备信息。 | | `device.deviceUUID` | String | Airbridge 设备 UUID。 | | `device.deviceModel` | String | 设备型号(例如:"iPhone 14 Pro")。 | | `device.osName` | String | 操作系统名称(例如:"iOS"、"Android")。 | | `device.osVersion` | String | 操作系统版本。 | | `device.ifa` | String | **仅限 iOS**。广告商标识符。 | | `device.ifv` | String | **仅限 iOS**。供应商标识符。 | | `device.gaid` | String | **仅限 Android**。Google 广告 ID。 | | `app` | Object | 应用信息。 | | `app.packageName` | String | 应用的包名 / Bundle ID。 | | `app.version` | String | 应用版本。 | | `eventUUID` | String | Adapty 中事件的唯一 ID。 | | `eventTimestamp` | Long | 事件时间戳(毫秒)。 | | `eventData` | Object | 事件详情。 | | `eventData.goal.category` | String | Airbridge 事件类别(从 Adapty 事件映射而来)。 | | `eventData.goal.semanticAttributes` | Object | 标准事件属性。 | | `...semanticAttributes.transactionID` | String | 应用商店交易 ID。 | | `...semanticAttributes.totalValue` | Float | 收入金额。 | | `...semanticAttributes.currency` | String | 货币代码(例如:"USD")。 | | `...semanticAttributes.period` | String | ISO 8601 时长格式的订阅周期(例如:"P1M")。 | | `...semanticAttributes.isRenewal` | Boolean | 若为续订交易则为 `true`。 | | `...semanticAttributes.renewalCount` | Integer | 成功续订次数。 | | `...semanticAttributes.products` | Array | 事件涉及的产品列表。 | | `...semanticAttributes.products[].productID` | String | 应用商店中的产品 ID(例如:"yearly.premium.6999")。 | | `...semanticAttributes.products[].name` | String | 与 `productID` 相同。 | | `...semanticAttributes.products[].position` | Integer | 产品在列表中的位置(始终为 1)。 | --- # File: apple-search-ads --- --- title: "Apple Ads" description: "将 Apple Ads 与 Adapty 集成,优化订阅转化率。" --- :::important **App settings** 中的 Apple Ads 集成仅用于基础分析以及 SplitMetrics Acquire 和 Asapty 集成。 [Adapty Ads Manager](adapty-ads-manager) 使用单独的连接方式。请在 [Adapty Ads Manager](adapty-ads-manager-get-started) 中连接您的 Apple Ads 账户。 ::: Adapty 可以帮助您获取 Apple Ads 的归因数据,并通过广告系列和关键词细分来分析您的数据图表。Adapty 通过其 SDK 和 AdServices 框架自动收集 Apple Ads 的归因数据。 完成 Apple Ads 集成设置后,Adapty 将开始接收来自 Apple Ads 的归因数据。您可以在用户画像页面轻松访问和查看这些数据。 <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 AdServices 框架 \{#connect-adapty-to-the-adservices-framework\} 通过 [AdServices](https://developer.apple.com/documentation/adservices) 使用 Apple Ads 需要在 Adapty 看板中进行一些配置,同时也需要在应用端启用该功能。按照以下步骤,通过 Adapty 使用 AdServices 框架完成 Apple Ads 的设置: #### 步骤 1:获取公钥 \{#step-1-obtain-public-key\} 在 Adapty 看板中,前往 [Settings -> Apple Ads。](https://app.adapty.io/settings/apple-search-ads) 找到预先生成的公钥(Adapty 会为您提供一对密钥)并复制。 <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note 如果您使用其他服务或自有方案进行 Apple Ads 归因,可以上传您自己的私钥。 ::: #### 第二步:在 Apple Ads 上配置用户管理 \{#step-2-configure-user-management-on-apple-ads\} 在您的 [Apple Ads 账户](https://ads.apple.com/app-store)中,前往 **Settings > User Management** 页面。为使 Adapty 能够获取归因数据,您需要邀请另一个 Apple ID 账户并授予其 API Account Manager 访问权限。您可以使用任何有权限的账户,或专门创建一个新账户。重要的是,您必须能够使用该 Apple ID 登录 Apple Ads。 <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 步骤 3:生成 API 凭据 \{#step-3-generate-api-credentials\} 接下来,在 Apple Ads 中登录新添加的账户,进入 Apple Ads 界面中的 Settings -> API,将之前复制的公钥粘贴到指定字段中,然后生成新的 API 凭据。 #### 步骤 4:在 Adapty 中配置 Apple Ads 凭据 \{#step-4-configure-adapty-with-apple-ads-credentials\} 从 Apple Ads 设置中复制 Client ID、Team ID 和 Key ID 字段。在 Adapty 看板中,将这些凭据粘贴到对应字段中。 <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 将您的应用连接到 AdServices 网络 \{#connect-your-app-to-the-adservices-network\} 完成 [AdServices 框架设置](#connect-the-adservices-framework)后,Adapty 会自动开始收集 Apple Search Ad 归因数据。您无需添加任何 SDK 代码。 对于 iOS 应用,此归因数据将**始终**优先于其他来源的数据。如果不需要此行为,请按照以下说明*禁用* ASA 归因。 ## 禁用集成 \{#disable-integration\} 要关闭 Apple Search Ads 归因,请打开 [**App Settings** -> **Apple Search Ads** 标签页](https://app.adapty.io/settings/apple-search-ads),然后关闭 **Receive Apple Search Ads attribution** 开关。 <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning 请注意,禁用此选项将完全停止接收 ASA 分析数据。因此,ASA 将不再用于数据分析,也不会发送至任何集成。此外,SplitMetrics Acquire 和 Asapty 也将停止运行,因为它们依赖 ASA 归因才能正常工作。 此更改之前已接收的归因数据不受影响。 ::: ## 上传您自己的密钥 \{#uploading-your-own-keys\} :::note 可选 这些步骤不是 Apple Ads 归因所必需的,仅用于与 Asapty 等其他服务或您自己的解决方案配合使用。 ::: 如果您使用其他服务或自己的 ASA 归因解决方案,可以使用您自己的公私密钥对。 ### 第 1 步 \{#step-1\} 在终端中生成私钥 ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` 在 Adapty Settings -> Apple Ads 中上传(点击 Upload private key 按钮) ### 第 2 步 \{#step-2\} 在终端中生成公钥 ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` 您可以在具有 API Account Manager 角色的账户的 Apple Ads 设置中使用此公钥。这样您就可以将生成的 Client ID、Team ID 和 Key ID 值用于 Adapty 和其他服务。 --- # File: switch-from-appsflyer-s2s-api-2-to-3 --- --- title: "从 AppsFlyer S2S API 2 切换到 3" description: "在 Adapty 中从 AppsFlyer S2S API 2 升级到 3。" --- 根据 [AppsFlyer 官方最新公告](https://support.appsflyer.com/hc/en-us/articles/20509378973457-Bulletin-Upgrading-the-AppsFlyer-S2S-API),为了提供更安全的 API 使用体验并减少欺诈行为,AppsFlyer 已对其应用内事件的服务器对服务器(S2S)API 进行了升级。现有端点将在未来被弃用,我们建议您开始规划切换工作。 Adapty 支持 AppsFlyer S2S API 3,并为您提供从 API 2 的无缝切换。请注意,此切换为单向操作,一旦完成切换,将无法回退到 API 2。 从 AppsFlyer S2S API 2 切换到 3 的步骤如下: 1. 打开 [AppsFlyer 网站](https://www.appsflyer.com/home) 并登录。 2. 点击看板左上角的 **Your account name** -> **Security Center**。 <img src="/assets/shared/img/be299ea-appsflyer_security_center.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Manage your account security** 窗口中,点击 **Manage your AppsFlyer API and S2S tokens** 按钮。 4. 如果您没有 S2S 令牌,请点击 **New token** 按钮。如果已有令牌,请直接跳至第 8 步。 <img src="/assets/shared/img/7934920-appsflyer_new_token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在 **New token** 窗口中,输入令牌名称。此名称仅供您参考。 6. 在 **Choose type** 列表中选择 **S2S**。 7. 请务必点击 **Create new token** 按钮以保存新令牌。 8. 在 **Tokens** 窗口中,复制 S2S 令牌。 <img src="/assets/shared/img/d014c25-appsflyer_tokens.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. 在 Adapty 看板中打开 [**Integrations** -> **AppsFlyer**](https://app.adapty.io/integrations/appsflyer)。 10. 在 **AppsFlyer S2S API** 字段中,选择 **API 3**。 <img src="/assets/shared/img/c0b3e72-appsflyer_switch_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. 将复制的 S2S 密钥粘贴到 **Dev key for iOS** 和 **Dev key for Android** 字段中。 12. 点击 **Save** 按钮确认切换。 完成以上操作后,您的集成将立即切换到 AppsFlyer S2S API 3,新事件将发送至新的 URL:`https://api3.appsflyer.com/inappevent`。 --- # File: asapty --- --- title: "Asapty" description: "了解 Asapty 及其在 Adapty 订阅生态系统中的角色。" --- 使用 [Asapty](https://asapty.com/) 集成,您可以优化搜索广告活动。Adapty 将订阅事件发送至 Asapty,让您可以基于 Apple Search Ads 归因在那里构建自定义看板。 此特定集成不会向 Adapty 添加任何归因数据,因为我们已直接从 [ASA](apple-search-ads) 获取了所需的全部数据。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Asapty \{#connect-adapty-to-asapty\} 要集成 Asapty,请在 Adapty 看板中导航至 [Integrations > Asapty](https://app.adapty.io/integrations/asapty),并填写 Asapty ID 字段值。 <img src="/assets/shared/img/895de2b-CleanShot_2023-08-14_at_18.57.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Asapty ID 可在您的 Asapty 账户的 Settings > General 部分找到。 ### 配置事件和标签 \{#configure-events-and-tags\} 在凭据下方,有三组事件可从 Adapty 发送到 Asapty。只需开启您需要的事件即可。查看 Adapty 提供的完整事件列表,请点击[此处](events)。 <img src="/assets/shared/img/58ddf41-CleanShot_2023-08-15_at_15.11.072x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们建议使用 Asapty 提供的默认事件名称。但您也可以根据需要更改事件名称。 ### 将您的应用连接到 Asapty \{#connect-your-app-to-asapty\} 完成上述步骤后,Adapty 会自动从 Asapty 接收归因数据。无需在应用代码中显式请求归因数据。为提高归因数据准确性,请配置 Asapty 在每个事件数据中共享 `customerUserId`。 ## Asapty 事件结构 \{#asapty-event-structure\} Adapty 通过 GET 请求使用查询参数将事件发送到 Asapty。每个事件 URL 格式如下: ``` https://asapty.com/_api/mmpEvents/?source=adapty&asaptyid=a1b2c3d4&keywordid=12345&adgroupid=67890&campaignid=11223&conversiondate=1709294400000&event_name=subscription_renewed&install_time=1709100000&app_name=MyApp&json=%7B%22af_revenue%22%3A%229.99%22%2C%22af_currency%22%3A%22USD%22...%7D ``` 查询参数: | 参数 | 类型 | 描述 | |:-----------------|:-------|:-------------------------------------------------------------| | `source` | String | 始终为 "adapty"。 | | `asaptyid` | String | 您凭据中的 Asapty ID。 | | `keywordid` | String | Apple Search Ads 关键词 ID(如可用)。 | | `adgroupid` | String | Apple Search Ads 广告组 ID(如可用)。 | | `campaignid` | String | Apple Search Ads 广告活动 ID(如可用)。 | | `conversiondate` | Long | 事件时间戳,单位为**毫秒**。 | | `event_name` | String | 事件名称(从 Adapty 事件映射而来)。 | | `install_time` | Long | 安装时间戳,单位为秒。 | | `app_name` | String | Adapty 中的应用名称(如可用)。 | | `json` | String | URL 编码的 JSON 字符串,包含事件详情(见下文)。 | `json` 参数是一个 URL 编码的 JSON 字符串,包含以下字段: | 参数 | 类型 | 描述 | |:--------------------------|:-------|:---------------------------------------| | `af_revenue` | String | 收入金额(字符串形式)。 | | `af_currency` | String | 货币代码(例如 "USD")。 | | `transaction_id` | String | 商店交易 ID。 | | `original_transaction_id` | String | 原始商店交易 ID。 | | `purchase_date` | Long | 购买时间戳,单位为毫秒。 | | `original_purchase_date` | Long | 原始购买时间戳,单位为毫秒。 | | `environment` | String | `Production` 或 `Sandbox`。 | | `vendor_product_id` | String | 商店中的产品 ID。 | | `profile_country` | String | 基于用户 IP 的国家代码。 | | `store_country` | String | 商店用户的国家代码。 | ## 故障排除 \{#troubleshooting\} - 请确保您已在 Adapty 中配置 [Apple Search Ads](apple-search-ads) 并[上传凭据](https://app.adapty.io/settings/apple-search-ads),否则 Asapty 将无法正常工作。 - 只有具有详细非自然量 ASA 归因的用户画像才会将其事件传递至 Asapty。如果归因数据不足,您将看到"The user profile is missing the required integration data."。 - 在配置集成之前创建的用户画像将无法将其事件传递至 Asapty。 - 如果尽管设置正确但与 Adapty 的集成仍无法正常工作,请确保在 [**App Settings** -> **Apple Search Ads** 标签页](https://app.adapty.io/settings/apple-search-ads)中启用了 **Receive Apple Search Ads attribution in Adapty** 开关。 --- # File: branch --- --- title: "Branch" description: "将 Branch 与 Adapty 集成,以追踪深度链接和应用转化。" --- [Branch](https://www.branch.io/) 帮助客户跨设备、渠道和平台触达用户、开展互动并评估效果。这是一个专注于提升移动端营收的易用平台,通过在所有设备、渠道和平台上无缝运作的专属链接来实现这一目标。 Adapty 提供完整的数据集,让你可以在一个地方追踪来自各大应用商店的[订阅事件](events)。借助 Adapty,你可以轻松了解订阅者的行为习惯和偏好,并以有针对性、高效率的方式与他们沟通。 Adapty 与 Branch 的集成主要通过两种方式运作。 1. **从 Branch 接收归因数据** 配置 Branch 集成后,Adapty 将开始从 Branch 接收归因数据。您可以在用户画像页面轻松查看这些数据。 2. **向 Branch 发送订阅事件** Adapty 可以将集成中配置的所有订阅事件发送到 Branch,让你能够在 Branch 看板中追踪这些事件。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Branch \{#connect-adapty-to-branch\} 要集成 Branch,请在 Adapty 看板中前往 [Integrations > Branch](https://app.adapty.io/integrations/branch),将开关从关闭切换为开启,并填写相关字段。 <img src="/assets/shared/img/817a051-CleanShot_2023-08-11_at_15.54.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 要获取 **Branch Key** 的值,请打开 Branch 的[账户设置](https://dashboard.branch.io/account-settings/profile),找到 **Branch Key** 字段。将其填入 Adapty 看板中的 **Key test**(用于沙盒)或 **Key live**(用于生产环境)字段。在 Branch 中,可以切换 Live 和 Tests 环境来获取对应的密钥。 <img src="/assets/shared/img/130e58b-CleanShot_2023-08-11_at_15.24.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 配置事件和标签 \{#configure-events-and-tags\} 在凭证下方,有三组事件可以从 Adapty 发送到 Branch。直接开启你需要的事件即可。点击[此处](events)查看 Adapty 提供的完整事件列表。 你可以发送包含净收入(扣除 Apple/Google 分成后)的事件,也可以仅发送原始收入。此外,还可以勾选按用户本地货币上报的选项。 <img src="/assets/shared/img/a645cf8-CleanShot_2023-08-11_at_15.18.282x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们建议使用 Adapty 提供的默认事件名称,但你也可以根据需要自行修改。 Adapty 将通过服务器到服务器的集成方式向 Branch 发送订阅事件,让你可以在 Branch 看板中查看所有订阅事件,并将其与获客广告系列关联起来。 ### 将您的应用连接到 Branch \{#connect-your-app-to-branch\} 1. 调用 `.setIntegrationIdentifier()` SDK 方法来初始化连接。您可以将 Branch Identity ID 传递给 `customerUserId` 参数。 :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.branchId(<BRANCH_IDENTITY_ID>)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID> ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // login and update attribution and identifier Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> Adapty.updateAttribution(data, "branch") { error -> if (error != null) { //handle the error } } } } // logout Branch.getAutoInstance(context).logout() ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; FlutterBranchSdk.setIdentity('YOUR_USER_ID'); ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers Branch.setIdentity("your user id"); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers import branch from 'react-native-branch'; 2. 使用 `.updateAttribution()` 方法保存归因数据。如果你在上一步中未指定 Branch 用户 ID,请在此处将其传入 `networkUserId` 参数。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class YourBranchImplementation { func initializeBranch() { // 将从 Branch iOS SDK 初始化方法中获取的归因数据传递给 Adapty。 Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in if let data { // Adapty SDK 4.x Adapty.updateAttribution(data, source: .branch) // Adapty SDK 3.x Adapty.updateAttribution(data, source: "branch") } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers //everything is in the above snippet for Android ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID>, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; ```typescript showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { adapty.updateAttribution(params, "branch"); }, }); ``` </TabItem> </Tabs> ## 事件结构 \{#event-structure\} Adapty 会按照 [**Branch 集成页面**](https://app.adapty.io/integrations/branch) 上 **Events names** 部分的配置,将所选事件发送至 Branch。每个事件的结构如下: ```json { "branch_key": "key_live_kaFuWw8WvY7n1ss7...", "name": "PURCHASE", "user_data": { "os": "iOS", "developer_identity": "user_12345", "country": "US", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "aaid": "00000000-0000-0000-0000-000000000000" }, "event_data": { "transaction_id": "GPA.3383-4699-1373-07113", "revenue": 9.99, "currency": "USD" }, "custom_data": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383-4699-1373-07113", "store": "play_store", "environment": "production" } } ``` 其中: | 参数 | 类型 | 描述 | |:-------------------------------|:-------|:------------------------------------------------------------------------------------------------------------------------| | `branch_key` | String | 您的 Branch Key。 | | `name` | String | Branch 事件名称(从 Adapty 事件映射而来,例如 "PURCHASE")。 | | `user_data` | Object | 用户信息。 | | `user_data.os` | String | "Android" 或 "iOS"。 | | `user_data.developer_identity` | String | 用户的 Customer User ID。 | | `user_data.country` | String | 基于用户 IP 的国家代码。 | | `user_data.ip` | String | 用户的 IP 地址。 | | `user_data.idfa` | String | **仅限 iOS**。广告标识符(ID for Advertisers)。 | | `user_data.idfv` | String | **仅限 iOS**。供应商标识符(ID for Vendors)。 | | `user_data.aaid` | String | **仅限 Android**。Google 广告 ID。 | | `event_data` | Object | 标准事件数据指标(仅在 PURCHASE 及类似事件中存在)。 | | `event_data.transaction_id` | String | 应用商店交易 ID。 | | `event_data.revenue` | Float | 收入金额。 | | `event_data.currency` | String | 货币代码(例如 "USD")。 | | `custom_data` | Object | 详细事件属性(包含所有可用的[事件字段](webhook-event-types-and-fields#for-most-event-types))。 | --- # File: facebook-ads --- --- title: "Facebook Ads" description: "将 Facebook Ads 与 Adapty 集成,实现高效的订阅营销。" --- 借助 Facebook Ads 集成,您可以轻松在 Meta Analytics 中查看应用数据统计。Adapty 将事件发送至 Meta Ads Manager,帮助您根据订阅行为创建相似受众,从而获得更好的广告回报。这样,您可以准确了解广告从订阅中带来的收益。 Adapty 与 Facebook Ads 的集成方式如下:Adapty 将您在集成中配置的所有订阅事件发送至 Facebook Ads,帮助您评估广告活动的效果。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Facebook Ads \{#connect-adapty-to-facebook-ads\} 要集成 Facebook Ads 并分析应用数据指标,您可以与 Meta Analytics 建立集成。通过向 Meta Ads Manager 发送事件,您可以根据续订等订阅事件创建相似受众。要配置此集成,请在 Adapty 看板中导航至 [Integrations > Facebook Ads](https://app.adapty.io/integrations/facebookanalytics),并填写所需凭据。 :::note 请注意,Facebook Ads 集成仅适用于已授予 ATT 同意的 iOS 14.5+ 用户。 ::: <img src="/assets/shared/img/fd84ddf-CleanShot_2023-08-15_at_15.45.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. 要查找 App ID,请打开 [App Store Connect](https://appstoreconnect.apple.com/) 中的应用页面,进入 **General** 部分的 **App Information** 页面,在屏幕左下角找到 **Apple ID**。 2. 您需要在 [Meta for Developers](https://developers.facebook.com/) 平台上创建一个应用。登录您的应用后,进入高级设置,在页面顶部即可找到 **App ID**。 <img src="/assets/shared/img/4b326c4-001563-August-23-4tO3JVso.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在您的 Meta SDK 配置中禁用客户端追踪,以防止在 Meta Ads Manager 中重复计算收益。您可以在 Meta 开发者控制台的 **App Settings > Advanced Settings** 中找到此设置。将 **Log in-app events automatically** 设置为"No"。这将确保收益事件仅通过 Adapty 的集成进行追踪。 要追踪安装和使用事件,您需要在代码中激活 Meta SDK。您可以在以下 Meta SDK 文档中找到各平台的实现详情: - [iOS SDK](https://developers.facebook.com/docs/ios/getting-started) - [Android SDK](https://developers.facebook.com/docs/android/getting-started) - [Unity SDK](https://developers.facebook.com/docs/unity/getting-started/canvas) <img src="/assets/shared/img/c4eb8eb-001565-August-23-483KKBbC.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 此集成同样适用于 Android 应用。如果您在 **App Settings** 中配置了 Android SDK,只需设置 **Facebook App ID** 即可。 ### 配置事件和标签 \{#configure-events-and-tags\} 请注意,Facebook Ads 集成专为使用 Meta 投放广告并根据客户行为进行优化的公司而设计。它支持 Meta 的标准事件以实现优化目的。因此,Meta Ads 集成不支持修改事件名称。Adapty 会自动将您的客户事件映射到对应的 Meta 事件,以便进行准确分析。 | Adapty 事件 | Meta Ads 事件 | | :---------------------------- | :-------------------------- | | Subscription initial purchase | Subscribe | | Subscription renewed | Subscribe | | Subscription cancelled | CancelSubscription | | Trial started | StartTrial | | Trial converted | Subscribe | | Trial cancelled | CancelTrial | | Non subscription purchase | fb_mobile_purchase | | Billing issue detected | billing_issue_detected | | Entered grace period | entered_grace_period | | Auto renew off | auto_renew_off | | Auto renew on | auto_renew_on | | Auto renew off subscription | auto_renew_off_subscription | | Auto renew on subscription | auto_renew_on_subscription | StartTrial、Subscribe、CancelSubscription 均为标准事件。 <img src="/assets/shared/img/8a5df9d-CleanShot_2023-07-04_at_12.47.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 要启用特定事件,只需开启您所需的事件开关。如果选择了多个事件名称,Adapty 会将所有选定事件的数据合并到同一个 Adapty 事件名称下。 ### 将您的应用连接到 Facebook Ads \{#connect-your-app-to-facebook-ads\} 按照上述步骤操作后,Facebook 将自动从 Adapty 接收订阅数据。 随着 iOS 14.5 对 IDFA 的政策变更,我们建议您向 Facebook 请求用户的 `facebookAnonymousId`。这样,即使用户的 IDFA 不可用,集成也能继续正常运行。请参阅<InlineTooltip tooltip="set user attributes guide">[iOS](setting-user-attributes)、[Android](android-setting-user-attributes)、[React Native](react-native-setting-user-attributes)、[Flutter](flutter-setting-user-attributes) 和 [Unity](unity-setting-user-attributes)</InlineTooltip>指南来设置此参数。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "facebook_anonymous_id", value: AppEvents.shared.anonymousID ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( "facebook_anonymous_id", AppEventsLogger.getAnonymousAppDeviceGUID(context) ) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const anonymousId = await AppEventsLogger.getAnonymousID(); await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```text There is no official SDK for Flutter ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp anonymousID is not available in the official SDK https://github.com/facebook/facebook-sdk-for-unity/issues/676 ``` </TabItem> </Tabs> ## 事件结构 \{#event-structure\} Adapty 通过 Graph API 向 Facebook Ads(Meta)发送事件。每个事件的结构如下: ```json { "event": "CUSTOM_APP_EVENTS", "app_user_id": "user_12345", "advertiser_id": "00000000-0000-0000-0000-000000000000", "advertiser_tracking_enabled": 1, "application_tracking_enabled": 1, "custom_events": "[{\"_eventName\":\"Subscribe\",\"_logTime\":1709294400,\"fb_num_items\":1,\"fb_content_type\":\"in_app\",\"fb_content_id\":\"yearly.premium.6999\",\"fb_currency\":\"USD\",\"fb_order_id\":\"GPA.3383...\",\"fb_transaction_id\":\"GPA.3383...\",\"_valueToSum\":9.99}]", "extinfo": "[\"i2\",\"com.example.app\",\"1.0.0\",\"100\",\"17.0.1\",\"iPhone14,3\",\"en_US\",\"GMT+3\",\"\",0,0,0,0,0,0,\"GMT+3\"]", "anon_id": "facebook_anon_id_123" } ``` 其中: | 参数 | 类型 | 描述 | |:---|:---|:---| | `event` | String | 固定为 "CUSTOM_APP_EVENTS"。 | | `app_user_id` | String | 用户的 Customer User ID。 | | `advertiser_id` | String | IDFA(iOS)或广告 ID(Android)。 | | `advertiser_tracking_enabled` | Integer | 已启用追踪(ATT 已授权)时为 `1`,否则为 `0`。 | | `application_tracking_enabled` | Integer | 固定为 `1`。 | | `custom_events` | String | 事件对象的 JSON 编码字符串(见下文)。 | | `extinfo` | String | 包含应用/设备信息(如版本、操作系统、语言区域)的 JSON 编码字符串。 | | `anon_id` | String | Facebook 匿名 ID(如果可用)。 | `custom_events` 参数是一个 JSON 编码的对象数组,包含以下字段: | 参数 | 类型 | 描述 | |:---|:---|:---| | `_eventName` | String | Meta Ads 事件名称(例如 "Subscribe")。 | | `_logTime` | Long | 事件的时间戳(秒)。 | | `_valueToSum` | Float | 收益金额。 | | `fb_content_id` | String | 商店中的产品 ID。 | | `fb_currency` | String | 货币代码(例如 "USD")。 | | `fb_order_id` | String | 原始交易 ID。 | | `fb_transaction_id` | String | 原始交易 ID。 | | `fb_content_type` | String | 固定为 "in_app"。 | | `fb_num_items` | Integer | 购买事件固定为 1。 | --- # File: singular --- --- title: "Singular" description: "将 Singular 与 Adapty 集成,分析营销和订阅数据。" --- [Singular](https://www.singular.net/) 是领先的移动归因平台(MMP)之一,能够收集并呈现营销活动数据,帮助企业追踪广告投放效果。 Adapty 提供了一套完整的数据,让您可以在一个地方追踪来自各应用商店的[订阅事件](events)。借助 Adapty,您可以轻松了解订阅用户的行为规律,掌握他们的偏好,并据此进行精准、有效的用户触达。因此,通过此集成,您可以在 Singular 中追踪订阅事件,并精确分析各广告系列带来的收益。 Adapty 可以将所有已在集成中配置的订阅事件发送到 Singular。这样,您就能在 Singular 看板中追踪这些事件。此集成有助于评估广告投放活动的效果。 ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接至 Singular 要设置与 Singular 的集成,请在 Adapty 看板中前往 [Integrations > Singular](https://app.adapty.io/integrations/singular),开启开关并填写相关字段。 可填写的凭据如下: - **Singular SDK Key**:必填。你的 Singular 应用的生产环境 SDK 密钥。 - **Singular SDK Key (Sandbox)**:选填。你的沙盒 Singular 应用的 SDK 密钥。如果未设置,沙盒事件将不会发送至 Singular。 这两个密钥都可以在 Singular 看板的 **Developer tools -> SDK Keys -> SDK Key (**不是** SDK Secret)** 下找到: <img src="/assets/shared/img/4bc50d1-singular_sdk_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在凭据下方,有三组事件可供您从 Adapty 发送到 Singular。点击[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/e67de0c-singular_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们建议使用 Adapty 提供的默认事件名称,但您也可以根据需要自定义事件名称。 Adapty 将通过服务器对服务器集成向 Singular 发送订阅事件,让您可以在 Singular 看板中查看所有订阅事件,并将其与您的获客活动关联起来。 :::warning 在配置集成之前创建的用户画像将无法向 Singular 传送其事件。 ::: ### 将您的应用连接到 Singular \{#connect-your-app-to-singular\} Adapty 与 Singular 之间的集成为服务器对服务器方式,因此无需在您的应用程序中添加任何额外代码。 ## 事件结构 \{#event-structure\} Adapty 通过带有查询参数的 GET 请求将事件发送给 Singular。每个事件的结构如下: ```json { "n": "subscription_renewed", "a": "singular_sdk_key_123", "p": "iOS", "i": "com.example.app", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "ve": "17.0.1", "att_authorization_status": 3, "custom_user_id": "user_12345", "utime": 1709294400, "amt": 9.99, "cur": "USD", "purchase_product_id": "yearly.premium.6999", "purchase_transaction_id": "GPA.3383...", "e": "{\"is_revenue_event\":true,\"amt\":9.99,\"cur\":\"USD\",\"purchase_product_id\":\"yearly.premium.6999\",\"purchase_transaction_id\":\"GPA.3383...\"}" } ``` Where: | 参数 | 类型 | 描述 | |:---------------------------|:--------|:-----------------------------------------------------| | `n` | String | 事件名称(从 Adapty 事件映射而来)。 | | `a` | String | 你的 Singular SDK Key。 | | `p` | String | 平台("iOS" 或 "Android")。 | | `i` | String | 应用商店 App ID(Bundle ID)。 | | `ip` | String | 用户的 IP 地址。 | | `idfa` | String | **仅 iOS**。广告标识符(大写)。 | | `idfv` | String | **仅 iOS**。供应商标识符(大写)。 | | `aifa` | String | **仅 Android**。Google 广告 ID(小写)。 | | `andi` | String | **仅 Android**。Android ID(小写)。 | | `asid` | String | **仅 Android**。App Set ID(小写)。 | | `ve` | String | 操作系统版本。 | | `att_authorization_status` | Integer | **仅 iOS**。ATT 状态(例如,`3` 表示已授权)。 | | `custom_user_id` | String | 用户的 Customer User ID。 | | `utime` | Long | 事件的 UNIX 时间戳(秒)。 | | `amt` | Float | 收入金额。 | | `cur` | String | 货币代码(例如,"USD")。 | | `purchase_product_id` | String | 应用商店中的产品 ID。 | | `purchase_transaction_id` | String | 原始交易 ID。 | | `e` | String | 包含事件详情的 JSON 字符串(见下文)。 | `e` 参数(自定义事件数据)是一个 JSON 编码的字符串,包含: | 参数 | 类型 | 描述 | |:--------------------------|:--------|:-------------------------| | `is_revenue_event` | Boolean | 若事件包含收入则为 `true`。 | | `amt` | Float | 收入金额。 | | `cur` | String | 货币代码。 | | `purchase_product_id` | String | 商店中的产品 ID。 | | `purchase_transaction_id` | String | 原始交易 ID。 | --- # File: tenjin --- --- title: "Tenjin 集成" description: "" --- Tenjin 是一个面向应用开发者和营销人员的移动端归因与分析平台。它提供工具来衡量和优化用户获取活动,并对应用性能和用户行为提供深入洞察。凭借透明灵活的方式,Tenjin 汇聚来自广告网络和应用商店的数据,帮助团队分析 ROI、追踪转化并监控关键绩效指标。 通过将[订阅事件](events)转发到 Tenjin,您可以准确了解转化来自哪里,以及哪些营销活动在所有渠道、平台和设备上带来了最大价值。本质上,Tenjin 看板为营销活动提供了高级分析功能。 通过将 Tenjin 的归因数据转发到 Adapty,您可以用额外的筛选条件丰富 Adapty 的分析数据,并将其用于同期群分析和转化分析。 该集成以两种主要方式运作: 1. **从 Tenjin 获取归因数据** 集成完成后,Adapty 会从 Tenjin 收集归因数据。你可以在 Adapty 看板的用户画像页面查看这些信息。 2. **向 Tenjin 发送订阅事件** Adapty 会实时将购买事件发送到 Tenjin,帮助你直接在 Tenjin 看板中评估广告活动的效果。 | 集成特性 | 描述 | | -------------------------- | ------------------------------------------------------------ | | 计划安排 | 实时 | | 数据方向 | <p>双向传输:</p><ul><li> **Adapty 事件**:从 Adapty 服务器到 Tenjin 服务器</li><li> **Tenjin 归因**:从 Tenjin SDK 到 Adapty 服务器</li></ul> | | Adapty 集成点 | <ul><li> 移动应用代码中的 Tenjin 和 Adapty SDK</li><li> Adapty 服务器</li></ul> | ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 Tenjin \{#connect-adapty-to-tenjin\} 1. 在 Adapty 看板中打开 [**Integrations** -> **Tenjin**](https://app.adapty.io/integrations/tenjin) 页面。 2. 启用开关以激活集成。 <img src="/assets/shared/img/tenjin-toggle.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 登录 [Tenjin 看板](https://tenjin.com/)。 4. 在导航菜单中,前往 **Configuration** -> **Apps**。 <img src="/assets/shared/img/tenjin-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 选择对应平台(iOS 或 Android)的应用,然后切换到 **App and SDK** 标签页。 6. 在 **App and SDK** 标签页中,点击 **SDK Key** 列的 **Copy**。如果你还没有 SDK 密钥,请点击 **Generate SDK Key** 按钮创建一个。 <img src="/assets/shared/img/tenjin-copy-sdk-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 返回 Adapty 看板,将复制的 SDK Key 粘贴到对应平台的字段中: - iOS 应用:粘贴到 **iOS SDK Key** 或 **iOS Sandbox SDK Key** 字段 - Android 应用:粘贴到 **Android SDK Key** 或 **Android Sandbox SDK Key** 字段 :::info Tenjin 的服务端集成没有专门的沙盒模式。请使用单独的 Tenjin 应用,或对生产环境和沙盒事件使用同一个 Key。 ::: <img src="/assets/shared/img/tenjin-keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. 如果你同时有两个平台的应用,请针对另一个平台重复步骤 5-7。 9. (可选)根据需要调整 **How the revenue data should be sent** 部分。有关其设置的详细说明,请参阅[集成设置](configuration#integration-settings)。 10. 点击 **Save** 完成设置。 Adapty 将向 Tenjin 发送购买事件并接收归因数据。你可以在 **Events names** 部分调整事件共享设置。 ### 配置事件和标签 \{#configure-events-and-tags\} Tenjin 仅接受购买事件和 **Trial started** 事件。在 **Events names** 部分,选择要与 Tenjin 共享的事件,以符合您的追踪目标。 <img src="/assets/shared/img/tenjin-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 将您的应用连接到 Tenjin \{#connect-your-app-to-tenjin\} 使用 `Adapty.updateAttribution()` SDK 方法从 Tenjin 获取归因数据,并将其传递给 Adapty。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers func updateTenjinId() { guard let tenjinId = TenjinSDK.getAnalyticsInstallationId() else { return } do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.tenjinAnalyticsInstallationId(tenjinId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "tenjin_analytics_installation_id", value: tenjinId ) } catch { // handle the error } } func updateTenjinAttribution() { let instance = TenjinSDK.getInstance("<YOUR_TENJIN_API_TOKEN>") instance?.getAttributionInfo { info, _ in guard let info else { return } Task { do { // Adapty SDK 4.x try await Adapty.updateAttribution(info, source: .tenjin) // Adapty SDK 3.x try await Adapty.updateAttribution(info, source: "tenjin") } catch { // handle the error } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", tenjinSdk.analyticsInstallationId) { error -> if (error != null) { // handle the error } } tenjinSdk.getAttributionInfo { attribution -> if (attribution == null) return@getAttributionInfo Adapty.updateAttribution(attribution, "tenjin") { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", tenjinSdk.getAnalyticsInstallationId(), error -> { if (error != null) { // handle the error } }); tenjinSdk.getAttributionInfo(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "tenjin", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { final tenjinId = await TenjinSDK.instance.getAnalyticsInstallationId(); if (tenjinId != null) { await Adapty().setIntegrationIdentifier( key: 'tenjin_analytics_installation_id', value: tenjinId, ); } final attribution = await TenjinSDK.instance.getAttributionInfo(); if (attribution != null) { await Adapty().updateAttribution(attribution, source: 'tenjin'); } } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using System.Linq; BaseTenjin instance = Tenjin.getInstance("<SDK_KEY>"); var tenjinId = instance.GetAnalyticsInstallationId(); Adapty.SetIntegrationIdentifier( "tenjin_analytics_installation_id", tenjinId, (error) => { // handle the error }); instance.GetAttributionInfo((attribution) => { var dynamicAttribution = attribution.ToDictionary( kvp => kvp.Key, kvp => (dynamic)kvp.Value ); Adapty.UpdateAttribution( dynamicAttribution, "tenjin", (error) => { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog() // ... try { await adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", await Tenjin.getAnalyticsInstallationId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## 事件结构 \{#event-structure\} Adapty 会根据 [**Tenjin 集成页面**](https://app.adapty.io/integrations/tenjin) 上 **Events names** 部分的配置,将所选事件发送至 Tenjin。每个事件的结构如下: ```json showLineNumbers title="Json" { "price": 99.0, "locale": "en-US", "country": "ME", "postcut": "false", "currency": "USD", "platform": "ios", "quantity": 1, "bundle_id": "com.adapty.adaptydemoapp", "ip_address": "127.0.0.1", "os_version": "18.1.1", "product_id": "month.premium.99", "app_version": "3.2.0", "sdk_version": "server", "device_model": "iPhone 13 Mini", "advertising_id": "00000000-0000-0000-0000-000000000000", "os_version_release": "18.1.1", "developer_device_id": "00000000-0000-0000-0000-000000000000", "analytics_installation_id": "00000000-0000-0000-0000-000000000000" } ``` 在哪里 | **参数** | **类型** | **描述** | | ----------------------------- | ---------------- | ------------------------------------------------------------ | | **price** | Float | 购买商品的单价,以标准货币单位计(例如,USD 以美元报告)。 | | **locale** | String | 设备的语言区域。Android:`Locale.getDefault().toString()`;iOS:`[[NSLocale currentLocale] localeIdentifier]`。 | | **country** | String | ISO 语言区域国家代码标准(例如,US 代表美国)。 | | **postcut** | String (Boolean) | 表示购买是否在平台抽成后发送。1 为是,0 为否。 | | **currency** | String | ISO 货币代码(例如,USD 代表美元)。 | | **platform** | String | 设备平台(例如,ios、android、windows、amazon)。 | | **quantity** | Integer | 购买的数量。 | | **bundle_id** | String | 应用的 Bundle 标识符(例如,`com.example.app`)。 | | **ip_address** | String (IPv4) | 用户的 IP 地址,用于查找所在国家。 | | **os_version** | String | 设备的操作系统版本。Android:`String.valueOf(Build.VERSION.SDK_INT)`;iOS:`[[UIDevice currentDevice] systemVersion]`。 | | **product_id** | String | 所购产品的唯一标识符。 | | **app_version** | Float, Decimal | 应用版本号。Android:`context.getPackageManager().getPackageInfo()`;iOS:`[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]`。 | | **sdk_version** | String | 当前使用的 SDK 版本,始终设置为 `server`。 | | **device_model** | String | 设备型号。Android:`Build.MODEL`;iOS:`sysctl("hw.machine")`。 | | **advertising_id** | UUID | 设备的广告 ID。Android 必填;iOS 可为空或全零。 | | **os_version_release** | String | 操作系统版本发行号。Android:`String.valueOf(Build.VERSION.RELEASE)`;iOS:`[[UIDevice currentDevice] systemVersion]`。 | | **developer_device_id** | UUID | 供应商标识符(仅限 iOS)。 | | **analytics_installation_id** | UUID | 分析安装 ID。详情请参阅 `https://docs.tenjin.com` 文档。 | --- # File: amplitude --- --- title: "Amplitude" description: "将 Amplitude 与 Adapty 集成,获取更深入的用户行为洞察。" --- [Amplitude](https://amplitude.com/) 是一款功能强大的移动分析服务。借助 Adapty,你可以轻松地将事件发送到 Amplitude,了解用户行为,并做出更明智的决策。 Adapty 提供完整的数据集,让你可以在一个地方追踪来自各应用商店的[订阅事件](events),并将其发送到你的 Amplitude 账户。这样你就能在 Amplitude 中将用户行为与其付款历史相匹配,从而为产品决策提供依据。 ### 如何设置 Amplitude 集成 \{#how-to-set-up-amplitude-integration\} 在 Adapty 中,你可以为来自 Apple 或 Stripe 沙盒环境或 Google 测试账户的**生产**事件和**测试事件**分别设置独立的流程。 - 对于生产事件,请在 Adapty 中输入 Amplitude 看板中的 **Production** API 密钥,每个平台(iOS、Android 和 Stripe)使用各自独立的 API 密钥。 - 对于测试事件,请按需填写 **Sandbox** 字段。 设置 Amplitude 集成的步骤: 1. 在 Adapty 看板中打开 [**Integrations** -> **Amplitude**](https://app.adapty.io/integrations/amplitude)。 <img src="/assets/shared/img/3b50552-CleanShot_2023-08-15_at_16.47.102x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 打开 **Amplitude integration** 开关以启用该集成。 3. 填写集成字段: | 字段 | 说明 | | ------------------------------------------ | ------------------------------------------------------------ | | **Amplitude iOS/ Android/ Stripe API key** | 将 iOS/ Android/ Stripe 对应的 Amplitude **API Key** 填入 Adapty。可在 Amplitude 的 **Project settings** 中找到。如需帮助,请查阅 [Amplitude 文档](https://amplitude.com/docs/apis/authentication)。建议先使用 **Sandbox** 密钥进行测试,测试通过后再切换为 **Production** 密钥。 | <img src="/assets/shared/img/2297782-CleanShot_2023-08-15_at_16.53.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 可选设置,用于进一步自定义: | 参数 | 说明 | | --------------------------------------- | ------------------------------------------------------------ | | **How the revenue data should be sent** | 选择发送含税含佣金的总收入,还是扣除税费和佣金后的净收入。详情请参阅[商店佣金与税费](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)。 | | **Exclude historical events** | 选择是否排除 Adapty SDK 安装前的事件,以防止数据重复。例如,如果用户在 1 月 10 日订阅,但在 3 月 6 日才安装 Adapty SDK,则 Adapty 仅会发送 3 月 6 日起的事件。 | | **Send User Attributes** | 选择此选项以发送用户特定属性,例如语言偏好。 | | **Always populate user_id** | Adapty 会自动将 `device_id` 作为 `amplitudeDeviceId` 发送。对于 `user_id`,此设置定义以下行为:<ul><li>**开启**:若 `amplitudeUserId` 或 `customer_user_id` 不可用,则发送 Adapty 的 `profile_id`。</li><li>**关闭**:若两个 ID 均不可用,则 `user_id` 留空。</li></ul> | 5. 选择你希望接收的事件,并[映射其名称](amplitude#events-and-tags)。 6. 点击 **Save** 保存更改。 点击 **Save** 后,Adapty 将开始向 Amplitude 发送事件。 除事件外,Adapty 还会将[订阅状态](subscription-status)和订阅产品 ID 发送至 [Amplitude 用户属性](https://amplitude.com/docs/data/user-properties-and-events)。 ### 事件与标签 \{#events-and-tags\} 在凭据下方,有三组事件可供你从 Adapty 发送至 Amplitude,直接开启所需事件即可。完整的 Adapty 事件列表请参阅[此处](events)。 <img src="/assets/shared/img/da67694-CleanShot_2023-08-15_at_16.52.352x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们建议使用 Adapty 提供的默认事件名称。当然,你也可以根据需要修改事件名称。Adapty 将通过服务器到服务器的集成方式向 Amplitude 发送订阅事件,让你可以在 Amplitude 看板中查看所有订阅事件。 ### SDK 配置 \{#sdk-configuration\} 使用 `setIntegrationIdentifier()` 方法设置 `amplitude_device_id` 参数,这是设置集成的必要步骤。 如果你有用户注册流程,也可以同时传入 `amplitude_user_id`。 :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **设置 amplitudeDeviceId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_device_id", value: Amplitude.instance().deviceId ) } catch { // handle the error } ``` **设置 amplitudeUserId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **设置 amplitudeDeviceId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId // Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> if (error != null) { // handle the error } } ``` **设置 amplitudeUserId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeUserId = amplitude.store.userId // Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **设置 amplitudeDeviceId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_device_id", value: amplitude.getDeviceId(), ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` **设置 amplitudeUserId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **设置 amplitudeDeviceId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_device_id", amplitude.getDeviceId(), (error) => { // handle the error }); ``` **设置 amplitudeUserId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_user_id", "YOUR_AMPLITUDE_USER_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> **设置 amplitudeDeviceId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` **设置 amplitudeUserId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Amplitude 事件结构 \{#amplitude-event-structure\} Adapty 通过 HTTP API v2 向 Amplitude 发送事件,每个事件的结构如下: ```json { "api_key": "your_amplitude_api_key", "events": [ { "partner_id": "adapty", "event_type": "subscription_renewed", "time": 1709294400000, "insert_id": "123e4567-e89b-12d3-a456-426614174000", "user_id": "user_12345", "device_id": "device_12345", "platform": "iOS", "os_name": "iOS", "productId": "yearly.premium.6999", "revenue": 9.99, "event_properties": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store" }, "user_properties": { "subscription_state": "subscribed", "subscription_product": "yearly.premium.6999" } } ] } ``` 各字段说明: | 参数 | 类型 | 说明 | |:----------------------------|:-------|:---------------------------------------------------------------------| | `api_key` | String | 你的 Amplitude API 密钥。 | | `events` | Array | 事件对象列表(Adapty 每次发送一条)。 | | `events[].partner_id` | String | 固定为 "adapty"。 | | `events[].event_type` | String | 事件名称(从 Adapty 事件映射而来)。 | | `events[].time` | Long | 事件时间戳,单位为毫秒。 | | `events[].insert_id` | String | 唯一事件 ID(UUID)。 | | `events[].user_id` | String | Amplitude 用户 ID 或客户用户 ID。 | | `events[].device_id` | String | Amplitude 设备 ID。 | | `events[].platform` | String | 平台(如 "iOS"、"Android")。 | | `events[].os_name` | String | 操作系统名称。 | | `events[].productId` | String | 应用商店中的产品 ID。 | | `events[].revenue` | Float | 收入金额。 | | `events[].event_properties` | Object | 详细事件属性(包含所有可用的[事件字段](webhook-event-types-and-fields#for-most-event-types))。 | | `events[].user_properties` | Object | 用户属性,如订阅状态。 | --- # File: appmetrica --- --- title: "AppMetrica" description: "将 AppMetrica 与 Adapty 集成,深入分析订阅数据。" --- [AppMetrica](https://appmetrica.yandex.com/about) 是一款免费的分析工具,可帮助您实时追踪用户行为并分析移动应用的表现。通过将 AppMetrica 与 Adapty 集成,您可以更深入地了解订阅数据指标和用户参与情况。 ## 如何设置 AppMetrica 集成 \{#how-to-set-up-appmetrica-integration\} 设置 AppMetrica 集成主要分为两个步骤: 1. 在 Adapty 看板中配置集成 2. 在应用代码中设置集成 ### 看板配置 \{#dashboard-configuration\} 要设置 AppMetrica 集成: 1. 打开 [AppMetrica 应用列表](https://appmetrica.yandex.ru/application/list) 2. 选择您要追踪的应用 3. 前往 **Settings > Main**,复制 **Application ID** 和 **Post API key** <img src="/assets/shared/img/appmetrica.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 Adapty 看板中前往 [Integrations > AppMetrica](https://app.adapty.io/integrations/appmetrica) 5. 粘贴您的 AppMetrica 凭据。 <img src="/assets/shared/img/appmetrica_creds.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 事件与标签 \{#events-and-tags\} Adapty 允许您向 AppMetrica 发送三组事件。您可以启用需要追踪的事件来监控应用表现。有关可用事件的完整列表,请参阅我们的[事件文档](events)。 :::note AppMetrica 每 4 小时同步一次事件,因此事件出现在您的看板中可能会有延迟。 ::: <img src="/assets/shared/img/6ed2d88-CleanShot_2023-08-18_at_14.59.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip 我们建议使用 Adapty 的默认事件名称以保持一致性,但您也可以自定义事件名称以匹配您现有的分析设置。 ::: ### 收入设置 \{#revenue-settings\} 默认情况下,Adapty 将收入数据作为事件属性发送,这些数据会显示在 AppMetrica 的 Events 报告中。您可以配置收入数据的计算和显示方式: - **Revenue calculation**(收入计算):选择收入值的计算方式,以符合您的财务报告需求: - **Gross revenue**(总收入):显示扣除任何费用前的总收入,便于追踪客户支付的全额金额 - **Proceeds after store commission**(扣除应用商店佣金后的收入):显示扣除 App Store/Play Store 费用后的收入,帮助您追踪实际收益 - **Proceeds after store commission and taxes**(扣除应用商店佣金和税费后的收入):显示同时扣除商店费用和适用税费后的净收入,提供最准确的收益情况 - **Report user's currency**(报告用户货币):启用后,销售额将以用户本地货币报告,便于按地区分析收入。禁用后,所有销售额将转换为美元,以便在不同市场间进行一致的报告。 - **Send revenue events**(发送收入事件):启用此选项后,收入数据不仅会出现在 Events 报告中,还会出现在 AppMetrica 的[应用内及广告收入](https://appmetrica.yandex.com/docs/en/mobile-reports/revenue-report)报告中。请确保您没有从其他地方发送收入数据,否则可能导致数据重复。 - **Exclude historical events**(排除历史事件):启用后,Adapty 不会发送用户在安装带有 Adapty SDK 的应用之前发生的事件。如果您在集成 Adapty 之前已向分析工具发送事件,此选项有助于避免数据重复。 <img src="/assets/shared/img/appmetrica_revenue.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### SDK 配置 \{#sdk-configuration\} 要在应用中启用 AppMetrica 集成,您需要设置两个标识符: 1. `appmetrica_device_id`:基础集成所必需 2. `appmetrica_profile_id`:可选,但如果您的应用有用户注册功能则推荐设置 使用 `setIntegrationIdentifier()` 方法来设置这些值。以下是各平台的实现方式: :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **设置 appmetrica_device_id** ```swift showLineNumbers AppMetrica.requestStartupIdentifiers(on: nil) { ids, error in if let error { // handle AppMetrica error return } guard let deviceIDHash = ids?[.deviceIDHashKey] as? String else { // handle AppMetrica error return } Task { do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIDHash ) } catch { // handle the error } } } ``` **设置 appmetrica_profile_id** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **设置 appmetrica_device_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` **设置 appmetrica_profile_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **设置 appmetrica_device_id** ```javascript showLineNumbers final startupParams = await AppMetrica.requestStartupParams([AppMetricaStartupParams.deviceIdHashKey]); final deviceIdHash = startupParams.result?.deviceIdHash; if (deviceIdHash != null) { try { await Adapty().setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIdHash, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` **设置 appmetrica_profile_id** ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **设置 appmetrica_device_id** ```csharp showLineNumbers using AdaptySDK; using Io.AppMetrica; AppMetrica.RequestStartupParams( (result, errorReason) => { string deviceIdHash = result.DeviceIdHash; if (deviceIdHash != null) { Adapty.SetIntegrationIdentifier( "appmetrica_device_id", deviceIdHash, (error) => { // handle the error }); } }, new List<string>() { StartupParamsKey.AppMetricaDeviceIDHash } ); ``` **设置 appmetrica_profile_id** ```csharp showLineNumbers Adapty.SetIntegrationIdentifier( "appmetrica_profile_id", "YOUR_APPMETRICA_PROFILE_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="RN" label="React Native (TS)" default> **设置 appmetrica_device_id** ```typescript showLineNumbers // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceIdHash = params?.deviceIdHash if (deviceIdHash) { try { await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_HASH_KEY]) ``` **设置 appmetrica_profile_id** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## AppMetrica 事件结构 \{#appmetrica-event-structure\} Adapty 通过 POST 请求将事件发送到 AppMetrica,参数以查询参数的形式传递。对于每个 Adapty 事件,AppMetrica 最多会收到**两个独立的请求**: 1. **用户画像事件**(始终发送):包含事件元数据 2. **收入事件**(可选):如果在 Adapty 看板中启用了"Send revenue events"选项,则包含收入数据 ### 用户画像事件请求 \{#profile-event-request\} 发送至:`https://api.appmetrica.yandex.ru/logs/v1/import/events` 带查询参数的示例 URL: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/events?post_api_key=your_key&application_id=your_app_id&event_name=subscription_renewed&event_timestamp=1709294400&event_json=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&ios_ifv=12345678-1234-1234-1234-123456789012&profile_id=user_12345&session_type=foreground ``` 查询参数: | 参数 | 类型 | 描述 | |:-----------------------|:-------|:-----------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | 您的 AppMetrica Post API Key。 | | `application_id` | String | 您的 AppMetrica Application ID。 | | `event_name` | String | 事件名称(从 Adapty 事件映射而来)。 | | `event_timestamp` | Long | 事件的 UNIX 时间戳(秒)。如果超过 7 天则截断为最近 7 天。 | | `event_json` | String | URL 编码的 JSON 字符串,包含所有可用的[事件字段](webhook-event-types-and-fields#for-most-event-types)。仅包含非空字段。 | | `os_name` | String | "ios" 或 "android"。 | | `profile_id` | String | AppMetrica 用户画像 ID(如已设置),否则为 Customer User ID(如可用)。 | | `appmetrica_device_id` | String | AppMetrica 设备 ID 哈希值。仅在 `profile_id` 不可用时发送。 | | `session_type` | String | 始终为 "foreground"。 | | `ios_ifa` | String | **仅 iOS**。广告商标识符。 | | `ios_ifv` | String | **仅 iOS**。供应商标识符。 | | `google_aid` | String | **仅 Android**。Google 广告 ID。 | ### 收入事件请求(可选) \{#revenue-event-request-optional\} 发送至:`https://api.appmetrica.yandex.ru/logs/v1/import/revenue` 仅当在 Adapty 看板集成设置中启用了"Send revenue events"选项时,才会发送此请求。 带查询参数的示例 URL: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/revenue?post_api_key=your_key&application_id=your_app_id&revenue_event_type=subscription_renewed&price=9.99¤cy=USD&product_id=yearly.premium&quantity=1&transaction_id=GPA.3383...&payload=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&profile_id=user_12345&session_type=foreground ``` 查询参数: | 参数 | 类型 | 描述 | |:---------------------|:--------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | 您的 AppMetrica Post API Key。 | | `application_id` | String | 您的 AppMetrica Application ID。 | | `revenue_event_type` | String | 收入事件类型(例如 "subscription_renewed"、"refund"、"intro_started")。请参阅 [AppMetrica 事件映射](#revenue-event-type-mapping)。 | | `price` | Float | 收入金额(基于您的收入计算设置)。 | | `currency` | String | 货币代码(例如 "USD")。 | | `product_id` | String | 商店中的产品 ID。 | | `quantity` | Integer | 始终为 1。 | | `transaction_id` | String | 商店交易 ID。 | | `payload` | String | URL 编码的 JSON 字符串,包含事件详情。如果超过 30KB,将按重要性顺序移除可选字段以保留最关键数据。 | | `os_name` | String | "ios" 或 "android"。 | | `profile_id` | String | AppMetrica 用户画像 ID(如已设置),否则为 Customer User ID(如可用)。 | | `appmetrica_device_id` | String | AppMetrica 设备 ID 哈希值。仅在 `profile_id` 不可用时发送。 | | `session_type` | String | 始终为 "foreground"。 | | `ios_ifa` | String | **仅 iOS**。广告商标识符。 | | `ios_ifv` | String | **仅 iOS**。供应商标识符。 | | `google_aid` | String | **仅 Android**。Google 广告 ID。 | --- # File: firebase-and-google-analytics --- --- title: "Firebase 和 Google Analytics" description: "将 Adapty 订阅事件发送到 Firebase 和 Google Analytics——驱动受众分群、远程配置、Google Ads 归因及其他 Firebase 工具。" --- Adapty 可以将订阅事件(购买、续订、退款、试用开始等)发送到 Firebase 和 Google Analytics,一次集成即可将数据同时传递到两个平台。 :::warning 你需要同时拥有 Firebase 项目和关联的 Google Analytics 媒体资源——即使你只使用其中一个。Firebase 和 Google Analytics 在两个控制台中共享同一份数据。 ::: 购买和退款事件会附带收入、货币和产品详情。同样的数据会同时流入 Firebase 的移动端工具(受众群体、Remote Config 等)以及 Google Analytics 的报告。 这是一个数据分析集成,而非 Google Ads 归因工具——请参阅[限制条件](#limitations)。 ## 通过此集成可以实现什么 \{#what-you-can-do-with-this-integration\} Adapty 按 `subscription_state`(`subscribed`、`active_trial`、`never_subscribed` 等)对用户进行分组,并将订阅生命周期事件转发至 Firebase 和 Google Analytics。 - **目标受众**:在 Firebase 和 Google Analytics 中构建订阅者目标受众,用于外部渠道——Google Ads 再营销、FCM 活动、相似受众建模。 - **Google Ads 转化** *(Google Analytics)*:将 `purchase` 和 `refund` 用作 Google Ads 转化目标。 - **Firebase Remote Config**:无需更新应用即可更改使用限制、文案或功能开关,并根据订阅状态设置条件。(请勿与[Adapty 远程配置](customize-paywall-with-remote-config)混淆,后者用于配置流程/付费墙内容。) - **Cloud Messaging**:在应用关闭时向流失订阅者推送通知。 - **跨设备追踪** *(Google Analytics)*:Adapty 将 `customer_user_id` 发送至 Google Analytics,使 Google Ads 能够跨设备追踪同一用户。 - **转化漏斗** *(Google Analytics)*:查看用户在转化或流失前在应用内的行为路径。 - **趋势预测**:根据购买历史预测流失率和消费情况。 - **A/B 测试**:在订阅者同期群上测试应用功能——例如,向试用用户推出新的导航模式并衡量会话时长。(如需测试付费墙实验变体,请使用 [Adapty A/B 测试](ab-tests)。) ## 集成工作原理 \{#how-the-integration-works\} 1. 当用户首次打开您的应用时,Firebase SDK 会为该安装创建一个唯一标识符——**Firebase App Instance ID**。Firebase 和 Google Analytics 使用此 ID 来识别每个事件对应的安装来源。 2. 您的应用将 Firebase App Instance ID 传递给 Adapty SDK,Adapty 将用户的用户画像与该 Firebase 安装关联起来。 3. 当用户完成购买时,Adapty 服务器会将事件连同 Firebase App Instance ID 一起转发给 Firebase。数据通过服务器之间直接交换,不经过应用本身。 4. Firebase 将购买事件与对应的安装匹配,这样您就能在看板中看到购买行为与用户在应用内其他操作的完整关联。 :::note Firebase App Instance ID 是设备级别的标识符。当同一个 Adapty 用户在其他设备上打开应用时,新的 Firebase ID 会覆盖之前的记录。请使用 [customer user ID](identifying-users) 来跨设备维持用户身份。 ::: ### Stripe 购买 \{#stripe-purchases\} Stripe 购买仅在买家**首先**启动移动应用后才会到达 Firebase。Firebase App Instance ID 必须在 Stripe 购买触发**之前**完成设置。 对于 App Store 和 Play Store 的购买,这一过程会自动完成。它们发生在移动应用内部,与 [`setIntegrationIdentifier`](#configure-your-app-code) 调用同步进行。因此在购买触发时,Firebase ID 已经存在。 Stripe 购买在应用外部、在你的服务器上发起。你的移动应用必须在启动时调用 `setIntegrationIdentifier`——在任何 Stripe 购买触发之前。否则 Adapty 没有可附加的 ID,Stripe 购买就永远不会到达 Firebase。 ### 局限性 \{#limitations\} - **不是 Google Ads 归因工具。** 此集成将 Adapty 事件发送到 Firebase 和 Google Analytics 用于数据分析,不会将应用安装归因到 Google Ads 广告系列(UAC / Universal App Campaigns),也无法区分付费流量与自然流量。如需安装归因,请使用 Adapty 内置的 [Adapty 归因](adapty-user-acquisition)功能。 - **不支持历史数据回填。** Adapty 仅从启用集成之时起转发事件——过去的购买、续订和退款不会同步到 Firebase。(历史数据保存在 Adapty 的 [S3](s3-exports) / [GCS](google-cloud-storage) 导出中,但将其导入 Firebase 不在本集成的支持范围内。) - **纯 Web 买家的数据不会进入 Firebase。** Adapty 通过移动应用设置的 Firebase App Instance ID 将购买事件转发至 Firebase。从未安装过应用的买家没有该 ID,其购买记录不会进入 Firebase。详见上文 [Stripe 购买](#stripe-purchases),也可考虑使用 [FunnelFox 的 Firebase 集成](https://funnelfox.com/docs/integrations/subscription-management/adapty)或 Google Analytics Web 数据流进行 Web 端追踪。 - **不支持 Paddle 购买。** 本集成目前不支持 Paddle。Paddle 购买记录保留在 Adapty Analytics 中,不会通过此路径同步到 Firebase。 - **Stripe 购买受 Stripe 特有限制约束。** 详见 [Stripe 集成限制](stripe#current-limitations)。 ## 配置说明 \{#setup-instructions\} ### 配置 Firebase \{#configure-firebase\} 1. 打开 [Firebase 控制台](https://console.firebase.google.com/),选择或新建一个项目。为了避免沙盒事件污染生产环境数据,建议为开发构建单独创建一个 Firebase 项目。 2. 将项目关联到 Google Analytics 属性。Firebase 在创建项目时会提示你进行关联,也可以之后通过 **Project settings** > **Integrations** > **Google Analytics** 添加。 3. 在 **Project settings** > **General** > **Your apps** 中,为你发布的每个平台(iOS / Android / Web)添加一条记录。如果使用 Stripe,请添加一条 Web 应用记录——Stripe 没有原生应用类型。每条记录都会生成一个唯一的 **Firebase App ID** 以及 Google Analytics 中对应的数据流。在设置过程中,你需要将该 ID 填入 Adapty 的 Firebase 集成配置中。 ### 配置 Adapty \{#configure-adapty\} 1. 在 Adapty 看板中打开 [**Integrations** > **Firebase**](https://app.adapty.io/integrations/firebase)。 2. 开启 **Firebase integration** 开关。 3. 为每个发布平台填写凭据。Adapty 需要每个平台的 **Firebase App ID** 和 **Google Analytics secret**——iOS、Android 和 Stripe 各自的值不同。 | Adapty 看板 | Google Analytics | 获取位置 | | --- | --- | --- | | **Firebase App ID** | **App ID** | Firebase Console > **Project settings** > **General** > **Your apps** | | **Google Analytics secret** | **Measurement Protocol API secret** | Google Analytics > **Admin** > **Data streams** > **Measurement Protocol API secrets** > **Create** | 4. 配置 Adapty 如何转发收入和用户数据。这四个控制项共享看板中的同一行: - **Revenue definition** 下拉菜单:可选总收入、扣除商店佣金后的收入,或[扣除商店佣金和税费后的收入](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)。 - **Send user properties** 开关:开启后,事件中将包含 `subscription_state` 和 `subscription_product_id`。如需在报告或目标受众中使用这些字段,请参阅[在报告中使用订阅数据](#use-subscription-data-in-reports-and-audiences)。 - **Report user's currency** 开关:开启后,Adapty 会在转发至 Google Analytics 之前,将每笔交易的本地货币换算为您账户的报告货币。 - **Send trial price** 开关:试用开始是非常有价值的事件——大多数付费用户往往都是从试用开始的。但 Google Ads 的出价优化只会将带有收入的事件视为值得追踪的目标。开启此开关可为每次试用分配一个占位价格,使 Google 将其视为转化事件,从而将广告投放优化方向指向吸引试用用户。开启后,将显示 **Trial price percentage** 字段。将其设置为 Google 应将每次试用视为等同于完整订阅价格的比例——例如,填写 `50%` 表示在试用期间上报订阅价格的一半。 5. 将 Adapty 事件映射到 Firebase/Google Analytics 事件名称。Adapty 分别为 **iOS** 和 **Android** 提供独立的事件映射,因此你可以为不同平台使用不同的名称。**Stripe 购买使用 iOS 事件映射** —— 没有单独的 Stripe 映射。 Google Analytics 对 Measurement Protocol 有严格的字符限制——事件名称最多 40 个字符,用户属性名称最多 24 个字符,属性值最多 36 个字符。超出限制的自定义事件会被 Google Analytics 静默丢弃。 :::warning 部分事件在 Firebase 和 Google Analytics 中使用了保留的电商词汇——`purchase` 和 `refund`。Google Ads 转化导入、Google Analytics 收入报告以及预测受众功能均依赖这些固定字符串。除非不需要上述功能,否则请勿修改默认值。 ::: 6. 点击 **Save**。几分钟内,Adapty 就会开始将事件转发到 Firebase。 ### 配置你的应用代码 \{#configure-your-app-code\} :::tip 请确保你的应用已集成 <InlineTooltip tooltip="Firebase SDK">[iOS](https://firebase.google.com/docs/ios/setup)、[Android](https://firebase.google.com/docs/android/setup)、[Flutter](https://firebase.google.com/docs/flutter/setup)、[Unity](https://firebase.google.com/docs/unity/setup)、[React Native](https://rnfirebase.io/) 和 [Capacitor](https://github.com/capawesome-team/capacitor-firebase)</InlineTooltip>。 ::: Adapty 需要在每个事件中包含 **Firebase App Instance ID**,否则数据将无法送达 Firebase(`MISSING_INTEGRATION_ID`)。 在 `FirebaseApp.configure()` 和 `Adapty.activate()` 调用之后,向 Firebase SDK 请求 App Instance ID,并通过 `setIntegrationIdentifier` 将其传递给 Adapty。每次应用启动时执行一次,且须在任何购买流程开始之前完成。 :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> <Tabs groupId="sdk-version" queryString> <TabItem value="v4" label="Adapty SDK v4+"> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier(.firebaseAppInstanceId(appInstanceId)) } catch { // handle the error } } ``` </TabItem> <TabItem value="v3" label="Adapty SDK v3" default> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId ) } catch { // handle the error } } ``` </TabItem> </Tabs> </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)"> ```java showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { if (error != null) { // handle the error } }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; if (appInstanceId != null) { try { await Adapty().setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers using AdaptySDK; using Firebase.Analytics; FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle the error return; } Adapty.SetIntegrationIdentifier( "firebase_app_instance_id", task.Result, (error) => { // handle the error } ); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers try { const appInstanceId = await analytics().getAppInstanceId(); if (appInstanceId) { await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ### 验证集成 \{#verify-the-integration\} 确认事件是否正常传输最快的方式是使用 Firebase DebugView: 1. 在测试设备上,[启用 Firebase 调试模式](https://firebase.google.com/docs/analytics/debugview#enable_debug_mode)后运行你的应用。 2. 触发一次沙盒购买或你在 Adapty 看板中启用的任意事件。 3. 打开 Firebase Console > **Analytics** > **DebugView**。事件会在几秒内显示,并附带所有参数。 标准报告——实时报告、报表、目标受众——会在几分钟到 24 小时内填充,具体取决于报告类型。DebugView 是唯一能实时确认事件的地方。 ## 在报告和目标受众中使用订阅数据 \{#use-subscription-data-in-reports-and-audiences\} 在 Adapty 看板中开启 **Send user properties**([配置 Adapty](#configure-adapty),第 4 步)。若未开启,Adapty 不会转发 `subscription_state` 或 `subscription_product_id`,本节其余内容将不会生效。 默认情况下,Firebase 和 Google Analytics 不会公开用户属性。需要将每个属性注册为自定义维度,这样 `subscription_state` 和 `subscription_product_id` 才能在报告、探索和受众中使用。请在 Google Analytics 管理后台配置维度。配置完成后,即可在 Firebase 和 Google Analytics 中查询这些维度——两者共用同一个后端。 适用于为付费用户创建 Google Ads 受众,或为预测模型提供数据。 完成设置后,Adapty 将为后续事件填充这些属性。已有事件不会被更新。 1. 在 Google Analytics 中,打开 **Admin** > **Custom definitions**。 2. 点击 **Create custom dimensions**。 3. 对每个属性进行如下设置: - **Dimension name**:任意可读名称,例如"Subscription state"。 - **Scope**:**User**。 - **User property**:`subscription_state` 或 `subscription_product_id`。名称必须完全匹配——Google Analytics 区分大小写。 ## 故障排查 \{#troubleshooting\} ### Firebase 中未出现事件 \{#events-dont-appear-in-firebase\} - 请确认在**首次购买发生之前**已设置 Firebase App Instance ID。未携带 Firebase ID 的事件无法到达 Firebase,并会产生报错。 - 请确认 Firebase Console 中关联的 Google Analytics 媒体资源与数据流匹配。 - 请确认 Adapty 中设置的凭据(ID + 密钥)与平台相匹配。 ### `access_level_updated` 在事件流中显示为失败 \{#access_level_updated-shows-as-failed-in-the-event-feed\} `access_level_updated` 是一个**仅限 webhook 的事件**。Adapty 不会尝试将其推送至 Firebase——但事件流仍会将其列为推送失败。忽略该条目即可,你的集成一切正常。如需使用此事件,请配置 [webhook 集成](webhook)。 ### 沙盒事件污染生产数据 \{#sandbox-events-pollute-production-data\} Adapty 会将沙盒和生产环境的交易都转发到同一个 Firebase 项目。参见[配置 Firebase](#configure-firebase)——为开发构建使用独立的 Firebase 项目可以从根本上避免这一问题。 ### Firebase 对 StoreKit 2 应用的收入统计不足 \{#firebase-undercounts-revenue-for-storekit-2-apps\} Firebase 会为每笔 StoreKit 1 购买自动记录一个 `in_app_purchase` 事件,无需任何代码。而 StoreKit 2 使用了不同的 API,Firebase 根本无法感知这些交易。 由此带来的影响:大量使用 SK2、且没有独立收入数据管道的应用,在 Firebase、Google Analytics 以及所有下游 Google Ads 广告系列中,收入都会被低报一半甚至更多。出价优化基于错误的数字运行,收入报告只呈现了真实情况的一半。 修复方法:将 `firebase_app_instance_id` 传入 Adapty(详见[配置应用代码](#configure-your-app-code))。Adapty 会通过 Measurement Protocol 转发每一笔购买记录,包含金额、货币和产品信息。 ### Adapty Analytics 与 Firebase 数据不一致 \{#adapty-analytics-and-firebase-numbers-diverge\} - **StoreKit 2**:目前最主要的原因。请参阅 [Firebase 对 StoreKit 2 应用的收入统计偏低](#firebase-undercounts-revenue-for-storekit-2-apps)。 - **SDK 覆盖率**:Firebase 只统计通过应用发送了 Firebase App Instance ID 的用户事件。旧版本应用不会发起该调用,而 Adapty 仍会统计这些用户,Firebase 则不会。 - **沙盒事件**:Adapty 也会将沙盒交易转发给 Firebase。建议为开发构建使用独立的 Firebase 项目,以便与生产数据隔离。 - **采样**:Google Analytics Explorations 会对大型数据集进行采样。如需未采样的统计数据,请查看实时视图或标准报告。 ### 自定义事件名称被 Google Analytics 拒绝 \{#custom-event-names-are-rejected-by-google-analytics\} Google Analytics 对事件名称有限制:最多 40 个字符,只允许字母、数字和下划线,且必须以字母开头。请在看板中将不符合上述规则的自定义 Adapty 事件重命名。 --- # File: mixpanel --- --- title: "Mixpanel" description: "将 Mixpanel 与 Adapty 连接,获取强大的订阅分析能力。" --- [Mixpanel](https://mixpanel.com/home/) 是一款功能强大的产品分析服务。其基于事件驱动的追踪方案,帮助产品团队深入了解不同平台上用户获取、转化和留存的最优策略。 通过此集成,您可以将所有 Adapty 事件导入 Mixpanel。这样,您将对订阅业务和用户行为获得更全面的洞察。Adapty 提供完整的数据集,让您能够在一处追踪来自各应用商店的[订阅事件](events)。借助 Adapty,您可以轻松了解订阅者的行为规律,掌握他们的偏好,并将这些信息用于精准、有效的用户沟通。 ## 如何设置 Mixpanel 集成 \{#how-to-set-up-mixpanel-integration\} 1. 在 Adapty 看板中打开 [Integrations -> Mixpanel](https://app.adapty.io/integrations/mixpanel) 页面。 2. 启用开关并输入您的 **Mixpanel Token**。您可以为所有平台指定一个 Token,也可以限定到特定平台,仅接收来自特定平台的数据。 3. 将 **Mixpanel Data Residency** 设置为与您的 Mixpanel 项目一致。该字段为必填项,默认值为 **US**。若使用 `api.mixpanel.com` 端点,请选择 **US**;若使用 `api-eu.mixpanel.com`,请选择 **Europe**。 :::warning 如果您的 Mixpanel 项目使用欧盟数据驻留,必须将 **Mixpanel Data Residency** 设置为 **Europe**。Mixpanel 会丢弃从欧盟项目发送到美国端点的事件。 ::: <img src="/assets/shared/img/mixpanel.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 查找 Mixpanel Token \{#finding-your-mixpanel-token\} 获取 **Mixpanel Token** 的步骤: 1. 登录 [Mixpanel 看板](https://mixpanel.com/settings/project/)。 2. 打开 **Settings**,选择 **Organization Settings**。 <img src="/assets/shared/img/mixpanel-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在左侧边栏中,进入 **Projects** 并选择你的项目。 <img src="/assets/shared/img/mixpanel-project-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 集成工作原理 \{#how-the-integration-works\} Adapty 会自动将相关事件属性(如用户 ID 和收入)映射到 [Mixpanel 原生属性](https://docs.mixpanel.com/docs/data-structure/user-profiles),确保订阅相关事件的追踪和报告准确无误。 此外,Adapty 会按用户累积收入数据,并更新其[用户画像属性](https://docs.mixpanel.com/docs/data-structure/user-profiles),包括 `subscription state` 和 `subscription product ID`。一旦收到事件,Mixpanel 将实时更新对应字段。 ## 事件与标签 \{#events-and-tags\} 在凭据下方,有三组事件可以从 Adapty 发送到 Mixpanel。直接开启你需要的事件即可。点击[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/mixpanel-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 我们建议使用 Adapty 提供的默认事件名称。但您也可以根据需要修改事件名称。 ## SDK 配置 \{#sdk-configuration\} 使用 `.setIntegrationIdentifier()` 方法设置 `mixpanelUserId`。如果未设置,Adapty 将使用您的用户 ID(`customerUserId`),若该值为 null,则使用 Adapty ID。请确保您在应用中向 Mixpanel 发送数据所用的用户 ID 与发送给 Adapty 的一致。 :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "mixpanel_user_id", value: Mixpanel.mainInstance().distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) Adapty.updateProfile(params: builder.build()) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { await Adapty().setIntegrationIdentifier( key: "mixpanel_user_id", value: distinctId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; var distinctId = Mixpanel.DistinctId; if (distinctId != null) { Adapty.SetIntegrationIdentifier( "mixpanel_user_id", distinctId, (error) => { // handle the error }); } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // 如果你的应用中已有共享的 Mixpanel 实例,请直接使用该实例。 const trackAutomaticEvents = true; const mixpanel = new Mixpanel('YOUR_PROJECT_TOKEN', trackAutomaticEvents); await mixpanel.init(); // 这是 Mixpanel 当前的 distinct_id(自动生成,或通过 mixpanel.identify(...) 设置) const mixpanelUserId = await mixpanel.getDistinctId(); try { await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // 处理 `AdaptyError` } ``` </TabItem> </Tabs> ## Mixpanel 事件结构 \{#mixpanel-event-structure\} Adapty 使用 `track` 方法向 Mixpanel 发送事件。事件属性的结构如下: ```json { "event": "subscription_renewed", "properties": { "ip": 0, "time": 1709294400, "$insert_id": "123e4567-e89b-12d3-a456-426614174000", "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store", "purchase_date": "2024-03-01T12:00:00.000000+0000" } } ``` 其中: | 参数 | 类型 | 描述 | |:-------------------------------------|:--------|:-----------------------------------| | `event` | String | 事件名称(从 Adapty 事件映射而来)。 | | `properties` | Object | 事件属性。 | | `properties.ip` | Integer | IP 地址(服务端到服务端发送时为 0)。 | | `properties.time` | Long | 事件的 UNIX 时间戳(以秒为单位)。 | | `properties.$insert_id` | String | 用于去重的唯一事件 ID(UUID)。 | | `properties.vendor_product_id` | String | 应用商店中的产品 ID。 | | `properties.original_transaction_id` | String | 原始交易 ID。 | | `properties.currency` | String | 货币代码。 | | `properties.store` | String | 应用商店名称(例如 "app_store")。 | | `properties.environment` | String | 环境("Sandbox" 或 "Production")。 | ### 用户画像更新 \{#user-profile-updates\} Adapty 还会使用 `people_set` 更新 Mixpanel 用户画像,包含以下属性: | 参数 | 类型 | 描述 | |:--------------------------|:-------|:-----------------------------------------------| | `subscription_state` | String | 当前订阅状态(如 "subscribed")。 | | `subscription_product_id` | String | 当前有效订阅产品的 ID。 | --- # File: posthog --- --- title: "PostHog" description: "" --- PostHog 是一个分析平台,提供用户行为追踪、产品使用可视化和留存分析等工具。凭借事件追踪、用户流程分析和功能标志等功能,它能帮助你更好地了解和改进产品。 将 PostHog 与 Adapty 集成后,即可无缝追踪订阅相关事件,例如试用开始、续订和取消等。通过将这些事件发送至 PostHog,你可以分析订阅变化对用户行为的影响、评估付费墙效果,并在现有分析工作流中深入了解你的变现策略。 ## 集成特性 \{#integration-characteristics\} | 集成特性 | 描述 | | -------- | ---- | | 时间安排 | 实时;事件可能不会立即出现在 PostHog 看板上。 | | 数据方向 | Adapty 事件从 Adapty 服务器发送到 PostHog 服务器。 | | Adapty 集成点 | <ul><li>移动应用代码中的 PostHog 和 Adapty SDK</li><li>Adapty 服务器</li></ul> | ## PostHog 事件结构 \{#posthog-event-structure\} Adapty 会根据 [PostHog 集成页面](https://app.adapty.io/integrations/posthog) 上 **Events names** 部分的配置,将所选事件发送到 PostHog。每个事件的结构如下: ```json showLineNumbers { "distinct_id": "john.doe@example.com", "timestamp": "2025-01-08T11:06:12+00:00", "event": "subscription_started", "properties": { "$set": { "email": "user@example.com", "first_name": "John", "last_name": "Doe", "birthday": "1990-01-01", "gender": "male", "os": "iOS" }, "timezone": "America/New_York", "ip_address": "10.168.1.1", "*": "{{other_event_properties}}" } } ``` 其中 | **参数** | **类型** | **描述** | | --------------- | -------------------- | ------------------------------------------------------------ | | **distinct_id** | String | 用户的唯一标识符(例如 `profile.posthog_distinct_user_id`、`customer_user_id` 或 `profile_id`)。 | | **timestamp** | ISO 8601 日期和时间 | 事件发生的日期和时间。 | | **event** | String | 事件名称,即你在 [PostHog 配置](https://app.adapty.io/integrations/posthog) 的 Events names 部分中定义的名称。 | | **properties** | Object | 包含 [properties.$set](posthog#propertiesset-parameters) 以及所有[事件专属属性](messaging#event-properties)。每个属性均为可选项,若缺失则不会发送至 PostHog。 | ### properties.$set 参数 \{#propertiesset-parameters\} `properties.$set` 对象中的每个参数都是可选的,如果缺失则不会发送到 PostHog。 | **参数** | **类型** | **描述** | | --------------- | -------------------- | ------------------------------------------------------------ | | **email** | String | 用户的电子邮件地址。 | | **first_name** | String | 用户的名字。 | | **last_name** | String | 用户的姓氏。 | | **birthday** | String (Date) | 用户的出生日期。 | | **gender** | String | 用户的性别。 | | **os** | String | 用户设备的操作系统。 | ## 设置 PostHog 集成 \{#setting-up-posthog-integration\} 1. 打开 Adapty 看板中的 [Integrations -> PostHog](https://app.adapty.io/integrations/posthog) 页面,并启用开关。 <img src="/assets/shared/img/posthog-on.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 登录 [PostHog 看板](https://posthog.com/)。 3. 导航至 **Settings -> Project**。 4. 在 **Project** 窗口中,向下滚动至 **Project ID** 部分,复制 **Project API key**。 5. 将 API key 粘贴到 Adapty 看板中的 **Project API key** 字段。PostHog 的服务端集成不支持专用的沙盒模式。 6. 选择您的 **PostHog Deployment**: | 选项 | 描述 | | ------ | ------------------------------------------------------------ | | us/eu | 默认的 PostHog 托管部署。 | | Custom | 适用于自托管实例。请在 **PostHog Instance URL** 字段中输入您的实例 URL。 | 7. (可选)如果您使用的是自托管的 PostHog 部署,请在 **PostHog Instance URL** 字段中输入您的部署地址。 8. (可选)调整 **Reporting Proceeds**、**Exclude Historical Events**、**Report User's Currency** 和 **Send Trial Price** 等设置。详情请参阅[集成设置](configuration#integration-settings)。 9. (可选)你还可以在 **Events names** 部分自定义发送至 PostHog 的事件。禁用不需要的事件或根据需要重命名它们。 10. 点击 **Save** 完成配置。 ## SDK 配置 \{#sdk-configuration\} 要启用从 PostHog 接收归因数据,请按如下方式将 `distinctId` 值传递给 Adapty: :::note 第三方 SDK 会异步生成用户 ID,在 `Adapty.activate()` 执行时该 ID 可能尚未就绪。如果你的 **Customer User ID** 来自此类 SDK,请先不带该 ID 调用 `Adapty.activate()`。待 ID 到位后,依次调用 `setIntegrationIdentifier()`,再用 CUID 调用 `identify()`。 ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let distinctId = PostHogSDK.shared.getDistinctId() try await Adapty.setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId()) { error -> if (error != null) { // handle the error } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final distinctId = await Posthog().getDistinctId(); await Adapty().setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId, ); } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> Unity 没有官方的 PostHog SDK。 </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog(); // ... try { await adapty.setIntegrationIdentifier("posthog_distinct_user_id", posthog.getDistinctId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> Adapty 现在将向 PostHog 发送事件并从中接收归因数据。 --- # File: splitmetrics --- --- title: "SplitMetrics Acquire" description: "使用 SplitMetrics 和 Adapty 进行订阅 A/B 测试与优化。" --- 通过 [SplitMetrics Acquire](https://splitmetrics.com/acquire/) 集成,你可以清楚地了解 Apple Search Ads 的订阅收益,并可以持续追踪用户数月,掌握广告的长期变现表现。 此外,Adapty 还会将[订阅事件](events)发送至 SplitMetrics Acquire,让你基于 Apple Search Ads 归因数据构建自定义看板和自动化流程。 由于 Adapty 已直接从 ASA 获取所需数据,因此不会向 Adapty 写入任何归因数据。 ## 如何配置 SplitMetrics Acquire 集成 \{#how-to-set-up-splitmetrics-acquire-integration\} 要集成 SplitMetrics Acquire,请前往 [Integrations > SplitMetrics Acquire](https://app.adapty.io/integrations/splitmetrics) 并填写凭证信息。 <img src="/assets/shared/img/8255349-CleanShot_2023-08-14_at_17.39.422x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 打开你的 SplitMetrics Acquire 账户,将鼠标悬停在某个 MMP 图标上,点击 **Settings** 按钮。在弹出的对话框中找到第 **5** 项下的 Client ID,复制后粘贴至 Adapty 的 **Client ID** 字段。 <img src="/assets/shared/img/4d0b2b6-Adapty.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/4f8d0b8-AdaptyGuide.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 使用集成功能还需要设置 Apple App ID。要查找 App ID,请在 App Store Connect 中打开你的应用页面,进入 **General** 部分下的 **App Information page**,在屏幕左下角找到 **Apple ID**。 <img src="/assets/shared/img/61578ee-CleanShot_2022-04-20_at_17.55.03.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 事件与标签 \{#events-and-tags\} 在凭证信息下方,有三组事件可从 Adapty 发送至 SplitMetrics Acquire,按需开启即可。完整的事件列表请参见[此处](events)。 <img src="/assets/shared/img/1b0c777-CleanShot_2023-08-11_at_14.56.362x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 建议使用 Adapty 提供的默认事件名称,当然你也可以根据需要自定义事件名称。Adapty 将通过服务端到服务端的集成方式向 SplitMetrics Acquire 发送订阅事件,你可以在 SplitMetrics 看板中查看所有订阅事件。 ## SDK 配置 \{#sdk-configuration\} SDK 端无需任何额外配置,但建议向 Adapty 传入 `customerUserId` 以提高数据准确性。 :::warning 请确保已在 Adapty 中配置 [Apple Search Ads](apple-search-ads) 并[上传凭证](https://app.adapty.io/settings/apple-search-ads),否则 SplitMetrics Acquire 将无法正常工作。 ::: ## 故障排查 \{#troubleshooting\} 如果 SplitMetrics Acquire 集成在配置正确的情况下仍无法正常工作,请检查以下几点: - 确保已在 [App Settings -> Apple Search Ads tab](https://app.adapty.io/settings/apple-search-ads) 中启用 **Receive Apple Search Ads attribution in Adapty** 开关,已在 Adapty 中配置 [Apple Search Ads](apple-search-ads),并已[上传凭证](https://app.adapty.io/settings/apple-search-ads),否则 SplitMetrics 将无法正常工作。 - 确认用户画像具有非自然流量的 ASA 归因数据。只有包含详细非自然流量 ASA 归因的用户画像才会将事件传递至 Adapty。 ## SplitMetrics Acquire 事件结构 \{#splitmetrics-acquire-event-structure\} Adapty 通过 GET 请求以查询参数的形式向 SplitMetrics Acquire 发送事件,每个事件的结构如下: ```json { "source": "Apple Search Ads", "app_id": "123456789", "name": "subscription_renewed", "type": "subscription_renewed", "revenue": 9.99, "currency": "USD", "tap_time": "2024-03-01 12:00:00", "open_time": "2024-03-01 12:05:00", "event_time": "2024-03-02 12:00:00", "adaccount_id": "123456", "campaign_id": "123456789", "adgroup_id": "123456789", "keyword_id": "123456789", "creative_set_id": "123456789", "Ad_id": "123456789", "country_or_region": "US", "conversion_type": "Download", "user_id": "user_12345", "att_status": "3", "device_type": "iphone", "app_version": "1.2.3", "sdk_version": "2.10.0", "ios_version": "17.2", "event_value": "{\"vendor_product_id\":\"yearly.premium.6999\",\"original_transaction_id\":\"GPA.3383...\"}", "event_id": "123e4567-e89b-12d3-a456-426614174000" } ``` 各字段说明: | 参数 | 类型 | 说明 | |:--------------------|:-------|:----------------------------------------------------------------------------------------------------------------| | `source` | String | 固定值 "Apple Search Ads"。 | | `app_id` | String | Apple App ID。 | | `name` | String | 事件名称(由 Adapty 事件映射而来)。 | | `type` | String | 事件类型(与 `name` 相同)。 | | `revenue` | Float | 收入金额。 | | `currency` | String | 货币代码。 | | `tap_time` | String | 广告点击的日期和时间。 | | `open_time` | String | 应用打开(安装)的日期和时间。 | | `event_time` | String | 事件发生的日期和时间。 | | `adaccount_id` | String | ASA 组织 ID。 | | `campaign_id` | String | ASA 广告系列 ID。 | | `adgroup_id` | String | ASA 广告组 ID。 | | `keyword_id` | String | ASA 关键词 ID。 | | `creative_set_id` | String | ASA 创意集 ID。 | | `Ad_id` | String | ASA 广告 ID。 | | `country_or_region` | String | 商店所在国家或地区。 | | `conversion_type` | String | 转化类型(例如 "Download")。 | | `user_id` | String | Customer User ID 或 Adapty 用户画像 ID。 | | `att_status` | String | 追踪使用状态(0-3)。 | | `device_type` | String | 设备类型(例如 "iphone"、"ipad")。 | | `app_version` | String | 应用版本号。 | | `sdk_version` | String | Adapty SDK 版本号。 | | `ios_version` | String | iOS 版本号。 | | `event_value` | String | 包含所有可用[事件详情](webhook-event-types-and-fields#for-most-event-types)的 JSON 字符串。 | | `event_id` | String | 唯一事件 ID(UUID)。 | --- # File: braze --- --- title: "Braze" description: "将 Braze 与 Adapty 集成,实现无缝的客户互动和推送通知。" --- 作为顶级客户互动解决方案之一,[Braze](https://www.braze.com/) 提供了一整套推送通知、电子邮件、短信和应用内消息工具。通过将 Adapty 与 Braze 集成,您可以在一个地方轻松访问所有订阅事件,并能够根据这些事件触发自动化通信。 Adapty 提供完整的数据集,让您可以在一个地方追踪来自所有应用商店的[订阅事件](events),并可用于更新 Braze 中的用户画像。借助 Adapty,您可以轻松了解订阅用户的行为,掌握他们的偏好,并利用这些信息以精准有效的方式与他们沟通。因此,该集成允许您在 Braze 看板中追踪订阅事件,并将其与您的[获客活动](https://www.braze.com/product/journey-orchestration)相关联。 Adapty 将订阅事件、用户属性和购买信息发送至 Braze,让您可以在完成简短、便捷的集成后,通过 Braze 推送通知与客户建立定向沟通。 ## 如何设置 Braze 集成 \{#how-to-set-up-braze-integration\} 要集成 Braze,请前往 [Integrations -> Braze](https://app.adapty.io/integrations/braze),打开开关并填写相关字段。 集成过程的第一步是提供必要的凭据,以建立您的 Braze 与 Adapty 用户画像之间的连接。集成正常运行需要 **REST API Key**、您的 **Braze Instance ID** 以及 iOS 和 Android 的 **App IDs**: <img src="/assets/shared/img/5f1e62c-adapty_braze.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **REST API Key** 可在 **Braze Dashboard** → **Settings** → **API Keys** 中创建。创建时请确保您的密钥具有 `users.track` 权限: <img src="/assets/shared/img/b5fdf16-adapty_braze_create_api_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/1e5b4b8-adapty_braze_api_key_users_track.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 要获取 **Braze Instance ID**,请记录您的 Braze 看板 URL,然后前往 [Braze 文档](https://www.braze.com/docs/api/basics/#endpoints)中指定实例 ID 的部分。它应具有区域格式,例如 US-03、EU-01 等。 3. iOS 和 Android App IDs 同样可在 Braze Dashboard → Settings → API Keys 中找到。从此处复制它们: <img src="/assets/shared/img/1e6d21b-adapty_braze_app_ids.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 事件、用户属性和购买 \{#events-user-attributes-and-purchases\} 在凭据下方,有三组事件可以从 Adapty 发送到 Braze。只需打开您需要的事件即可。您也可以根据需要更改发送到 Braze 的事件名称。查看 Adapty 提供的完整事件列表,请点击[这里](events): <img src="/assets/shared/img/702e628-adapty_braze_events_names.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 将通过服务器对服务器集成的方式,将订阅事件和用户属性发送至 Braze,让您可以在 Braze 看板中查看这些信息,并据此配置营销活动。 对于包含收入的事件(例如试用转化和续订),Adapty 将以购买的形式将此信息发送至 Braze。 [这里](messaging#event-properties)是发送至 Braze 的事件属性的完整规格说明。 :::note 实用的用户属性 Adapty 默认会为 Braze 集成发送一些用户属性。您可以参考以下列表,确定哪些属性最适合您的需求。 ::: | 用户属性 | 类型 | 值 | |--------------|----|-----| | `adapty_customer_user_id` | String | 包含客户定义的用户唯一标识符的值。可在 Adapty [看板](profiles-crm)和 Braze 中找到。 | | `adapty_profile_id` | String | 包含 Adapty 用户画像 ID 的唯一标识符值,可在 Adapty [看板](profiles-crm)中找到。 | | `environment` | String | <p>指示用户是在沙盒环境还是生产环境中操作。</p><p></p><p>值为 `Sandbox` 或 `Production`</p> | | `store` | String | <p>包含用于完成购买的商店名称。</p><p></p><p>可能的值:</p><p>`app_store` 或 `play_store`。</p> | | `vendor_product_id` | String | <p>包含 Apple/Google 商店中产品 ID 的值。</p><p></p><p>例如:org.locals.12345</p> | | `subscription_expires_at` | String | <p>包含最新订阅的到期日期。</p><p></p><p>值格式为:</p><p>YYYY-MM-DDTHH:mm:ss.SSS+TZ</p><p>例如:2023-02-15T17:22:03.000+0000</p> | | `active_subscription` | String | 在任何购买/续订事件时该值将设置为 `true`,若订阅已到期则设置为 `false`。 | | `period_type` | String | <p>指示购买或续订的最新周期类型。</p><p></p><p>可能的值为</p><p>试用期为 `trial`,其余为 `normal`。</p> | 所有浮点数值将四舍五入为整数,字符串保持不变。 除了预定义的标签列表外,还可以使用标签发送[自定义属性](segments#custom-attributes)。这为标签中包含的数据类型提供了更大的灵活性,对于追踪与产品或服务相关的特定信息非常有用。如果用户在[集成页面](https://app.adapty.io/integrations/braze)勾选了 **Send user attributes** 复选框,所有自定义用户属性将自动发送至 Braze。 ## SDK 配置 \{#sdk-configuration\} 要在 Adapty 和 Braze 中关联用户画像,您需要使用与 Adapty 相同的客户用户 ID 配置 Braze SDK,或使用其 `.changeUser()` 方法: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers let braze = Braze(configuration: configuration) braze.changeUser(userId: "adapty_customer_user_id") ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Braze.getInstance(context).changeUser("adapty_customer_user_id") ``` </TabItem> </Tabs> --- # File: onesignal --- --- title: "OneSignal" description: "将 OneSignal 与 Adapty 集成,提升基于推送通知的用户互动。" --- [OneSignal](https://onesignal.com/) 是一个领先的客户互动平台,提供推送通知、电子邮件、短信和应用内消息等功能。将 Adapty 与 OneSignal 集成,可让您在一处访问所有订阅事件,从而根据这些事件触发自动化通信。 通过 Adapty,您可以跨多个商店追踪[订阅事件](events)、分析用户行为,并将这些数据用于更精准的通信。此集成帮助您在 OneSignal 看板中监控订阅事件,并将其映射到您的[获客活动](https://documentation.onesignal.com/docs/en/automated-messages)。 Adapty 会根据订阅事件更新 OneSignal 标签,让您以极少的配置即可发送个性化推送通知。 **集成特性** | 集成特性 | 说明 | | :------------------------- | :----------------------------------------------------------- | | 更新频率 | 实时更新 | | 数据方向 | 单向:从 Adapty 到 OneSignal 服务器 | | Adapty 集成点 | <ul><li>移动应用代码中的 OneSignal 和 Adapty SDK</li><li>Adapty 服务器</li></ul>| ## 设置 OneSignal 集成 \{#setting-up-one-signal-integration\} 要设置集成: 1. 在 Adapty 看板中打开 [Integrations → OneSignal](https://app.adapty.io/integrations/onesignal)。 <img src="/assets/shared/img/onesignal-on.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 启用集成开关。 3. 输入您的 **OneSignal App ID**。 要设置与 OneSignal 的集成,请前往 Adapty 看板中的 [Integrations -> OneSignal](https://app.adapty.io/integrations/onesignal),开启开关并配置集成凭据。 ## 获取您的 OneSignal App ID \{#retrieving-your-onesignal-app-id\} 在您的 [OneSignal 看板](https://dashboard.onesignal.com/login)中找到 **OneSignal App ID**: 1. 导航至 **Settings** → **Keys & IDs**。 <img src="/assets/shared/img/onesignal-dashboard.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 复制您的 **OneSignal App ID** 并将其粘贴到 Adapty 看板中的 **App ID** 字段。 <img src="/assets/shared/img/onesignal-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 您可以在[以下文档](https://documentation.onesignal.com/docs/en/keys-and-ids)中找到有关 OneSignal ID 的更多信息。 ### 配置事件 \{#configuring-events\} Adapty 允许您向 OneSignal 发送三组事件。在 Adapty 看板中开启您需要的事件。您可以在[此处](events)查看所有可用事件的完整列表及详细说明。 <img src="/assets/shared/img/onesignal.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 通过服务器到服务器集成将订阅事件发送到 OneSignal,让您能够在 OneSignal 中追踪所有与订阅相关的活动。 :::warning 从 2023 年 4 月 17 日起,OneSignal 的免费套餐不再支持此集成。该功能仅适用于 **Growth**、**Professional** 及更高级别的套餐。详情请参阅 [OneSignal 定价](https://onesignal.com/pricing)。 ::: ## 自定义标签 \{#custom-tags\} 此集成会将各种属性作为标签更新并分配给您的 Adapty 用户,然后将其发送到 OneSignal。请参阅以下标签列表,找到最适合您需求的标签。 :::warning OneSignal 对标签数量有限制。这包括 Adapty 生成的标签和 OneSignal 中已有的所有标签。超出限制可能会在发送事件时导致错误。 ::: | 标签 | 类型 | 说明 | |---|----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `adapty_customer_user_id` | String | 用户在您应用中的唯一标识符。必须在您的系统、Adapty 和 OneSignal 中保持一致。 | | `adapty_profile_id` | String | Adapty 用户画像 ID,可在您的 [Adapty 看板](profiles-crm)中找到。 | | `environment` | String | `Sandbox` 或 `Production`,表示用户当前所处的环境。 | | `store` | String | 购买产品的商店。选项:**app_store**、**play_store**、**stripe**,或您的[自定义商店](custom-store)名称。 | | `vendor_product_id` | String | 应用商店中的产品 ID(例如 `org.locals.12345`)。 | | `subscription_expires_at` | String | 最新订阅的到期日期(`YYYY-MM-DDTHH:MM:SS+0000`,例如 `2023-02-10T17:22:03.000000+0000`)。 | | `last_event_type` | String | 来自 [Adapty 事件列表](events)的最新事件类型。<br/> 请注意以下情况:<br/>- 对于 **Subscription expired** 事件,Adapty 将 `last_event_type` 属性发送为 `subscription_cancelled`。<br/>- 对于 **Trial renew canceled**,发送为 `auto_renew_off`<br/>- 对于 **Subscription renew canceled**,发送为 `auto_renew_off_subscription` | | `purchase_date` | String | 最近一次交易日期(`YYYY-MM-DDTHH:MM:SS+0000`,例如 `2023-02-10T17:22:03.000000+0000`)。 | | `active_subscription` | String | 如果用户有有效订阅则为 `true`,订阅已到期则为 `false`。 | | `period_type` | String | 表示购买或续订的最新周期类型。可能的值:`trial` 表示试用期,`normal` 表示其他所有情况。 | 所有浮点值均四舍五入为整数,字符串保持不变。 除了预定义标签之外,您还可以将[自定义属性](segments#custom-attributes)作为标签发送,从而在包含的数据方面获得更大的灵活性。这对于追踪与您的产品或服务相关的特定详细信息非常有用。 如果在[集成页面](https://app.adapty.io/integrations/onesignal)上启用了 **Send user attributes** 复选框,自定义用户属性将自动发送到 OneSignal。未勾选时,Adapty 恰好发送 10 个标签;勾选后,可以发送超过 10 个标签,从而实现更丰富的数据捕获。 ## SDK 配置 \{#sdk-configuration\} 将 OneSignal 与 Adapty 集成有两种方式: 1. **旧版(v5 之前):** 使用 `playerId`(在 [OneSignal SDK v5](https://github.com/OneSignal/OneSignal-iOS-SDK/releases/tag/5.0.0) 中已弃用)。 2. **当前版本(v5+):** 使用 `subscriptionId`。 :::warning 请确保将 `playerId`(适用于 v5 之前的 OneSignal SDK)或 `subscriptionId`(适用于 OneSignal SDK v5+)发送给 Adapty。若不发送,OneSignal 标签将无法更新,集成也无法正常运行。 ::: <Tabs groupId="current-version" queryString> <TabItem value="v5+" label="OneSignal SDK v5+ (current)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // SubscriptionID OneSignal.Notifications.requestPermission({ accepted in Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalSubscriptionId(OneSignal.User.pushSubscription.id)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_subscription_id", value: OneSignal.User.pushSubscription.id ) } }, fallbackToSettings: true) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="(Android) Java" default> ```java showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // handle the error } }); }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // 1. Since OneSignal.User.pushSubscription.id may return null if called too early, // OneSignal suggests to listen for the updates: OneSignal.User.pushSubscription.addObserver((state) { if (state.current.optedIn) { // now you can try to retrieve subscriptionId } }); // 2. Then you can push subscriptionId to Adapty: final subscriptionId = OneSignal.User.pushSubscription.id; if (subscriptionId != null) { await Adapty().setIntegrationIdentifier(key: "one_signal_subscription_id", value: subscriptionId); } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using OneSignalSDK; var pushUserId = OneSignal.Default.PushSubscriptionState.userId; Adapty.SetIntegrationIdentifier( "one_signal_player_id", pushUserId, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. up to 4.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // PlayerID // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalPlayerId(playerId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // handle the error } }); } }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> </TabItem> </Tabs> 更多内容请参阅 OneSignal 文档: - [推送订阅 ID](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#user-pushsubscription-id) - [推送订阅变更](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#addobserver-push-subscription-changes) ## 处理多设备 \{#dealing-with-multiple-devices\} 如果用户拥有多台设备,追踪购买事件和订阅可能会比较复杂。OneSignal 通过[外部用户 ID](https://documentation.onesignal.com/docs/en/users) 提供了处理此问题的方法。 要保持跨设备的用户数据一致性: 1. 在您的**服务器端**匹配不同设备,并将此数据发送至 OneSignal。 2. 将 Adapty 的 [customer_user_id](identifying-users) 用作 OneSignal 中的 [externalUserId](https://documentation.onesignal.com/docs/en/users#external-id)。如果您的应用没有注册系统,可考虑使用另一个在用户所有设备上保持一致的唯一标识符。 在所有设备上保持用户标识符的一致性,并在用户 ID 更改时及时更新 OneSignal,这一点非常重要。这样可以简化用户活动和订阅的追踪,同时确保消息传递的一致性,并实现更准确的数据分析和更好的用户体验。更多详情,请参阅 OneSignal 的[外部用户 ID 文档](https://documentation.onesignal.com/docs/en/users)。 --- # File: pushwoosh --- --- title: "Pushwoosh" description: "将 Pushwoosh 与 Adapty 集成,实现无缝推送通知追踪。" --- Adapty 使用订阅事件来更新 [Pushwoosh](https://www.pushwoosh.com/) 用户画像标签,因此您可以在完成以下简单快捷的集成设置后,通过推送通知与客户建立精准沟通。 ## 如何设置 Pushwoosh 集成 \{#how-to-set-up-pushwoosh-integration\} 要集成 Pushwoosh,请前往 [**Integrations** -> **Pushwoosh**](https://app.adapty.io/integrations/pushwoosh),将开关从关闭切换为开启,并填写相关字段。 首先,设置凭据以在您的 Pushwoosh 和 Adapty 用户画像之间建立连接。需要提供 Pushwoosh 应用程序 ID 和身份验证令牌。 <img src="/assets/shared/img/64e48a1-CleanShot_2023-08-18_at_11.13.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **App ID** 可在您的 Pushwoosh 看板中找到。 <img src="/assets/shared/img/ee27687-CleanShot_2023-08-18_at_14.37.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Auth token** 可在 Pushwoosh 设置的 API Access 部分找到。 <img src="/assets/shared/img/50e634b-CleanShot_2023-08-18_at_14.35.022x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 事件与标签 \{#events-and-tags\} 在凭据下方,有三组事件可从 Adapty 发送到 Pushwoosh。只需开启您需要的事件即可。您也可以根据需要更改发送到 Pushwoosh 的事件名称。在[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/392dc31-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 将通过服务器到服务器的集成向 Pushwoosh 发送订阅事件,使您能够在 Pushwoosh 看板中查看所有订阅事件。 :::note 自定义标签 使用 Adapty,您还可以为 Pushwoosh 集成使用自定义标签。您可以参考下方提供的标签列表,确定最适合您需求的标签。 ::: | 标签 | 类型 | 值 | |---|----|-----| | `adapty_customer_user_id` | String | 包含用户唯一标识符的值,可在 Pushwoosh 端找到。 | | `adapty_profile_id` | String | 包含用户的 Adapty 用户画像 ID 唯一标识符的值,可在您的 Adapty [看板](profiles-crm)中找到。 | | `environment` | String | <p>指示用户是在沙盒环境还是生产环境中运行。</p><p></p><p>值为 `Sandbox` 或 `Production`</p> | | `store` | String | <p>包含用于购买的商店名称。</p><p></p><p>可能的值:</p><p>`app_store` 或 `play_store`。</p> | | `vendor_product_id` | String | <p>包含 Apple/Google 商店中产品 ID 的值。</p><p></p><p>例如:org.locals.12345</p> | | `subscription_expires_at` | String | <p>包含最新订阅的到期日期。</p><p></p><p>值格式为:</p><p>年-月-日T时:分:秒</p><p>例如:2023-02-10T17:22:03.000000+0000</p> | | `last_event_type` | String | 指示您为集成启用的标准 [Adapty 事件](events)列表中最后接收到的事件类型。 | | `purchase_date` | String | <p>包含最后一次交易(原始购买或续订)的日期。</p><p></p><p>值格式为:</p><p>年-月-日T时:分:秒</p><p>例如:2023-02-10T17:22:03.000000+0000</p> | | `original_purchase_date` | String | <p>包含根据交易记录的首次购买日期。</p><p></p><p>值格式为:</p><p>年-月-日T时:分:秒</p><p>例如:2023-02-10T17:22:03.000000+0000</p> | | `active_subscription` | String | 在任何购买/续订事件时该值将设置为 `true`,如果订阅已过期则设置为 `false`。 | | `period_type` | String | <p>指示购买或续订的最新周期类型。</p><p></p><p>可能的值为:</p><p>`trial` 表示试用期,`normal` 表示其他情况。</p> | 所有浮点值将被四舍五入为整数。字符串保持不变。 除了预定义的标签列表外,还可以使用标签发送[自定义属性](segments#custom-attributes)。这为标签中包含的数据类型提供了更大的灵活性,可用于追踪与产品或服务相关的特定信息。如果用户在[集成页面](https://app.adapty.io/integrations/pushwoosh)勾选了 **Send user custom attributes** 复选框,所有自定义用户属性将自动发送到 Pushwoosh。 ## SDK 配置 \{#sdk-configuration\} 要将 Adapty 与 Pushwoosh 关联,您需要向我们发送 `HWID` 值: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "pushwoosh_hwid", value: Pushwoosh.sharedInstance().getHWID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; try { await Adapty().setIntegrationIdentifier( key: "pushwoosh_hwid", value: hwid, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "pushwoosh_hwid", Pushwoosh.Instance.HWID, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... try { await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> --- # File: slack --- --- title: "Slack" description: "将 Slack 与 Adapty 集成,实时接收订阅事件通知。" --- [Slack](https://slack.com/) 是一款无需多介绍的职场通讯与效率平台。 通过此集成,每当 Adapty 追踪到收入事件时,您都可以在 Slack 中收到通知。如果您希望珍视每一次 MRR 增长的时刻,或者希望随时关注试用取消、账单问题、退款等情况,这个功能将非常有用。 ## 如何设置 Slack 集成 \{#how-to-set-up-slack-integration\} 您需要: - 在您的 Slack 工作区中创建一个应用 - 授予其发送消息的权限 - 然后在 [Integrations → Slack](https://app.adapty.io/integrations/slack) 中向 Adapty 提供必要信息。 ### 1\. 在 Slack 中创建应用 \{#1-create-an-app-in-slack\} 1. 前往 [Slack API 看板](https://api.slack.com/apps) 并按如下方式创建应用: <img src="/assets/shared/img/f43aedc-CleanShot_2024-01-04_at_18.27.412x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/08fa9e6-CleanShot_2024-01-04_at_18.28.142x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 为其命名(例如 `Adapty`)并将其添加到您的工作区: <img src="/assets/shared/img/5002bb1-CleanShot_2024-01-04_at_18.29.132x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. 授予发帖权限并获取应用 Token \{#2-give-permission-to-post-and-get-a-token-for-your-app\} 您将被重定向到 Slack 中您的应用页面。 1. 向下滚动并点击 **Permissions**: <img src="/assets/shared/img/9750451-CleanShot_2024-01-04_at_18.48.072x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 重定向后,向下滚动至 **Scopes** 并点击 **Add an OAuth Scope**: <img src="/assets/shared/img/db5b5f4-CleanShot_2024-01-04_at_18.50.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 授予 `chat:write`、`chat:write.public` 和 `chat:write.customize` 权限。这些权限用于在您的频道中发布消息并自定义消息内容: <img src="/assets/shared/img/d97ccb9-CleanShot_2024-01-04_at_18.51.572x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 滚动回页面顶部,点击 **Install to Workspace**: <img src="/assets/shared/img/14608e3-CleanShot_2024-01-04_at_19.17.58.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Allow**: <img src="/assets/shared/img/143967e-CleanShot_2024-01-04_at_18.53.292x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 完成后,您将被重定向到同一页面,但此时将显示可用的 OAuth Token(`xoxb-...`)。这正是完成设置所需的内容: <img src="/assets/shared/img/59b33ee-CleanShot_2024-01-04_at_18.55.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 3\. 在 Adapty 中配置集成 \{#3-configure-the-integration-in-adapty\} 1. 前往 [**Integrations** → **Slack**](https://app.adapty.io/integrations/slack): <img src="/assets/shared/img/b4ffd71-CleanShot_2024-01-04_at_19.05.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 粘贴上一步中的 `xoxb-...` Token,并选择应用将发帖的频道。您可以设置集成仅接收生产环境、沙盒环境或两者的事件。您还可以选择发帖时使用的货币(原始货币或转换为美元)。 :::note 请注意,如果您希望 Adapty 在私有频道中发布消息,您需要手动将您在 Slack 中创建的 `Adapty` 应用添加到该频道,否则将无法正常工作。 ::: 3. 最后,您可以在 **Events** 下选择希望接收的事件: <img src="/assets/shared/img/970a7bb-CleanShot_2024-01-04_at_19.09.472x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 设置完成! 事件将被发送到您指定的频道。您可以在适用情况下查看收入,并在 Adapty 中查看客户的用户画像: <img src="/assets/shared/img/852b8c8-CleanShot_2024-01-04_at_19.11.332x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: s3-exports --- --- title: "Amazon S3" description: "将订阅数据导出到 S3,用于高级分析和报告。" --- Adapty 与 Amazon S3 的集成允许你将事件和付费墙访问数据安全地存储在一个集中位置。你可以将[订阅事件](events)以 .csv 文件的形式保存到 Amazon S3 存储桶中。 要设置此集成,你需要在 AWS 控制台和 Adapty 看板中按照几个简单的步骤进行操作。 :::note 计划 Adapty 每 **24h** 在 UTC 时间 4:00 发送一次数据。 每个文件将包含整个上一个日历日(UTC 时间)内所创建事件的数据。例如,北京时间 3 月 8 日 4:00 UTC 自动导出的数据,将包含 3 月 7 日 00:00:00 至 23:59:59(UTC)期间创建的所有事件。 ::: ## 如何配置 Amazon S3 集成 \{#how-to-set-up-amazon-s3-integration\} 要开始接收数据,您需要以下凭证: 1. Access key ID 2. Secret access key 3. S3 bucket name 4. S3 存储桶内的文件夹名称 :::note 嵌套目录 您可以在 Amazon S3 bucket name 字段中指定嵌套目录,例如:adapty-events/com.sample-app ::: 要集成 Amazon S3,请前往 [**Integrations** -> **Amazon S3**](https://app.adapty.io/integrations/s3),将开关从关闭切换为开启,并填写相关字段。 首先,设置凭证以建立 Amazon S3 与 Adapty 用户画像之间的连接。 <img src="/assets/shared/img/2b1a6e3-CleanShot_2023-03-24_at_14.51.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在 Adapty 看板中,需要填写以下字段来完成连接配置: | 字段 | 描述 | | :--------------------------- | :----------------------------------------------------------- | | **Access Key ID** | 用于验证用户或应用程序访问 AWS 服务的唯一标识符。请在下载的 [csv 文件](s3-exports#how-to-create-amazon-s3-credentials) 中查找此 ID。 | | **Secret Access Key** | 与 Access Key ID 配合使用,用于验证用户或应用程序访问 AWS 服务的私钥。请在下载的 [csv 文件](s3-exports#how-to-create-amazon-s3-credentials) 中查找此密钥。 | | **S3 Bucket Name** | 用于在 AWS 云中标识特定 S3 存储桶的全局唯一名称。S3 存储桶是一种简单的存储服务,允许用户在云中存储和检索文件、图片等数据对象。 | | **Folder Inside the Bucker** | 您希望在所选 S3 存储桶中创建的文件夹名称。请注意,S3 通过对象键前缀来模拟文件夹,这些前缀本质上就是文件夹名称。 | ## 如何创建 Amazon S3 凭证 \{#how-to-create-amazon-s3-credentials\} 本指南将帮助您在 AWS 控制台中创建必要的凭证。 ### 1\. 创建访问策略 \{#1-create-access-policy\} 首先,前往 AWS 控制台中的 [IAM Policy Dashboard](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies),然后选择 **Create Policy** 选项。 <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在 Policy 编辑器中,粘贴以下 JSON,并将 `adapty-s3-integration-test` 替换为你的存储桶名称: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 完成策略配置后,您可以选择添加标签(可选),然后点击 **Next** 进入最后一步。在此步骤中,为您的策略命名,然后点击 **Create policy** 按钮即可完成创建。 <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. 创建 IAM 用户 \{#2-create-iam-user\} 要让 Adapty 能够将原始数据报告上传至您的存储桶,您需要为其提供一个具有该存储桶写入权限的用户的 Access Key ID 和 Secret Access Key。 请前往 IAM 控制台,选择 [Users 部分](https://console.aws.amazon.com/iamv2/home#/users),然后点击 **Add users** 按钮。 <img src="/assets/shared/img/bb612c8-CleanShot_2023-03-21_at_11.12.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 为用户命名,选择 **Access key – Programmatic access**,然后继续进行权限设置。 <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 下一步,请选择 **Add user to group** 选项,然后点击 **Create group** 按钮。 <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 接下来,你需要为用户组命名,并选择之前创建的策略。选择策略后,点击 **Create group** 按钮完成操作。 <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 成功创建群组后,请**选择它**并继续下一步。 <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 这是本节的最后一步,直接点击 **Create User** 按钮即可。 <img src="/assets/shared/img/ea43722-CleanShot_2023-03-21_at_11.40.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 最后,您可以选择**以 .csv 格式下载凭据**,或者直接从看板中复制并粘贴凭据。 <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 手动数据导出 \{#manual-data-export\} 除了自动将事件数据导出到 Amazon S3 之外,Adapty 还提供了手动文件导出功能。通过此功能,你可以选择特定的时间范围来导出事件数据,并手动将其导出到你的 S3 存储桶。这让你能够更灵活地控制导出的数据内容和导出时机。 指定的日期范围将用于导出从日期 A 00:00:00 UTC 到日期 B 23:59:59 UTC 期间创建的事件。 <img src="/assets/shared/img/466bd29-CleanShot_2023-03-21_at_12.35.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 表格结构 \{#table-structure\} 在 AWS S3 集成中,Adapty 提供了一张表来存储交易事件和付费墙访问的历史数据。该表包含用户画像、收入与净收益、来源商店等多项数据。这些表本质上记录了应用在特定时间段内产生的所有交易。 :::warning 请注意,此结构可能会随时间增长——我们或与我们合作的第三方可能会引入新数据。请确保处理该结构的代码足够健壮,依赖于特定字段,而不是整体结构。 ::: 以下是事件的表结构: :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: | 列名 | 描述 | |---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile_id** | Adapty 用户 ID。 | | **event_type** | 小写的事件名称。请参阅[事件](events)章节了解事件类型。 | | **event_datetime** | ISO 8601 日期。 | | **transaction_id** | 交易(如购买或续订)的唯一标识符。 | | **original_transaction_id** | 原始购买的交易标识符。 | | **subscription_expires_at** | 订阅的到期日期,通常为未来时间。 | | **environment** | 可为沙盒或生产环境。 | | **revenue_usd** | 以美元计的收入,可为空。 | | **proceeds_usd** | 以美元计的收益,可为空。 | | **net_revenue_usd** | 以美元计的净收入(税后收入),可为空。 | | **tax_amount_usd** | 以美元计的税款扣除金额,可为空。 | | **revenue_local** | 以本地货币计的收入,可为空。 | | **proceeds_local** | 以本地货币计的收益,可为空。 | | **net_revenue_local** | 以本地货币计的净收入(税后收入),可为空。 | | **tax_amount_local** | 以本地货币计的税款扣除金额,可为空。 | | **customer_user_id** | 开发者用户 ID,例如可以是用户的 UUID、邮箱或其他任意 ID。如未设置则为 Null。 | | **store** | 可为 _app_store_ 或 _play_store_。 | | **product_id** | Apple App Store、Google Play Store 或 Stripe 中的产品 ID。 | | **base_plan_id** | Google Play Store 中的[基础方案 ID](https://support.google.com/googleplay/android-developer/answer/12154973) 或 Stripe 中的[价格 ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices)。 | | **developer_id** | 交易来源付费墙的开发者(SDK)ID。 | | **ab_test_name** | 交易来源 A/B 测试的名称。 | | **ab_test_revision** | 交易来源 A/B 测试的版本号。 | | **paywall_name** | 交易来源付费墙的名称。 | | **paywall_revision** | 交易来源付费墙的版本号。 | | **profile_county** | Adapty 根据 IP 地址确定的用户画像所在国家。 | | **install_date** | 安装发生时的 ISO 8601 日期。 | | **idfv** | iOS 设备上的 [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor)。 | | **idfa** | iOS 设备上的 [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier)。 | | **advertising_id** | Android 操作系统分配的唯一代码,广告商可用其唯一标识用户设备。 | | **ip_address** | 设备 IP(可为 IPv4 或 IPv6,优先使用 IPv4)。每次设备 IP 变更时更新。 | | **cancellation_reason** | <p>用户取消订阅的原因。</p><p></p><p>可为:</p><p>**iOS & Android** _voluntarily_cancelled_, _billing_error_, _refund_</p><p>**iOS** _price_increase_, _product_was_not_available_, _unknown_, _upgraded_</p><p>**Android** _new_subscription_replace_, _cancelled_by_developer_</p> | | **android_app_set_id** | [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) — 每台设备、每个开发者账号唯一且可由用户重置的 ID,用于非变现广告场景。 | | **android_id** | 在 Android 8.0(API 级别 26)及更高版本中,该值为 64 位数字(以十六进制字符串表示),对应用签名密钥、用户和设备的每种组合唯一。详情请参阅 [Android 开发者文档](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID)。 | | **device** | 面向终端用户显示的设备型号名称。 | | **currency** | 交易的三字母货币代码(ISO-4217)。 | | **store_country** | 由 Apple/Google 应用商店确定的用户画像所在国家。 | | **attribution_source** | 归因来源。 | | **attribution_network_user_id** | 归因来源分配给用户的 ID。 | | **attribution_status** | 可为 organic、non_organic 或 unknown。 | | **attribution_channel** | 营销渠道名称。 | | **attribution_campaign** | 营销活动名称。 | | **attribution_ad_group** | 归因广告组。 | | **attribution_ad_set** | 归因广告集。 | | **attribution_creative** | 归因创意关键词。 | | **attributes** | [自定义用户属性](setting-user-attributes#custom-user-attributes)的 JSON 数据,包含你在移动应用中配置发送的所有自定义属性。如需发送,请在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面启用 **Send User Attributes** 选项。 | | **integration_ids** | 与用户画像关联的所有集成 ID,为字典格式。示例:{'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | Here is the table structure for the paywall visits: | 列名 | 描述 | | :-------------------- | :----------------------------------------------------------------------------------------------------------- | | **profile_id** | Adapty 用户 ID。 | | **customer_user_id** | 开发者用户 ID。例如,可以是您的用户 UUID、邮箱或其他任意 ID。如未设置则为空。 | | **profile_country** | 由 Apple/Google 应用商店确定的用户画像所在国家/地区。 | | **install_date** | ISO 8601 格式的安装日期。 | | **store** | 值为 _app_store_ 或 _play_store_。 | | **paywall_showed_at** | 付费墙向用户展示的日期。 | | **developer_id** | 交易来源付费墙的开发者(SDK)ID。 | | **ab_test_name** | 交易来源 A/B 测试的名称。 | | **ab_test_revision** | 交易来源 A/B 测试的版本号。 | | **paywall_name** | 交易来源付费墙的名称。 | | **paywall_revision** | 交易来源付费墙的版本号。 | ## 事件与标签 \{#events-and-tags\} 您可以管理集成所传递的数据。该集成提供以下配置选项: | 设置 | 描述 | | :--------------------------------- | :----------------------------------------------------------- | | **Exclude Historical Events** | 选择排除用户在安装含有 Adapty SDK 的应用之前发生的事件。这可以防止事件重复,确保报告准确。例如,如果某用户在 1 月 10 日激活了月度订阅,并在 3 月 6 日更新了包含 Adapty SDK 的应用,则 Adapty 将忽略 3 月 6 日之前的事件,并保留此后的事件。 | | **Include events without profile** | 选择包含未关联到 Adapty 用户画像的交易。这些交易可能包括在安装 Adapty SDK 之前发生的购买,或从应用商店服务器通知中收到的、暂时无法关联到特定用户的交易。 | | **Send User Attributes** | 如果您希望发送用户特定属性(例如语言偏好),且您的 OneSignal 套餐支持超过 10 个标签,请选择此选项。启用后,可在默认 10 个标签之外包含额外信息。请注意,超出标签限制可能会导致错误。 | <img src="/assets/shared/img/s3-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在集成设置下方,有三组事件可供您从 Adapty 导出、发送并存储到 Amazon S3。只需开启您需要的事件即可。点击[此处](events)查看 Adapty 提供的完整事件列表。 <img src="/assets/shared/img/fd5ccb9-CleanShot_2023-08-17_at_14.49.282x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: google-cloud-storage --- --- title: "Google Cloud Storage" description: "将 Google Cloud Storage 与 Adapty 集成,实现安全的数据存储。" --- 启用 Google Cloud Storage 集成,将[订阅事件](events)和[付费墙访问数据](paywall-metrics)安全存储在一个统一位置:你的 Google Cloud Storage 存储桶中。 每天 UTC 时间凌晨 4 点,Adapty 会将前一天的数据以 .csv 文件形式上传到您的存储桶。您可以选择接收**事件**数据、**付费墙访问**数据,或**两者都要**。您也可以随时[手动导出](#manual-data-export)任意时间段的数据。 要设置集成,请先在 Google Cloud 控制台中[生成存储桶访问密钥](#create-google-cloud-storage-credentials),然后[将其添加到 Adapty 设置中](#set-up-google-cloud-storage-integration)。 ## 上传计划与时长 \{#upload-schedule-and-duration\} Adapty 每 24 小时在 UTC 时间 04:00 向 Google Cloud Storage 上传数据。 文件包含在前一个日历日(UTC)内创建的事件数据。3 月 8 日上传的文件将包含 3 月 7 日 00:00:00 至 23:59:59 UTC 期间创建的所有事件。 该过程可能需要数小时,具体取决于队列中的文件总数以及您个人请求的数据量。如果 Adapty 在首次上传时包含历史数据,所需时间将长于后续的每日上传。 ## 设置 Google Cloud 存储集成 \{#set-up-google-cloud-storage-integration\} 您需要一个具有**写入权限**的有效 Google Cloud 服务账号密钥。如需生成密钥,请按照[创建凭证](#create-google-cloud-storage-credentials)部分的步骤操作。 :::warning 您可以为事件和付费墙访问分别使用不同的存储桶和凭证。但是,如果**任意一方**的凭证无效,[**两项上传均会失败**](#troubleshooting)。 ::: 前往 [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/integrations/google-cloud-storage),打开所需的标签页(**Events** 或 **Paywall visits**),然后启用该集成。 上传包含 **Google Cloud service account key** 的文件,指定目标 **bucket** 和 **folder**,保存更改。 ### 事件数据的可选设置 \{#optional-settings-for-event-data\} 你可以指定要包含在报告中的事件,并为事件设置自定义名称。完整的可用事件列表请参阅 [events](events) 文章。 | 名称 | 默认值 | 描述 | | ------------------------------ | ----------------- | ----------- | | Exclude historical events | true | 排除在您将 Adapty SDK 集成到应用之前发生的事件的相关信息。<br /> <br />如果您的分析平台在您开始使用 Adapty **之前**就已接收过订阅事件,此选项可确保它不会再收到任何重复事件。<Details summary="实际示例"><p>某用户于 1 月 10 日购买了月度订阅。您的应用在 3 月 1 日发布的更新中首次集成了 Adapty SDK。<br /> <br /> 如果此设置**开启**,报告将不包含 1 月份的"订阅开始"事件,也不包含 2 月份的"订阅续费"事件。但**会**包含 3 月 10 日的"订阅续费"事件。</p> </Details> | | Include events without profile | false | 包含未与用户画像关联或无法立即关联到特定用户的交易记录。这些记录可能包括在安装 Adapty SDK 之前发生的购买,或通过服务器通知收到的交易。 | | Send user attributes | false | 包含[自定义用户属性](setting-user-attributes),例如用户数据和应用使用数据。如果您的 OneSignal 计划支持 10 个以上标签,请选择此选项。请注意,超出标签限制可能会导致错误。 | ## 创建 Google Cloud Storage 凭据 \{#create-google-cloud-storage-credentials\} 本指南将帮助您在 Google Cloud Platform Console 中创建所需的凭据。 为了让 Adapty 将原始数据报告上传到您指定的存储桶,需要提供服务账号密钥,并授予对应存储桶的写入权限。通过提供服务账号密钥并授予存储桶写入权限,您可以允许 Adapty 安全、高效地将原始数据报告从其平台传输到您的存储环境中。 :::warning 请注意,我们仅支持服务账户 HMAC 密钥授权,因此必须确保您的服务账户 HMAC 密钥已添加"Storage Object Viewer"、"Storage Legacy Bucket Writer"和"Storage Object Creator"角色,以便正常访问 Google Cloud Storage。 ::: 1. 第一步,前往您 Google Cloud 账户的 [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) 页面,选择相关项目或新建一个项目。 1. 接下来,点击"+ CREATE SERVICE ACCOUNT"按钮,为 Adapty 创建一个新的服务账号。 2. 填写第一步中的字段,访问权限将在后续阶段授予。如需了解该页面的更多详情,请参阅[此处](https://docs.cloud.google.com/iam/docs/service-accounts-create)的文档。 3. 要创建并下载[私有 JSON 密钥](https://docs.cloud.google.com/iam/docs/keys-create-delete),请导航到 KEYS 部分,然后点击"ADD KEY"按钮。 4. 在 DETAILS 部分,找到与刚刚创建的服务账号关联的 Email 值并复制。在后续步骤中,您需要用到此信息来授权该账号并允许其写入存储桶。 5. 接下来,前往 Google Cloud Storage 的 [Buckets](https://console.cloud.google.com/storage/browser) 页面,选择已有的存储桶或创建新存储桶,用于存储来自 Adapty 的 Event 或 Visits Data 报告。然后导航到 PERMISSIONS 部分,选择 [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en) 选项。 6. 在 PERMISSIONS 部分,输入在第五步中获取的服务账户 Email,然后选择 Storage Object Creator 角色。最后,点击 SAVE 保存更改。 请记住存储桶的名称,以备后续使用。 ## 手动数据导出 \{#manual-data-export\} 除了自动将事件数据导出到 Google Cloud Storage 之外,Adapty 还提供手动文件导出功能。使用此功能,您可以选择特定时间段的事件数据,并手动将其导出到您的 GCS 存储桶。这使您能够更好地控制导出哪些数据以及何时导出。 指定的日期范围将用于导出从日期 A 00:00:00 UTC 到日期 B 23:59:59 UTC 期间创建的事件。 ## 数据结构 \{#data-structure\} Adapty 使用 `.csv` 文件以表格格式导出数据。 :::warning 事件内容可能会随时间增长——由我们或我们合作的第三方引入新数据。请确保您处理这些数据的代码足够健壮,依赖于特定字段,而非整体结构。 ::: ### 事件 \{#events\} 你可以[修改](#optional-settings-for-event-data)报告中包含的事件列表。 :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: | 列名 | 描述 | |------|-----------| | **profile_id** | Adapty 用户 ID。 | | **event_type** | 小写的事件名称。请参阅[事件](events)部分了解事件类型。 | | **event_datetime** | ISO 8601 日期。 | | **transaction_id** | 交易(如购买或续订)的唯一标识符。 | | **original_transaction_id** | 原始购买的交易标识符。 | | **subscription_expires_at** | 订阅到期日期,通常为未来时间。 | | **environment** | 可为 Sandbox 或 Production。 | | **revenue_usd** | 以美元计的收入,可为空。 | | **proceeds_usd** | 以美元计的收益,可为空。 | | **net_revenue_usd** | 以美元计的净收入(税后收入),可为空。 | | **tax_amount_usd** | 以美元计的扣税金额,可为空。 | | **revenue_local** | 以本地货币计的收入,可为空。 | | **proceeds_local** | 以本地货币计的收益,可为空。 | | **net_revenue_local** | 以本地货币计的净收入(税后收入),可为空。 | | **tax_amount_local** | 以本地货币计的扣税金额,可为空。 | | **customer_user_id** | 开发者用户 ID,例如可以是用户的 UUID、邮箱或其他任意 ID。若未设置则为 Null。 | | **store** | 可为 *app_store* 或 *play_store*。 | | **product_id** | Apple App Store、Google Play Store 或 Stripe 中的产品 ID。 | | **base_plan_id** | Google Play Store 中的[基础方案 ID](https://support.google.com/googleplay/android-developer/answer/12154973),或 Stripe 中的[价格 ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices)。 | | **developer_id** | 交易来源付费墙的开发者(SDK)ID。 | | **ab_test_name** | 交易来源 A/B 测试的名称。 | | **ab_test_revision** | 交易来源 A/B 测试的版本号。 | | **paywall_name** | 交易来源付费墙的名称。 | | **paywall_revision** | 交易来源付费墙的版本号。 | | **profile_country** | Adapty 根据 IP 地址判断的用户画像所在国家/地区。 | | **install_date** | 安装发生时的 ISO 8601 日期。 | | **idfv** | iOS 设备上的 [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) | | **idfa** | iOS 设备上的 [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) | | **advertising_id** | 广告 ID 是由 Android 操作系统分配的唯一代码,广告主可用其唯一标识某台用户设备。 | | **ip_address** | 设备 IP(可为 IPv4 或 IPv6,优先使用 IPv4),每次设备 IP 变更时更新。 | | **cancellation_reason** | <p>用户取消订阅的原因。</p><p></p><p>可能的值:</p><p>**iOS & Android** — *voluntarily_cancelled*、*billing_error*、*refund*</p><p>**仅 iOS** — *price_increase*、*product_was_not_available*、*unknown*、*upgraded*</p><p>**仅 Android** — *new_subscription_replace*、*cancelled_by_developer*</p> | | **android_app_set_id** | [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) — 每台设备、每个开发者账号唯一、用户可重置的 ID,用于非变现广告场景。 | | **android_id** | 在 Android 8.0(API level 26)及更高版本上,该字段为一个 64 位数字(以十六进制字符串表示),对应用签名密钥、用户和设备的每种组合唯一。详情请参阅 [Android 开发者文档](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID)。 | | **device** | 对终端用户可见的设备型号名称。 | | **currency** | 交易的三字母货币代码(ISO-4217)。 | | **store_country** | Apple/Google 应用商店判断的用户画像所在国家/地区。 | | **attribution_source** | 归因来源。 | | **attribution_network_user_id** | 归因来源分配给用户的 ID。 | | **attribution_status** | 可为 organic、non_organic 或 unknown。 | | **attribution_channel** | 营销渠道名称。 | | **attribution_campaign** | 营销活动名称。 | | **attribution_ad_group** | 归因广告组。 | | **attribution_ad_set** | 归因广告集。 | | **attribution_creative** | 归因创意关键词。 | | **attributes** | [自定义用户属性](setting-user-attributes#custom-user-attributes)的 JSON 数据,包含你在移动端应用中设置并发送的所有自定义属性。如需发送,请在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面中启用 **Send User Attributes** 选项。 | | **integration_ids** | 与用户画像关联的所有集成 ID,以字典形式呈现。示例:{'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | ### 付费墙访问次数 \{#paywall-visits\} | 字段 | 描述 | | :-------------------- | :----------------------------------------------------------------------------------------------------------- | | **profile_id** | Adapty 用户 ID。 | | **customer_user_id** | 开发者用户 ID。例如,可以是您的用户 UUID、邮箱或其他任意 ID。如果未设置则为 Null。 | | **profile_country** | 由 Apple/Google 应用商店确定的用户画像所在国家/地区。 | | **install_date** | ISO 8601 格式的安装日期。 | | **store** | 可为 *app_store* 或 *play_store*。 | | **paywall_showed_at** | 付费墙向用户展示的日期。 | | **developer_id** | 交易来源付费墙的开发者(SDK)ID。 | | **ab_test_name** | 交易来源 A/B 测试的名称。 | | **ab_test_revision** | 交易来源 A/B 测试的修订版本号。 | | **paywall_name** | 交易来源付费墙的名称。 | | **paywall_revision** | 交易来源付费墙的修订版本号。 | ## 故障排查 \{#troubleshooting\} Adapty 在开始上传**之前**会检查您的访问密钥的有效性。即使只有一个 Google Cloud Storage 密钥无效,Adapty 也会**中止上传**并抛出错误。 为确保上传不中断,请在密钥过期之前替换它们。如果您更新了**事件**的密钥,请不要忘记同时更新**付费墙访问**的密钥,反之亦然。 --- # File: webhook-event-types-and-fields --- --- title: "Webhook 事件类型与字段" description: "" --- Adapty 会在订阅事件发生时发送 webhook。本节介绍这些事件类型及每个 webhook 包含的数据字段。 ## Webhook 事件类型 \{#webhook-event-types\} 您可以将所有事件类型发送到 Webhook,也可以只选择其中部分类型。您可以参考我们的[事件流程](event-flows),了解预期接收的数据格式以及如何围绕它构建业务逻辑。在[设置 Webhook 集成](set-up-webhook-integration#configure-webhook-integration-in-the-adapty-dashboard)时,您可以禁用不需要的事件类型,也可以在那里用自定义 ID 替换 Adapty 默认的事件 ID。 | 事件名称 | 描述 | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | 当用户激活没有试用期的付费订阅时触发,即立即扣款。 | | subscription_renewed | 订阅续费并成功扣款时发生。该事件从第二次计费开始记录,无论是试用订阅还是非试用订阅。 | | subscription_renewal_cancelled | 用户已关闭订阅自动续费。用户在付费订阅周期结束前仍可使用高级功能。 | | subscription_renewal_reactivated | 当用户重新激活订阅自动续费时触发。 | | subscription_expired | 当订阅取消后完全到期时触发。例如,用户在12月12日取消订阅,但订阅在12月31日到期,则该事件在12月31日记录。 | | subscription_paused | 当用户激活[订阅暂停](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause)时发生(仅限 Android)。 | | subscription_deferred | 当订阅购买被[延期](https://adapty.io/glossary/subscription-purchase-deferral/)时触发,允许用户延迟付款同时保留对高级功能的访问权限。此功能通过 Google Play Developer API 提供,可用于免费试用或帮助面临经济困难的用户。 | | non_subscription_purchase | 任何非订阅购买,例如永久授权或消耗型商品(如游戏内货币)。 | | trial_started | 当用户激活试用订阅时触发。 | | trial_converted | 当试用期结束并成功向用户扣款(首次购买)时发生。例如,用户的试用期至1月14日,但在1月7日被扣款,则该事件在1月7日记录。 | | trial_renewal_cancelled | 用户在试用期间关闭了订阅自动续费。用户在试用期结束前仍可使用高级功能,但不会被扣款或开始订阅。 | | trial_renewal_reactivated | 当用户在试用期间重新激活订阅自动续费时发生。 | | trial_expired | 当试用期结束且未转化为订阅时触发。 | | entered_grace_period | 当付款尝试失败且用户进入宽限期(如已启用)时发生。用户在此期间保留高级访问权限。 | | billing_issue_detected | 当扣款尝试中出现账单问题时触发(例如,卡余额不足)。 | | subscription_refunded | 当订阅被退款时触发(例如,由 Apple 客服处理)。 | | non_subscription_purchase_refunded | 当非订阅购买被退款时触发。 | | access_level_updated | 当用户的访问等级更新时发生。 | :::note `subscription_renewal_reactivated` 携带的是**之前**的产品 ID——即用户取消订阅时处于活跃状态的产品 ID——即使用户后来通过购买其他产品重新激活了订阅也是如此。Apple 在整个取消 → 重新激活链路中保持相同的 `original_transaction_id`,因此该事件反映的是原始产品。新产品将在下一个 `subscription_renewed` 事件中体现,届时新产品的计费正式开始。 ::: ## Webhook 事件结构 \{#webhook-event-structure\} Adapty 只会发送你在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面 **Events names** 部分所选择的事件。 Webhook 事件以 JSON 格式序列化。发送到您服务器的 `POST` 请求体将包含序列化事件,并封装在以下结构中。所有事件遵循相同的结构,但其字段会根据事件类型、商店以及您的具体配置有所不同。用户属性是您设置的[自定义用户属性](setting-user-attributes#custom-user-attributes),因此其内容取决于您的配置。归因数据字段在所有事件类型中保持一致,但归因列表取决于您的移动应用中使用了哪些归因来源。以下是一个事件示例: ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "idfv": "00000000-0000-0000-0000-000000000000", "idfa": "00000000-0000-0000-0000-000000000000", "advertising_id": "00000000-0000-0000-0000-000000000000", "profile_install_datetime": "2000-01-31T00:00:00.000000+0000", "user_agent": "ExampleUserAgent/1.0 (Device; OS Version) Browser/Engine", "email": "john.doe@company.com", "event_type": "subscription_started", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "profile_id": "00000000-0000-0000-0000-000000000000", "cohort_name": "All Users", "environment": "Production", "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "base_plan_id": "b1", "developer_id": "onboarding_placement", "ab_test_name": "onboarding_ab_test", "ab_test_revision": 1, "paywall_name": "UsedPaywall", "proceeds_usd": 4.2315, "variation_id": "00000000-0000-0000-0000-000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "store_country": "AR", "event_datetime": "2000-01-31T00:00:00.000000+0000", "proceeds_local": 4.2415, "tax_amount_usd": 0, "transaction_id": "0000000000000000", "net_revenue_usd": 4.2415, "profile_country": "AR", "paywall_revision": "1", "profile_event_id": "00000000-0000-0000-0000-000000000000", "tax_amount_local": 0, "net_revenue_local": 4.2415, "vendor_product_id": "onemonth_no_trial", "profile_ip_address": "10.10.1.1", "consecutive_payments": 1, "rate_after_first_year": false, "original_purchase_date": "2000-01-31T00:00:00.000000+0000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2000-01-31T00:00:00.000000+0000", "profile_has_access_level": true, "profile_total_revenue_usd": 4.99, "promotional_offer_id": null, "store_offer_category": null, "store_offer_discount_type": null }, "event_api_version": 1, "profiles_sharing_access_level": [{"profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem"}], "attributions": { "appsflyer": { "ad_set": "Keywords 1.12", "status": "non_organic", "channel": "Google Ads", "ad_group": null, "campaign": "Social media influencers - Rest of the world", "creative": null, "created_at": "2000-01-31T00:00:00.000000+0000" } }, "user_attributes": {"Favourite_color": "Violet", "Pet_name": "Fluffy"}, "integration_ids": {"firebase_app_instance_id": "val1", "branch_id": "val2", "one_signal_player_id": "val3"}, "play_store_purchase_token": { "product_id": "product_123", "purchase_token": "token_abc_123", "is_subscription": true } } ``` ### 事件字段 \{#event-fields\} 各类事件的事件参数均相同。 | **字段** | **类型** | **描述** | |---|---|---| | **advertising_id** | UUID | 广告 ID(仅限 Android)。 | | **attributions** | JSON | [归因数据](webhook-event-types-and-fields#attributions)。在 [Webhook 设置](https://app.adapty.io/integrations/customwebhook)中启用 **Send Attribution** 后包含此字段。 | | **customer_user_id** | String | 您应用中的用户 ID(UUID、邮箱或其他 ID),需在应用代码中[识别用户](ios-quickstart-identify)时设置。若未在应用代码中识别用户,或该用户为匿名用户(未登录),则此字段为 `null`。 | | **email** | String | 用户邮箱,需通过 Adapty SDK 中的 [`updateProfile`](setting-user-attributes) 方法,或通过服务端 API 创建/更新用户画像时设置。若未向 SDK 或 API 方法传入 `email` 值,则此字段为 `null`。 | | **event_api_version** | Integer | Adapty API 版本(当前版本:`1`)。 | | **event_datetime** | ISO 8601 | 事件的业务发生时间,例如购买事件对应购买日期,到期事件对应到期日期,而非 Adapty 接收或发送事件的时间。采用 [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) 格式(例如 `2020-07-10T15:00:00.000000+0000`)。有关排序的说明请参见下方注意事项。 | | **event_properties** | JSON | [事件属性](webhook-event-types-and-fields#event-properties)。 | | **event_type** | String | Adapty 格式的事件名称。完整列表请参见 [Webhook 事件类型](webhook-event-types-and-fields#webhook-event-types)。 | | **idfa** | UUID | 广告标识符(仅限 Apple)。对应 [Adapty 看板](https://app.adapty.io/profiles/users)用户画像中的 **IDFA**。若因追踪限制、儿童模式或隐私设置而不可用,则可能为 `null`。 | | **idfv** | UUID | 供应商标识符(IDFV),每位开发者唯一。对应 [Adapty 看板](https://app.adapty.io/profiles/users)用户画像中的 **IDFV**。 | | **integration_ids** | JSON | 用户集成 ID,需通过 Adapty SDK 中的 `setIntegrationIdentifier` 方法,或通过服务端 API 创建/更新用户画像时设置。不可用或集成已禁用时为 `null`。 | | **play_store_purchase_token** | JSON | [Play Store 购买令牌](webhook-event-types-and-fields#play-store-purchase-token),在 [Webhook 设置](https://app.adapty.io/integrations/customwebhook)中启用 **Send Play Store purchase token** 后包含此字段。 | | **profile_id** | UUID | Adapty 为每个用户画像自动生成的用户画像 ID。若未识别用户或允许购买发生在登录之前,同一 Apple/Google ID 可能关联多个不同的用户画像 ID。详情请参阅 [Adapty 如何处理父/继承用户画像](how-profiles-work#parent-and-inheritor-profiles)。 | | **profile_install_datetime** | ISO 8601 | 安装时间戳,采用 [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) 格式(例如 `2020-07-10T15:00:00.000000+0000`)。 | | **profiles_sharing_access_level** | JSON | 与当前用户画像共享访问等级的其他用户列表(不含当前用户)。若您的应用启用了访问等级共享,此列表将包含使用同一 Apple/Google ID 的其他用户画像。<br/>格式:<ul><li>**profile_id**:(UUID)Adapty ID</li><li>**customer_user_id**:(String)Customer User ID(如有)</li></ul> | | **user_agent** | String | 设备浏览器的 User-Agent。 | | **user_attributes** | JSON | 可设置的自定义数据,用于为用户画像补充应用专属信息。通常用于记录用户偏好(如主题、语言)或行为标记(是否完成用户引导、功能使用情况等)。<br/>格式为键值对,键为字符串,值可为字符串或数字(例如 `{"Favourite_color": "Violet", "Pet_name": "Fluffy"}`)。<br/>您可以在 Adapty 看板中针对单个用户画像手动设置自定义属性,也可通过 Adapty SDK 中的 `updateProfile` 方法以编程方式设置,或在通过服务端 API 创建/更新用户画像时设置。<br/>在 [Webhook 设置](https://app.adapty.io/integrations/customwebhook)中启用 **Send User Attributes** 后包含此字段。<p>虽然移动应用代码中的自定义属性值可设为浮点数或字符串,但通过服务端 API 或历史数据导入的属性可能以不同格式传入,此时布尔值和整数值将转换为浮点数。</p> | :::note `event_datetime` 反映的是订阅生命周期中事件发生的时间,而非 Adapty 处理或推送该事件的时间。因此,多个事件可能共享相同的 `event_datetime`,或以非时间顺序到达。例如,`subscription_expired` 事件的 `event_datetime` 可能早于 Adapty 先行推送的 `subscription_renewal_cancelled` 事件。请勿依赖 `event_datetime` 对事件排序。应改用自行记录的接收时间排序,并通过 `profile_event_id` 或交易 ID 进行去重。 ::: ### 归因 \{#attributions\} 若要发送归因数据,请在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面中启用 **Send Attribution** 选项。启用后,若你已配置[归因集成](attribution-integration),以下数据将随每个来源的事件一并发送。所有事件类型均会收到相同的归因数据。 ```json title="Json" showLineNumbers { "attributions": { "appsflyer": { "ad_set": "sample_ad_set_123", "status": "non_organic", "channel": "sample_channel", "ad_group": "sample_ad_group_456", "campaign": "sample_ios_campaign", "creative": "sample_creative_789", "created_at": "2000-01-31T00:00:00.000000+0000", "network_user_id": "0000000000000-0000000" } } } ``` | 字段名 | 字段类型 | 描述 | | :------------------ | :------------ | :------------------------------------------------- | | **ad_set** | String | 归因广告组。 | | **status** | String | 可为 `organic`、`non_organic,` 或 `unknown`。 | | **channel** | String | 营销渠道名称。 | | **ad_group** | String | 归因广告集。 | | **campaign** | String | 营销活动名称。 | | **creative** | String | 归因创意关键词。 | | **created_at** | ISO 8601 date | 归因记录的创建日期和时间。 | | **network_user_id** | String | 归因来源为用户分配的 ID。 | ### 集成 ID \{#integration-ids\} 以下集成 ID 目前在事件中使用: - `adjust_device_id` - `airbridge_device_id` - `amplitude_device_id` - `amplitude_user_id` - `appmetrica_device_id` - `appmetrica_profile_id` - `appsflyer_id` - `branch_id` - `facebook_anonymous_id` - `firebase_app_instance_id` - `mixpanel_user_id` - `pushwoosh_hwid` - `one_signal_player_id` - `one_signal_subscription_id` - `tenjin_analytics_installation_id` - `posthog_distinct_user_id` ### Play Store 购买令牌 \{#play-store-purchase-token\} 此字段包含重新验证购买所需的全部数据(如有需要)。只有在 [Webhook 集成设置](https://app.adapty.io/integrations/customwebhook)中启用了 **Send Play Store purchase token** 选项后,才会发送该字段。 | 字段 | 类型 | 描述 | | :------------------ | :------ | :----------------------------------------------------------- | | **product_id** | String | 在 Play Store 中购买的产品唯一标识符(SKU)。 | | **purchase_token** | String | Google Play 生成的令牌,用于唯一标识本次购买交易。 | | **is_subscription** | Boolean | 表示所购产品是否为订阅(`true`)或一次性购买(`false`)。 | ### 事件属性 \{#event-properties\} 事件属性因事件类型而异,即使是同类事件也可能有所不同。例如,来自 App Store 的事件不会包含 `base_plan_id` 等 Android 专属属性。 [访问等级更新](webhook-event-types-and-fields#for-access-level-updated-event)事件具有独特的属性,因此我们为其单独开辟了一个章节。同样,[附加税务和收入事件属性](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties)也被单独列出,因为它们仅适用于特定的事件类型。 #### 适用于大多数事件类型 \{#for-most-event-types\} 大多数事件类型的事件属性是一致的(**Access Level Updated** 事件除外,该事件在其专属章节中单独说明)。下表列出了所有属性,并标注了各属性所属的具体事件。 :::note Adapty 使用 [currencylayer.com](https://currencylayer.com/) 的汇率(每 8 小时更新一次)将其他货币换算为美元。汇率**在交易发生时锁定**——后续汇率变动不会影响已换算的结果。 ::: | 字段 | 类型 | 描述 | |:------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **ab_test_name** | String | 交易来源的 [Adapty A/B 测试](ab-tests)名称。 | | **ab_test_revision** | Integer | 交易来源的 A/B 测试版本号。 | | **base_plan_id** | String | Google Play Store 中的[基础方案 ID](https://support.google.com/googleplay/android-developer/answer/12154973),或 Stripe 中的[价格 ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices)。 | | **cancellation_reason** | String | <p>可能的取消原因:`voluntarily_cancelled`、`billing_error`、`price_increase`、`product_was_not_available`、`refund`、`cancelled_by_developer`、`new_subscription_replace`、`upgraded`、`unknown`、`adapty_revoked`。</p><p>出现在以下事件类型中:</p>`subscription_cancelled`、`subscription_refunded` 和 `trial_cancelled`。 | | **cohort_name** | String | 决定用户看到哪个付费墙的[目标受众](audience)名称。 | | **consecutive_payments** | Integer | 用户连续订阅的周期数(无中断),包含当前周期。 | | **currency** | String | 本地货币。 | | **developer_id** | String | 交易来源的[版位](placements) ID。 | | **discount_amount_local** | Float | 交易中应用的折扣金额:标准价格减去实际收取金额(Apple/Google 抽成前),以本地货币计。全价购买时为 `0`。免费试用时等于完整标准价格(`original_price_local`),因为实际未收取任何费用。若已应用优惠但标准价格未知,则为 `null`(详见 `original_price_local`)。App Store 预付费优惠始终为 `null`:因为单次预付金额涵盖多个计费周期,无法与每周期标准价格进行比较。 | | **discount_amount_usd** | Float | `discount_amount_local` 对应的 USD 金额。 | | **environment** | String | 可能的值为 `Sandbox` 或 `Production`。 | | **event_datetime** | ISO 8601 date | 事件发生的日期和时间,与事件根层级的值相同。 | | **original_price_local** | Float | 产品在 Apple/Google 抽成前的标准非折扣价格,以本地货币计。对于订阅,这是续订价格。全价购买时等于 `price_local`;一次性购买时始终等于 `price_local`,因为应用商店不会单独报告一次性购买的标准价格。当应用商店无法提供可靠标准价格(例如自动续订已关闭、续订仍带有优惠,或正在进行产品变更)时,折扣购买的值为 `null`。 | | **original_price_usd** | Float | 与 `original_price_local` 相同,以 USD 计。 | | **original_purchase_date** | ISO 8601 date | 对于定期订阅,原始购买是链中的第一笔交易,其 ID 称为原始交易 ID,用于关联一系列续订;后续交易均为其延续。原始购买日期即第一笔交易的日期和时间。 | | **original_transaction_id** | String | <p>对于定期订阅,这是关联一系列续订的原始交易 ID。原始交易是链中的第一笔;后续交易均为其延续。</p><p>若无延续交易,则 `original_transaction_id` 与 store_transaction_id 相同。</p> | | **paywall_name** | String | 交易来源的付费墙名称。 | | **paywall_revision** | String | 交易来源的付费墙版本号,默认值为 1。 | | **price_local** | Float | 交易中实际收取的金额(Apple/Google 抽成前),以本地货币计。免费试用时为 `null`,因为未收取任何费用。 | | **price_usd** | Float | 交易中实际收取的金额(Apple/Google 抽成前),以 USD 计。免费试用时为 `null`,因为未收取任何费用。 | | **profile_country** | String | 由 Adapty 根据用户画像 IP 地址确定。 | | **profile_event_id** | UUID | 可用于去重的唯一事件 ID。 | | **profile_has_access_level** | Boolean | 布尔值,表示该用户画像是否拥有有效的访问等级。 | | **profile_id** | UUID | Adapty 生成的用户画像 ID,与事件根层级的值相同。 | | **profile_ip_address** | String | 用户画像 IP(可以是 IPv4 或 IPv6,优先使用 IPv4)。若在[应用设置](https://app.adapty.io/settings/general)中禁用了 **Collect users' IP addresses**,则为 `null`。 | | **profile_total_revenue_usd** | Float | 该用户画像的总收入(已扣除退款金额),以 USD 计。 | | **promotional_offer_id** | String | 所使用的[促销活动](offers)的 Adapty ID,在看板中创建优惠时由您设置。 | | **purchase_date** | ISO 8601 date | 产品购买的日期和时间。 | | **rate_after_first_year** | Boolean | 布尔值,表示该订阅是否在连续续订满一年后符合降低佣金率(通常为 15%)的条件。佣金率因计划资格和国家/地区而异。详情请参阅[商店佣金与税费](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue)。 | | **store** | String | 购买产品的应用商店。标准值:**app_store**、**play_store**、**stripe**、**paddle**。<br/>若通过服务端 API 设置了[自定义商店交易](api-adapty/operations/setTransaction),则使用 **store** 参数中的值。 | | **store_country** | String | 应用商店发送给我们的国家/地区信息。 | | **store_offer_category** | String | 已应用的优惠类别。可能的值为 `introductory`、`promotional`、`winback`。 | | **store_offer_discount_type** | String | 已应用的优惠类型。可能的值为 `free_trial`、`pay_as_you_go` 和 `pay_up_front`。 | | **store_offer_number_of_periods** | Integer | 优惠所涵盖的基础计费周期数(1 个或更多)。仅在应用优惠时出现。App Store 预付费优惠以及应用商店未报告优惠时长时为 `null`。 | | **subscription_expires_at** | ISO 8601 date | 订阅的到期日期,通常为未来某一时间。 | | **transaction_id** | String | 交易的唯一标识符。 | | **trial_duration** | String | 试用期时长(天数),格式为"{} days",例如"7 days"。仅出现在与试用相关的事件类型中:`trial_started`、`trial_converted`、`trial_cancelled`。 | | **variation_id** | UUID | 发生购买行为的付费墙的唯一 ID。 | | **vendor_product_id** | String | <p>Apple App Store、Google Play Store 或 Stripe 中的产品 ID。</p><p>若访问权限是在无真实商店交易的情况下授予的,则 `vendor_product_id` 将为以下之一:</p><ul><li>`adapty_server_side_product` — 通过[服务端 API](api-adapty/operations/grantAccessLevel) 授予。</li><li>`adapty_dashboard_product` — 在 Adapty 看板中[手动授予](give-access-level-to-specific-customer)。</li><li>`adapty_promotion` — 旧版。</li></ul> | #### 额外的税费与收入事件属性 \{#additional-tax-and-revenue-event-properties\} 以下与税费和收入相关的事件属性是仅适用于特定事件类型的附加字段。也就是说,下列事件类型在包含[大多数事件类型的事件属性](webhook-event-types-and-fields#for-most-event-types)的基础上,还会额外附带以下字段。 包含税费与收入事件属性的事件类型: - `subscription_renewed` - `subscription_initial_purchase`(也称为 `subscription_started`,属于同一事件) - `subscription_refunded` - `non_subscription_purchase` | 字段 | 类型 | 描述 | | :-------------------- | :---- | :----------------------------------------------------------- | | **net_revenue_local** | Float | 净收入(扣除 Apple/Google 手续费和税费后的收入),以本地货币计。 | | **net_revenue_usd** | Float | 净收入(扣除 Apple/Google 手续费和税费后的收入),以 USD 计。 | | **proceeds_local** | Float | 扣除 Apple/Google 手续费后的产品价格,以本地货币计。 | | **proceeds_usd** | Float | 扣除 Apple/Google 手续费后的产品价格,以 USD 计。 | | **tax_amount_local** | Float | 扣除的税费金额,以本地货币计。 | | **tax_amount_usd** | Float | 扣除的税费金额,以 USD 计。 | #### `non_subscription_purchase` 示例载荷 `non_subscription_purchase` 与订阅事件结构相同,但反映的是一次性购买或消耗型商品的购买。仅适用于订阅的字段不适用:`cancellation_reason`、`will_renew`、`is_in_grace_period`、`is_refund`、`is_lifetime` 和 `trial_duration` 均不存在。`subscription_expires_at` 字段存在但值为 `null`。税费和收入字段(`net_revenue_*`、`proceeds_*`、`tax_amount_*`)包含在内。 <details> <summary>示例载荷(点击展开)</summary> ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "non_subscription_purchase", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "app_store", "currency": "USD", "price_usd": 4.99, "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "proceeds_usd": 4.2415, "proceeds_local": 4.2415, "net_revenue_usd": 4.2415, "net_revenue_local": 4.2415, "tax_amount_usd": 0, "tax_amount_local": 0, "profile_id": "00000000-0000-0000-0000-000000000000", "environment": "Production", "vendor_product_id": "100coins", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "original_purchase_date": "2024-11-15T10:45:36.181000+0000", "subscription_expires_at": null, "store_country": "US", "profile_country": "US", "profile_ip_address": "10.10.1.1", "profile_has_access_level": false, "profile_total_revenue_usd": 4.99, "consecutive_payments": 1, "rate_after_first_year": false, "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` </details> #### 关于访问等级更新事件 \{#for-access-level-updated-event\} **Access Level Updated** 事件是一种特殊的 Webhook 事件,仅在 Webhook 集成处于活跃状态且该事件类型已启用时才会生成。启用后,该事件将发送至配置的 Webhook,并显示在 **Event Feed** 中。若未启用,则不会创建该事件。 如果您已启用[共享访问等级](general#6-sharing-paid-access-between-user-accounts),则 **access level updated** 事件将发送至所有共享该访问等级的用户画像。 :::tip 使用此事件来更新数据库中用户的访问等级、在后端授予或撤销高级功能,并保持跨设备或跨平台的访问同步。 ::: | 属性 | 类型 | 描述 | | ---------------------------------- | ------------- | ------------------------------------------------------------ | | **ab_test_name** | String | 交易来源的 A/B 测试名称。 | | **access_level_id** | String | 访问等级的 ID。 | | **activated_at** | ISO 8601 date | 访问权限最近一次激活的日期和时间。 | | **active_introductory_offer_type** | String | 已应用的新用户优惠类型。可选值为 `free_trial`、`pay_as_you_go` 和 `pay_up_front`。 | | **active_promotional_offer_id** | String | 促销活动的 ID,如 Adapty 看板产品部分所示。 | | **active_promotional_offer_type** | String | 已应用的促销活动类型。可选值为 `free_trial`、`pay_as_you_go` 和 `pay_up_front`。 | | **base_plan_id** | String | Google Play 商店中的[基础方案 ID](https://support.google.com/googleplay/android-developer/answer/12154973),或 Stripe 中的[价格 ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices)。 | | **billing_issue_detected_at** | ISO 8601 date | 发生计费问题的日期和时间。 | | **cancellation_reason** | String | 取消原因,可选值:`voluntarily_cancelled`、`billing_error`、`price_increase`、`product_was_not_available`、`refund`、`cancelled_by_developer`、`new_subscription_replace`、`upgraded`、`unknown`、`adapty_revoked`。 | | **cohort_name** | String | 用户画像所属目标受众的名称。 | | **currency** | String | 本地货币(默认为 USD)。 | | **developer_id** | String | 交易来源版位的 ID。 | | **environment** | String | 可选值为 `Sandbox` 或 `Production`。 | | **event_datetime** | ISO 8601 date | 事件的日期和时间。 | | **expires_at** | ISO 8601 date | 访问权限到期的日期和时间。 | | **is_active** | Boolean | 布尔值,表示访问等级是否处于激活状态。 | | **is_in_grace_period** | Boolean | 布尔值,表示用户画像是否处于宽限期内。 | | **is_lifetime** | Boolean | 布尔值,表示访问等级是否为永久授权。 | | **is_refund** | Boolean | 布尔值,表示该交易是否为退款。 | | **original_purchase_date** | ISO 8601 date | 对于自动续订订阅,原始购买是该链中的第一笔交易,其 ID 称为原始交易 ID,用于关联续订链;后续交易均为其延续。原始购买日期即为第一笔交易的日期和时间。 | | **original_transaction_id** | String | <p>对于自动续订订阅,这是关联续订链的原始交易 ID。原始交易是链中的第一笔;后续交易均为其延续。</p><p>如果没有延续交易,`original_transaction_id` 与 store_transaction_id 相同。</p>原始购买的交易标识符。 | | **paywall_name** | String | 交易来源付费墙的名称。 | | **paywall_revision** | String | 交易来源付费墙的版本号。默认值为 1。 | | **profile_country** | String | 由 Adapty 根据用户画像 IP 判断。 | | **profile_event_id** | UUID | 唯一事件 ID,可用于去重。 | | **profile_has_access_level** | Boolean | 布尔值,表示用户画像是否拥有有效的访问等级。 | | **profile_id** | UUID | Adapty 内部用户画像 ID。 | | **profile_ip_address** | String | 用户画像的 IP 地址(可为 IPv4 或 IPv6,优先使用 IPv4)。若在[应用设置](https://app.adapty.io/settings/general)中禁用了 **Collect users' IP addresses**,则为 `null`。 | | **profile_total_revenue_usd** | Float | 用户画像的总收入,含退款。 | | **purchase_date** | ISO 8601 date | 购买产品的日期和时间。 | | **renewed_at** | ISO 8601 date | 访问权限将续订的日期和时间。 | | **starts_at** | ISO 8601 date | 访问等级开始生效的日期和时间。 | | **store** | String | 购买产品的商店。标准值:**app_store**、**play_store**、**stripe**、**paddle**。<br/>如果你通过服务端 API 设置了[自定义商店交易](api-adapty/operations/setTransaction),则使用 **store** 参数中的值。 | | **store_country** | String | 应用商店发送给 Adapty 的国家信息。 | | **subscription_expires_at** | ISO 8601 date | 订阅的到期日期。 | | **transaction_id** | String | 交易的唯一标识符。 | | **trial_duration** | String | 试用期时长,以天为单位(例如:"7 days")。 | | **variation_id** | UUID | 实验变体的标识符,用于将购买归因到对应付费墙。 | | **vendor_product_id** | String | <p>商店(Apple/Google/Stripe)中的产品 ID。</p><p>如果访问权限是在没有真实商店交易的情况下授予的,`vendor_product_id` 将为以下之一:</p><ul><li>`adapty_server_side_product` — 通过[服务端 API](api-adapty/operations/grantAccessLevel) 授予。</li><li>`adapty_dashboard_product` — 在 Adapty 看板中[手动授予](give-access-level-to-specific-customer)。</li><li>`adapty_promotion` — 旧版。</li></ul> | | **will_renew** | Boolean | 表示付费访问等级是否将自动续订。 | :::warning 请注意,此结构可能会随着时间推移而扩展——我们或我们合作的第三方可能会引入新的数据字段。请确保处理该结构的代码足够健壮,依赖特定字段而非整个结构。 ::: --- # File: set-up-webhook-integration --- --- title: "设置 Webhook 集成" description: "在 Adapty 中设置 Webhook 集成,自动化事件追踪。" --- Adapty [Webhook 集成](webhook) 由以下步骤组成: <img src="/assets/shared/img/webhook-setup.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> 1. **您设置好端点:** 1. 确保您的服务器能够处理 Adapty 请求,并将 **Content-Type** 请求头设置为 `application/json`。 2. 配置您的服务器以接收 Adapty 的验证请求,并返回任意 `2xx` 状态码和 JSON 响应体。 3. 连接验证通过后,[处理订阅事件](#subscription-events)。 2. **您在 [Adapty 看板](#configure-webhook-integration-in-the-adapty-dashboard)中配置并启用 Webhook 集成。** 您也可以[将 Adapty 事件映射到自定义事件名称](#configure-webhook-integration-in-the-adapty-dashboard)。建议先在 **Sandbox environment** 中测试,再切换到生产环境。 3. **Adapty 向您的服务器发送验证请求。** 4. **您的服务器返回** `2XX` 状态码和 JSON 响应体。 5. **Adapty 收到有效响应后,即开始发送订阅事件。** ## 设置服务器以处理 Adapty 请求 \{#set-up-your-server-to-process-adapty-requests\} Adapty 会向你的 webhook 端点发送 2 种类型的请求: 1. [验证请求](#verification-request):用于验证连接是否正确建立的初始请求。该请求不包含任何事件,将在您点击 Adapty 看板 Webhook 集成中的 **Save** 按钮时立即发送。为确认您的端点成功接收到验证请求,您的端点应返回验证响应。 2. [订阅事件](#subscription-events):Adapty 服务器在每次创建事件时发送的标准请求。您的服务器无需返回任何特定响应,Adapty 服务器唯一需要的是在成功接收消息后收到标准的 HTTP 200 响应码。 ### 验证请求 \{#verification-request\} 在 Adapty 看板中启用 webhook 集成后,Adapty 会发送一个 POST 验证请求,请求体为空 JSON 对象 `{}`。 请将你的端点配置为使用 **Content-Type header** `application/json`,即你的服务器端点应接受以 JSON 格式传入的 webhook 请求。 你的服务器必须返回 2xx 状态码,并发送任意有效的 JSON 响应,例如: ```json title="Json" {} ``` 一旦 Adapty 收到格式正确且状态码为 2xx 的验证响应,您的 Adapty webhook 集成即配置完成。 ### 订阅事件 \{#subscription-events\} 订阅事件在发送时,**Content-Type** 请求头设置为 `application/json`,并以 JSON 格式包含事件数据。有关可能的事件类型和请求结构,请参阅 [Webhook 事件类型与字段](webhook-event-types-and-fields)。 ## 在 Adapty 看板中配置 Webhook 集成 \{#configure-webhook-integration-in-the-adapty-dashboard\} 在 Adapty 中,你可以为正式环境事件和测试事件(来自 Apple 或 Stripe 沙盒环境,或 Google 测试账号)分别配置独立的流程。 :::tip Adapty 每个环境(正式环境和沙盒环境)仅支持一个 Webhook URL。如需将事件推送至多个服务,请将 Webhook 指向你自己的后端,再由后端进行分发。 ::: 对于生产环境事件,请使用 **Production endpoint URL** 字段填写回调发送的目标 URL。同时配置 **Authorization header value for production endpoint** 字段——该字段用于您的服务器验证 Adapty 事件。请注意,我们会将 **Authorization header value for production endpoint** 字段中填写的值原样作为 `Authorization` 请求头发送,不做任何修改或添加。 对于测试事件,请相应地使用 **Sandbox endpoint URL** 和 **Authorization header value for sandbox endpoint** 字段。 要设置 webhook 集成: 1. 在 Adapty 看板中打开 [Integrations -> Webhook](https://app.adapty.io/integrations/customwebhook)。 <img src="/assets/shared/img/webhook_integration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 打开开关以启动集成。 4. 填写集成字段: | 字段 | 描述 | | ------------------------------------------------------ | ------------------------------------------------------------ | | **Production endpoint URL** | Adapty 用于在生产环境中发送事件 HTTP POST 请求的 URL。 | | **Authorization header value for production endpoint** | <p>您的服务器用于验证来自 Adapty 的生产环境请求的请求头。请注意,我们将使用此字段中指定的值作为 `Authorization` 请求头,不会进行任何修改或添加。</p><p></p><p>虽然不是必填项,但强烈建议配置以提升安全性。</p> | 此外,为了满足您在沙盒环境中的测试需求,还提供了另外两个字段: | 测试字段 | 说明 | | --------------------------------------------------- | ------------------------------------------------------------ | | **Sandbox endpoint URL** | Adapty 在沙盒环境中发送事件 HTTP POST 请求时所使用的 URL。 | | **Authorization header value for sandbox endpoint** | <p>您的服务器在沙盒环境测试期间,用于验证 Adapty 请求的请求头。请注意,我们会将该字段中指定的值原样作为 `Authorization` 请求头使用,不做任何修改或补充。</p><p></p><p>虽然非强制要求,但强烈建议配置此项以提升安全性。</p> | 4. (可选)选择您希望接收的事件并映射其名称。请查阅[事件流程](event-flows),了解在不同情况下会触发哪些事件。 如果您系统中的事件 ID 与 Adapty 中使用的 ID 不同,请保留您系统中的 ID,并在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面的 **Events names** 部分,将 Adapty 默认事件 ID 替换为您自己的 ID。 事件 ID 可以是任意字符串;只需确保 Webhook 处理服务器中的事件 ID 与您在 Adapty 看板中输入的一致。已启用的事件不能将事件 ID 留空。 <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 其他字段和选项并非必填,请按需使用: | 设置 | 描述 | | :--------------------------------- | :----------------------------------------------------------- | | **Send Trial Price** | 启用后,Adapty 将在 **Trial Started** 事件的 `price_local` 和 `price_usd` 字段中包含订阅价格。 | | **Exclude Historical Events** | 选择排除用户在安装含 Adapty SDK 的应用之前发生的事件。这可以防止事件重复,并确保报告准确。例如,若用户于 1 月 10 日激活了月度订阅,并于 3 月 6 日更新了含 Adapty SDK 的应用,则 Adapty 将忽略 3 月 6 日之前的事件,并保留此后的事件。 | | **Send user attributes** | 启用此选项以发送用户特定属性,例如语言偏好。这些属性将显示在 `user_attributes` 字段中。详见[事件字段](webhook-event-types-and-fields#event-fields)。 | | **Send attribution** | 启用此选项以在 `attributions` 字段中包含归因信息(例如 AppsFlyer 数据)。详见[归因数据](webhook-event-types-and-fields#attributions)部分。 | | **Send Play Store purchase token** | 启用此选项以接收购买重新验证所需的 Play Store 令牌(如有需要)。启用后将在事件中添加 `play_store_purchase_token` 参数。有关其内容的详细信息,请参阅 [Play Store 购买令牌](webhook-event-types-and-fields#play-store-purchase-token)部分。 | 6. 记得点击 **Save** 按钮确认更改。 点击 **Save** 按钮后,Adapty 将立即发送验证请求,并等待您的服务器返回验证响应。 ### 选择要发送的事件并映射事件名称 \{#choose-events-to-send-and-map-event-names\} 通过启用事件旁边的开关,选择您希望服务器接收的事件。如果您的事件名称与 Adapty 中使用的名称不同,且需要保留自定义名称,可以在 [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook) 页面的 **Events names** 部分,将默认的 Adapty 事件名称替换为您自己的名称,从而完成映射配置。 <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 事件名称可以是任意字符串。已启用的事件对应的字段不能留空。如果您不小心删除了 Adapty 事件名称,可以随时从[发送至第三方集成的事件](events)文档中复制。 ## 处理 Webhook 事件 \{#handle-webhook-events\} Webhook 通常在事件发生后 5 到 60 秒内送达。但取消事件可能在用户取消订阅后最长 2 小时才会送达。 如果您服务器的响应状态码不在 200-404 范围内,Adapty 会以指数退避方式重试。首次重试大约在初次失败后 **1 分钟**发生,此后每次间隔翻倍——最多重试 9 次,分布在 24 小时内。建议您将 Webhook 配置为仅对 Adapty 发来的事件体做基本校验后再响应。如果您的服务器无法处理该事件且不希望 Adapty 重试,请使用 200-404 范围内的状态码。此外,请将耗时任务改为异步处理,并尽快向 Adapty 返回响应。若 Adapty 在 10 秒内未收到响应,则视为本次尝试失败并将进行重试。 --- # File: test-webhook --- --- title: "测试 webhook 集成" description: "在 Adapty 中测试 webhook 集成,以自动化订阅事件追踪。" --- 完成集成设置后,就可以开始测试了。您可以测试沙盒环境和生产环境的集成。我们建议先从沙盒环境开始,并在其中进行充分验证: - 事件已发送并成功送达。 - 您已正确配置历史事件、**Trial started** 事件的订阅价格、归因、用户属性以及 Google Play Store 购买令牌的发送选项。 - 您正确映射了事件名称,且您的服务器能够正常处理这些事件。 ## 如何测试 \{#how-to-test\} 在开始测试集成之前,请确保您已完成以下操作: 1. 按照 [设置 webhook 集成](set-up-webhook-integration) 主题中的说明完成 webhook 集成配置。 2. 按照 [在 Apple App Store 中测试应用内购买](test-purchases-in-sandbox) 和 [在 Google Play Store 中测试应用内购买](testing-on-android) 主题中的说明配置好测试环境。请确保您在沙盒环境而非生产环境中构建了测试应用。 3. 进行购买/开始试用/发起退款等操作,以触发您选择发送到 webhook 的事件。例如,要获取 **Subscription started** 事件,请购买一个新的订阅。 ## 验证结果 \{#validation-of-the-result\} ### 事件发送成功的结果 \{#successful-sending-events-result\} 集成成功后,事件将出现在集成的 **Last sent events** 部分,并显示 **Success** 状态。 <img src="/assets/shared/img/6ccc3bb-webhook_integration_success.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 事件发送失败的结果 \{#unsuccessful-sending-events-result\} | 问题 | 解决方案 | |-----|--------| | 事件未出现 | 您的购买未成功,因此未创建事件。请参阅 [测试购买故障排查](troubleshooting-test-purchases) 主题以获取解决方案。 | | 事件已出现但显示 **Sending failed** 状态 | <p>我们根据 HTTP 状态码判断是否送达,**200-399 范围以外**的状态码均视为失败。</p><p>如需了解更多问题详情,请将鼠标悬停在失败事件的 **Sending failed** 状态上,如下图所示。</p> | <img src="/assets/shared/img/12ff189-hover_sending_failed.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: handle-integration-errors --- --- title: "处理集成中的错误" description: "处理集成中的错误" --- 使用任何归因、消息推送或分析集成时,您可能会遇到一些常见错误。请参阅本指南了解故障排除方案。 ## 数据差异 \{#data-discrepancy\} **原因**:这可能是因为并非所有用户都在使用包含 Adapty SDK 的应用版本。 **解决方案**:为确保数据一致性,您可以强制用户将应用更新至包含 Adapty SDK 的版本。 ## 网络错误 \{#network-errors\} **原因**:这很可能是因为 Adapty 服务器与集成服务器之间的网络连接中断。 **解决方案**:此类问题通常不会持续太久,且仅影响少量事件。 ## 集成服务器处理事件失败 \{#integration-server-failed-to-process-the-event\} **原因**:集成配置不正确。 **解决方案**:请参阅我们文档中关于该集成的文章,确保您已在 Adapty 看板、第三方工具端以及应用代码中完成所有配置步骤。 ## 缺少集成数据 \{#missing-integration-data\} **原因**:用户画像缺少某些集成专用 ID。这可能发生在应用代码中集成未正确配置的情况下。 **解决方案**:请参阅我们文档中关于该集成的文章,确保您已在应用代码中实现代码片段中的方法,并且这些方法确实与您的用户画像进行了交互。 ## 缺少集成凭据 \{#missing-integration-credentials\} **原因**:某些集成凭据缺失或不正确。 **解决方案**:请在 Adapty 看板上检查该集成的所有凭据。此问题可能由版本或环境不匹配引起。 ## 事件已过期 \{#the-event-has-expired\} **原因**:集成设置中启用了 **Exclude historical events** 选项,且事件的创建日期早于我们系统中该用户画像的创建日期。 如果一条从多年前开始的交易链通过收据验证传入 Adapty,而对应的用户画像是最近才创建的,则可能发生这种情况。 **解决方案**:确保新事件不会出现此情况。如果您希望将历史事件发送至集成,请禁用 **Exclude historical events**。 ## 已禁用/不支持的事件类型 \{#disabledunsupported-event-type\} **原因**:该集成不支持此事件类型,或者您在配置集成时将其禁用。例如,大多数集成不支持 `access_level_updated` 事件。 **解决方案**:请查阅集成文档,确认该集成是否支持此事件类型。如果支持,请在 Adapty 看板中确认该事件类型在集成设置中已启用。 --- # File: manage-adapty-with-ai --- --- title: "使用 AI 智能体和编程工具管理 Adapty" description: "使用 Adapty 与 AI 协作的各种方式——通过编程智能体集成 SDK、借助大语言模型查询分析数据,并将 Adapty 文档提供给 AI 工具。" --- Adapty 可与 AI 编程工具和智能体配合使用。无需离开编辑器,即可用它们集成 SDK、查询分析数据或查阅 Adapty 文档。本页列出了所有可用方式及其适用场景。 ## 使用 AI 集成 Adapty SDK \{#integrate-the-adapty-sdk-with-ai\} 有两种方式可通过 AI 编程工具将 Adapty SDK 接入你的应用,均支持 Cursor、Claude 及其他 AI 助手。 ### 技能式集成 \{#skill-based-integration\} Adapty SDK 集成技能可在 AI 编程工具中通过一条命令完成整个集成流程。适合希望获得引导式自动化配置的用户。 选择你的平台:[iOS](adapty-sdk-integration-skill) · [Android](adapty-sdk-integration-skill-android) · [React Native](adapty-sdk-integration-skill-react-native) · [Flutter](adapty-sdk-integration-skill-flutter) · [Unity](adapty-sdk-integration-skill-unity) · [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) · [Capacitor](adapty-sdk-integration-skill-capacitor) ### 分步集成 \{#step-by-step-integration\} 按阶段引导 AI 工具完成集成,依次提供对应文档。适合希望逐步审查每个步骤的用户。 选择你的平台:[iOS](adapty-cursor) · [Android](adapty-cursor-android) · [React Native](adapty-cursor-react-native) · [Flutter](adapty-cursor-flutter) · [Unity](adapty-cursor-unity) · [Kotlin Multiplatform](adapty-cursor-kmp) · [Capacitor](adapty-cursor-capacitor) ## 从命令行管理 Adapty \{#manage-adapty-from-the-command-line\} [Adapty Developer CLI](developer-cli-quickstart) 让你可以在终端中管理 Adapty 的各类实体——应用、访问等级、产品、付费墙和版位——无需打开看板。由于它是命令行工具,AI 编程智能体可以直接调用它。 ## 查询你的数据 \{#ask-about-your-data\} 将 AI 编程智能体指向 Export Analytics API,即可用自然语言查询你的数据指标——包括收入、留存率、LTV 等,无需 MCP 服务器。 [向 AI 查询你的分析数据](export-analytics-with-ai) ## 将 Adapty 文档提供给 AI 工具 \{#give-your-ai-tool-the-adapty-docs\} ### 纯文本文档 \{#plain-text-docs\} 所有 Adapty 文档均提供 Markdown 格式——在页面 URL 后添加 `.md`,或点击标题下方的 **Copy for LLM**。如需更广泛的上下文,可将 [`llms.txt`](https://adapty.io/docs/zh/llms.txt) 索引或特定平台的子集(如 [`ios-llms.txt`](https://adapty.io/docs/zh/ios-llms.txt))提供给你的工具。 ### Context7 \{#context7\} [Context7](https://context7.com/adaptyteam/adapty-docs) 是一个 MCP 服务器,可将 Adapty 文档提供给你的 AI 工具,但它仅索引代码片段,而非完整正文。如需快速获取代码示例可使用它;如需完整指引,请使用上述纯文本文档。Context7 支持 Cursor、Claude Code、Windsurf 及其他兼容 MCP 的工具。 --- # File: export-analytics-with-ai --- --- title: "向 AI 询问您的分析数据" description: "使用导出分析 API,通过 AI 编码代理以自然语言查询您的 Adapty 分析数据。" --- Ask an AI coding agent about your Adapty analytics in plain language — revenue, conversions, retention, LTV — and let it pull the numbers for you. Point a tool that can make API calls at the [Export Analytics API](https://adapty.io/docs/zh/export-analytics-api.md), and it queries your metrics on demand. ## 可查询的内容 \{#what-you-can-ask-about\} Export Analytics API 返回的数据与 Adapty 看板数据图表中展示的内容一致。每个数据图表对应一个独立的操作: | 数据图表 | 涵盖内容 | 操作 | | --- | --- | --- | | 收入、MRR、ARR、ARPU | 按时间段、国家或推广活动分组的收入情况 | [retrieveAnalyticsData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveAnalyticsData.md) | | 同期群留存 | 某一同期群的订阅用户持续付费的时长 | [retrieveCohortData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveCohortData.md) | | 转化率 | 用户从某一步骤或渠道进入下一步骤的比例 | [retrieveConversionData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveConversionData.md) | | 流失与漏斗 | 用户在哪些环节流失以及退订速度 | [retrieveFunnelData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveFunnelData.md) | | 用户生命周期价值 (LTV) | 各用户市场细分的平均收入随时间的变化 | [retrieveLTVData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveLTVData.md) | | 留存率 | 若干天后仍活跃的用户占比 | [retrieveRetentionData](https://adapty.io/docs/zh/api-export-analytics/operations/retrieveRetentionData.md) | 有关参数和过滤器的完整列表,请参阅 [API 参考文档](https://adapty.io/docs/zh/api-export-analytics.md)。 ## 开始之前 \{#before-you-start\} 你需要准备以下三样东西: - **已有数据的 Adapty 账户**:该 API 返回的数据图表与看板中显示的一致,因此你的应用必须已经在收集分析数据。 - **Secret API 密钥**:在 [App settings → General](https://app.adapty.io/settings/general) 的 **Secret key** 字段中找到。密钥与应用绑定,每个应用需使用独立的密钥。建议将其存储在环境变量中(例如 `ADAPTY_SECRET_KEY`),这样 AI 代理可以直接读取,无需手动粘贴到对话中。 - **能够调用 API 的 AI 工具**:例如 Claude Code、Cursor,或配置了 fetch 工具的 Claude Desktop。claude.ai 或 ChatGPT 等纯对话工具无法直接调用 API。 ## 为你的 Agent 提供 API 规范 \{#give-your-agent-the-api-spec\} [OpenAPI 规范](https://adapty.io/docs/zh/api-specs/export-analytics-api.yaml)描述了每个端点、认证请求头、请求体以及响应示例。Agent 获取规范后,无需你编写任何代码即可构建正确的请求。 通过 URL 向 Agent 提供规范: - **粘贴 URL**:如果你的 AI 助手支持抓取 URL,将 `https://adapty.io/docs/zh/api-specs/export-analytics-api.yaml` 提供给它,让它读取该规范文件。 - **使用抓取工具**:如果你的 AI 助手有可以获取 URL 的工具(例如 MCP fetch 服务器),将其指向同一 URL 即可。 该规范将 base URL 设置为 `https://api-admin.adapty.io`,因此只要你的密钥已配置到环境中,AI 助手即可获取所有所需信息。 ## 查询你的数据 \{#ask-about-your-data\} 加载好规范文件并将密钥设置为环境变量后,就可以用自然语言描述你想查询的数据图表了。 示例提示词: ``` What was my MRR at the end of each month this year, and how does it compare to last year? Show my trial-to-paid conversion rate for the last 90 days, broken down by product. Which countries drive the most revenue from my yearly subscription? Top 10. How is week-1 retention trending for subscribers who started in the last 6 months? What's the refund rate on my annual plan since launch, by month? Compare LTV for paid-campaign users vs. organic over the last year, and export it as CSV. ``` 代理会将您的请求映射到对应操作,从环境变量中读取密钥,并返回数据。默认响应格式为 JSON。如果需要方便导入电子表格的文件,可以要求 CSV 格式——代理会在请求体中将 `format` 设置为 `csv`。 :::warning 请将密钥保存在环境变量中,不要粘贴到聊天记录里,也不要提交到规则文件中。密钥与应用绑定,如有泄露,请在 **Settings → General** 中轮换密钥。详见[轮换 API 密钥](https://adapty.io/docs/zh/export-analytics-api-authorization.md)。 ::: ## 一次配置,反复使用 \{#set-up-once-for-repeated-use\} 为避免每次会话都重复配置,请将 spec 和 key 保存到 agent 可复用的位置: - **保存规格链接**:将规格 URL 添加到你的智能体规则或记忆文件中(例如 `CLAUDE.md` 或 Cursor 规则文件),这样每次会话都会自动加载。 - **将密钥存入环境变量**:将 `ADAPTY_SECRET_KEY` 保存到你的 Shell 配置文件或工具的密钥存储中,以后就不需要再手动粘贴了。 - **保存常用提示词或创建自定义技能**:将常用问题保存为提示词,或封装成自定义技能或斜杠命令,让智能体随时按需生成报告。 ## 限制 \{#limits\} 请注意以下约束: - **速率限制**:API 每个 API key 每秒允许 2 次请求,超出限制将返回 `429 Too Many Requests` 错误。请告知你的 agent 在收到 `429` 时等待并重试。 - **应用专属密钥**:每个密钥仅对应一个应用。如需拉取多个应用的数据,请为每个应用提供对应的密钥。 - **输出格式**:默认响应格式为 JSON。如需导出 CSV,请在请求体中将 `format` 设置为 `csv`。 完整的身份验证和请求规则,请参阅[授权与请求格式](https://adapty.io/docs/zh/export-analytics-api-authorization.md)。 --- # File: handle-webhooks-with-ai --- --- title: "使用 Webhook 处理 Adapty 订阅事件" description: "通过 Webhook 在服务器端接收并处理 Adapty 订阅事件——涵盖端点配置、身份验证、数据载荷及测试的完整指南。" --- Webhooks 让您的服务器能够实时接收 Adapty 订阅事件——包括购买、续订、取消、账单问题和退款——从而授予访问权限、同步后端或触发工作流。本指南将带您在同一页面上完成从端点配置到验证、测试的完整集成流程,并介绍如何让 AI 编程助手为您的技术栈编写处理程序。 :::tip 正在使用 AI 编程助手?点击标题下方的 **Copy for LLM**,将整个页面粘贴到您的助手中——它包含所需的配置说明、数据载荷和处理逻辑。 ::: ## Adapty Webhook 的工作原理 \{#how-adapty-webhooks-work\} - **单向实时推送**:当事件发生时,Adapty 会向你的服务器发送 HTTP `POST` 请求,无需轮询。 - **两种请求类型**:一次性验证请求(在你保存集成时发送)和持续的订阅事件通知。 - **每个环境独立 URL**:你需要分别为生产环境和沙盒环境配置独立的接收端点。 - **需要应答每个请求**:请尽快以 `2xx` 状态码响应,否则 Adapty 会在失败时重试。 ## 构建你的 Endpoint \{#build-your-endpoint\} 创建一个能处理以下两种请求类型的公开 HTTPS Endpoint: - **验证请求**:在你保存集成时发送一次,请求体为空 JSON(`{}`)。请返回 `2xx` 状态码及 JSON 响应体。 - **订阅事件**:持续发来的 `POST` 请求,事件内容在请求体中。请在 10 秒内返回 `200`,然后以异步方式处理耗时操作。 选择一个密钥字符串并将其存储为环境变量(例如 `ADAPTY_WEBHOOK_SECRET`)。在每次请求时,验证 `Authorization` 请求头是否与其匹配,若不匹配则拒绝该请求——稍后你将在看板中输入相同的密钥。 ```javascript title="webhook.js" const app = express(); app.use(express.json()); const WEBHOOK_SECRET = process.env.ADAPTY_WEBHOOK_SECRET; app.post("/adapty/webhook", (req, res) => { // 1. Verify the shared secret Adapty echoes back. if (req.get("Authorization") !== WEBHOOK_SECRET) { return res.sendStatus(401); } // 2. Acknowledge fast, then process asynchronously. res.status(200).json({}); // 3. The verification request has an empty body — nothing to handle. const event = req.body; if (!event.event_type) return; switch (event.event_type) { case "subscription_started": case "subscription_renewed": case "trial_converted": // Grant or extend access. break; case "subscription_expired": case "subscription_refunded": // Revoke access. break; default: break; } }); app.listen(3000); ``` 在配置集成之前,请先将端点部署到公开的 HTTPS URL——Adapty 会在你保存的瞬间发送验证请求。 ### 关键事件及其数据载荷 \{#key-events-and-the-payload\} 每个事件共享相同的外层结构。字段内容因事件类型、应用商店及所启用的选项而有所不同。以下是一个精简版的 `subscription_started` 事件示例: ```json title="Example event" { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "subscription_started", "event_datetime": "2024-11-15T10:45:36.181000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "vendor_product_id": "onemonth_no_trial", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2024-12-15T10:45:36.181000+0000", "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` 最常处理的事件: | 事件类型 | 触发时机 | | --- | --- | | `subscription_started` | 用户开始付费订阅 | | `subscription_renewed` | 订阅成功续期并完成扣款 | | `subscription_renewal_cancelled` | 用户关闭自动续订(访问权限持续至到期) | | `subscription_expired` | 订阅未续期到期后访问权限终止 | | `trial_started` | 用户开始免费试用 | | `trial_converted` | 试用转换为付费订阅 | | `billing_issue_detected` | 续订付款失败 | | `subscription_refunded` | 订阅购买被退款 | 完整的事件列表及每个字段的详细说明,请参阅 [Webhook 事件类型与字段](https://adapty.io/docs/zh/webhook-event-types-and-fields.md)。 :::warning 不要按 `event_datetime` 对事件排序——该字段表示事件的业务发生时间,事件可能乱序到达,也可能具有相同的时间戳。请按自己的接收时间排序,并使用 `profile_event_id` 或事务 ID 进行去重。 ::: ## 在 Adapty 中配置 Webhook \{#configure-the-webhook-in-adapty\} 1. 在 Adapty 看板中打开 [Integrations → Webhook](https://app.adapty.io/integrations/customwebhook)。 2. 开启该集成。 3. 在 **Production endpoint URL** 中,输入你部署的端点的 HTTPS URL。 4. 在 **Authorization header value for production endpoint** 中,输入与端点校验逻辑相同的密钥。Adapty 会在每次请求时通过 `Authorization` 请求头将该值回传给你的端点。此项为可选,但强烈建议填写。 5. 如需先在沙盒环境中测试,同样填写 **Sandbox endpoint URL** 及其 **Authorization header value**。 6. 点击 **Save**。Adapty 会立即向你的端点发送验证请求,端点返回 `2xx` 响应后即完成配置。 要选择发送哪些事件、映射事件名称,或启用可选字段(试用价格、历史事件、归因、用户属性、Play Store token),请参阅[设置 Webhook 集成](https://adapty.io/docs/zh/set-up-webhook-integration.md)。 ## 用 AI 编程助手来构建 \{#build-it-with-your-ai-coding-agent\} 将本指南和以下参考文档以 Markdown 格式提供给你的 AI 编程助手(在任意页面 URL 后加 `.md` 即可获取),告诉它你的技术栈,让它自动生成处理程序: - [Webhook 事件类型与字段](https://adapty.io/docs/zh/webhook-event-types-and-fields.md) - [配置 Webhook 集成](https://adapty.io/docs/zh/set-up-webhook-integration.md) 示例提示词: ``` Read these Adapty webhook docs, then write a webhook handler for my Express app: verify the Authorization header against ADAPTY_WEBHOOK_SECRET, answer the verification request, acknowledge events with 200, and grant or revoke access based on event_type. ``` 代理会编写处理程序代码,但无法部署你的端点或配置看板——请自行托管端点,并在 **Integrations → Webhook** 中设置 URL 和密钥。 ## 测试您的 Webhook \{#test-your-webhook\} 在正式上线前,请先在沙盒环境中进行测试: 1. 按照上述说明配置沙盒端点和密钥。 2. 在沙盒应用中完成购买、开启试用或申请退款,以触发相应事件。 3. 打开集成页面的 **Last sent events** 部分。已成功投递的事件会显示 **Success** 状态。 如果事件显示 **Sending failed**,说明您的服务器返回了 200–399 范围之外的状态码——将鼠标悬停在状态上可查看详情。完整的测试流程,请参阅[测试 Webhook 集成](https://adapty.io/docs/zh/test-webhook.md)。 ## 限制 \{#limits\} - **10 秒内响应**:如果 Adapty 未能在规定时间内收到响应,会将本次尝试标记为失败并重新发送。 - **重试机制**:如果您返回的状态码不在 200–404 范围内,Adapty 会以指数退避策略进行重试——在 24 小时内最多重试 9 次。 - **取消延迟**:取消事件最多可能延迟 2 小时送达。 - **每个环境仅支持一个 URL**:如需将事件推送至多个服务,请将 Webhook 指向您自己的后端,再由后端进行分发。 --- # File: server-side-api-with-ai --- --- title: "从后端检查并授予订阅访问权限" description: "使用 Adapty 服务端 API 检查用户是否拥有有效订阅,并通过 AI 编程助手手动授予访问权限。" --- 在您的后端,使用 Adapty 服务端 API 来检查用户是否拥有有效订阅,以及手动授予访问权限。本指南涵盖两个最常用的接口调用——`getProfile` 和 `grantAccessLevel`——并介绍如何让 AI 编程助手为您的技术栈编写集成代码。 :::tip 正在使用 AI 编程助手?点击标题下方的 **Copy for LLM**,将整个页面粘贴给您的助手——其中包含所需的接口调用、字段说明和注意事项。 ::: ## 开始之前 \{#before-you-start\} - **一个密钥(Secret API key)**:在 [App settings → General](https://app.adapty.io/settings/general) 的 **Secret key** 字段中找到它。密钥与应用绑定。请将其存储在环境变量中(例如 `ADAPTY_SECRET_KEY`),并通过 `Authorization: Api-Key {key}` 传递。 - **基础 URL**:所有请求均发送至 `https://api.adapty.io`。 - **识别用户的方式**:发送 `adapty-customer-user-id`(你自己的用户 ID——仅当你在应用中标识了用户时有效)或 `adapty-profile-id`(Adapty 用户画像 ID)。两者可互换,选其一即可。 ## 查看订阅 \{#check-a-subscription\} 要查看订阅状态,请使用 `GET` 方法调用 `getProfile`,并在请求头中传入用户标识符——无需请求体。 ```javascript title="check-access.js" const res = await fetch("https://api.adapty.io/api/v2/server-side-api/profile/", { headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, }, }); const { data } = await res.json(); function hasActiveAccess(profile, accessLevelId = "premium") { const level = profile.access_levels?.find(a => a.access_level_id === accessLevelId); if (!level) return false; if (level.is_in_grace_period) return true; if (!level.expires_at) return true; // lifetime / non-expiring return new Date(level.expires_at) > new Date(); // not expired yet } if (hasActiveAccess(data)) { // unlock premium features } ``` 与 SDK 的用户画像不同,服务端响应**没有 `is_active` 字段**。请自行根据 `access_levels[].expires_at` 判断状态:`null` 表示永久授权,未来日期表示有效,过去日期表示已过期。`is_in_grace_period` 应视为仍处于有效状态。完整的用户画像及访问等级字段说明,请参阅 [getProfile](https://adapty.io/docs/zh/api-adapty/operations/getProfile.md)。 ## 手动授予访问权限 \{#grant-access-manually\} 如需在不经过购买流程的情况下解锁付费功能——例如促销码、投资者或测试版访问权限、客服案例——可使用 `POST` 方法调用 `grantAccessLevel`。 ```javascript title="grant-access.js" await fetch("https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/", { method: "POST", headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, "Content-Type": "application/json", }, body: JSON.stringify({ access_level_id: "premium" }), // add "expires_at" for temporary access }); ``` 请注意以下两点: - **访问等级必须已存在**于看板中(**Access levels**)—— `access_level_id` 是该等级的标识符,而非新名称。 - **手动授予的访问等级不会出现在分析报告中**。相关数据仅会推送至你的 webhook 集成和 Event Feed,因此收入和转化率数据图表不会反映这些操作。 请求与响应的详细信息,请参阅 [grantAccessLevel](https://adapty.io/docs/zh/api-adapty/operations/grantAccessLevel.md)。 ## 使用 AI 编程助手来构建 \{#build-it-with-your-ai-coding-agent\} 将本指南和 API 规范(在任意页面 URL 后加 `.md` 即可获取 Markdown 格式)提供给你的 AI 编程助手,告诉它你使用的技术栈,让它帮你编写调用代码: - [OpenAPI 规范](https://adapty.io/docs/zh/api-specs/adapty-api.yaml) - [getProfile](https://adapty.io/docs/zh/api-adapty/operations/getProfile.md) - [grantAccessLevel](https://adapty.io/docs/zh/api-adapty/operations/grantAccessLevel.md) 示例提示词: ``` Using the Adapty server-side API spec, write backend functions to check whether a user has an active "premium" access level (GET /profile/, derive status from expires_at — there's no is_active field) and to grant it (grantAccessLevel). Authenticate with ADAPTY_SECRET_KEY and identify users by adapty-customer-user-id. ``` The agent writes the code, but it can't run your backend or set your keys — you provide the secret key and the user identifiers. ## 限制 \{#limits\} - **频率限制**:每个应用每分钟最多 40,000 次请求。 - **应用专属密钥**:每个密钥仅对应一个应用,请为每个应用使用对应的密钥。 - **必填标识符**:每个请求都需要提供 `adapty-customer-user-id` 或 `adapty-profile-id`。 --- # File: test-purchases-in-sandbox --- --- title: "沙盒测试" description: "在沙盒环境中测试购买流程,确保交易顺畅。" --- 在 Adapty 看板和移动应用中完成所有配置后,就可以开始进行应用内购买测试了。 **注意:** 任何测试工具都不会向用户收取实际费用。App Store 不会针对测试环境中的购买或退款发送邮件通知。 :::note **沙盒交易不会显示在任何分析数据图表中。** 它们仍会出现在各个用户画像页面和事件流中。 ::: :::info 在进行应用内购买测试之前,请确认以下事项: - 已完成[快速入门](quickstart)指南中关于商店集成、添加产品及 Adapty SDK 集成的步骤。 - 你的产品在 App Store Connect 中已标记为 [**Ready to submit**](InvalidProductIdentifiers#step-2-check-products)。 ::: ## 沙盒测试 \{#sandbox-testing\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/hq4PRU-vuik?si=m5F5Sj6iLEJ-2q6n" title="YouTube 视频播放器" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::info 我们建议在真机上测试应用内购买。虽然沙盒购买可以在模拟器上运行,但要完整测试所有流程(包括支付对话框和生物识别提示),还是需要真机。 ::: 你有两种主要方式来测试应用内购买: - **在 Xcode 中构建并在测试设备上运行**:适合开发人员和 QA 工程师。 - **通过 TestFlight 使用沙盒测试账户**:适合其他所有人。 以下指南将介绍这两种方式。 ### 第 1 步:在 App Store Connect 中创建沙盒测试账号 \{#step-1-create-sandbox-test-account-in-app-store-connect\} :::warning 请创建一个全新的沙盒测试账号,以确保购买历史是干净的。如果复用已有账号,之前购买过的产品将仍然可用,届时将无法再次测试购买流程。 ::: 只需几步即可创建新的沙盒测试账号: 1. 在 App Store Connect 中前往 [**Users and Access** > **Sandbox** > **Test Accounts**](https://appstoreconnect.apple.com/access/users/sandbox),然后点击 **+**。 <img src="/assets/shared/img/add-sandbox-user.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 填写测试用户信息。请务必设置 **Country or Region**,因为这会影响该地区的产品可用性和购买货币。 :::tip - 如果你使用 Gmail 或 iCloud,可以通过[加号子地址](https://www.wikihow.com/Use-Plus-Addressing-in-Gmail)复用现有邮箱地址。 - 你也可以使用一个根本不存在的随机邮箱地址,但请确保在测试设备上登录时拒绝双因素认证(2FA)。 ::: <img src="/assets/shared/img/57c3a7c-apple_new_test_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Create**。 ### 第二步:启用开发者模式 \{#step-2-enable-the-developer-mode\} :::note 如果您的测试设备**已启用**开发者模式,或者您**没有 Mac 设备**,请跳过此步骤。 ::: 您需要准备一台安装了 Xcode 的 Mac 以及测试设备的数据线: 1. 在 Mac 上打开 Xcode。如果您打算通过 TestFlight 测试应用内购买,只需确保已安装 Xcode 即可,不需要在其中打开任何项目。 2. 用数据线将测试设备连接到 Mac。 3. 在测试设备上进入 **Settings > Privacy & Security > Developer Mode**,然后开启**开发者模式**。 ### 第三步:从 TestFlight 下载应用 \{#step-3-download-the-app-from-testflight\} :::info 此步骤仅适用于通过 TestFlight 进行测试的情况。如果你在 Xcode 中直接构建应用,请跳过此步骤。 ::: 有关如何将应用提交至 TestFlight 的详细信息,请参阅 [Apple 文档](https://developer.apple.com/documentation/StoreKit/testing-in-app-purchases-with-sandbox#Prepare-for-sandbox-testing)。 在下载 TestFlight 应用之前,请确保在测试设备上已使用你的正式 Apple 账号登录,然后从 TestFlight 下载要测试的应用。 :::danger 下载完成后请勿打开应用,直接进行后续步骤。 如果不小心打开了,请从测试设备上删除该应用并重新下载。否则,您的购买记录可能不干净,测试应用内购时会出现错误。 ::: ### 第四步:切换到沙盒测试账号 \{#step-4-switch-to-sandbox-test-account\} <Details> <summary>不用 Mac?这里有个替代方案</summary> 如果你不在 macOS 上工作,就无法通过 Xcode 切换到沙盒账号。不过你仍然可以直接在测试设备上完成切换: 1. 在测试设备上前往 **Settings > Your Apple Account > Media & Purchases**。 2. 在弹出菜单中选择 **Sign Out**。 3. 打开从 TestFlight 下载的应用,尝试购买一个产品。 4. 当提示登录时,输入你的沙盒账号凭据,即可切换到沙盒环境。 </Details> 切换到沙盒账户: 1. 在测试设备上前往 **Settings > Your Apple Account > Media & Purchases**。 2. 从弹出菜单中选择 **Sign Out**。 3. 前往 **Settings > Developer**。如果找不到 **Developer** 选项,请确认你已[在第 2 步中启用了它](#step-2-enable-the-developer-mode)。 <img src="/assets/shared/img/devmode.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动到 **Sandbox Apple Account** 部分,然后点击 **Sign In**。 <img src="/assets/shared/img/sandbox-acc.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 使用您的沙盒 Apple 账户凭据登录。 ### 第 5 步:清除购买记录 \{#step-5-clear-purchase-history\} 如果你刚创建了一个新的沙盒测试账户并已切换到该账户,可以跳过此步骤,因为它仅适用于使用同一沙盒测试账户重复测试的情况。 1. 在测试设备上,前往 **Settings > Developer > Sandbox Apple Account**。 2. 从弹出菜单中选择 **Manage**。 3. 进入 **Account Settings**,然后点击 **Clear Purchase History**。 :::danger 每次使用同一个沙盒测试账号重复测试时,都需要执行此步骤。此时,你还需要[退出沙盒测试账号](#step-4-switch-to-sandbox-test-account),然后重新登录,以清除测试设备上的购买历史缓存。 ::: ### 第六步:在 Xcode 中构建并运行 \{#step-6-build-in-xcode-and-run\} :::info 此步骤仅适用于使用 Xcode 构建进行测试的情况。如果你使用的是 TestFlight,请跳过此步骤。 ::: 1. 将测试设备连接到 Mac。 2. 打开 Xcode。 3. 点击工具栏中的 **Run**,或选择 **Product > Run**,将应用构建并运行到已连接的设备上。 构建成功后,Xcode 会在你的设备上启动应用,并在调试区域开启调试会话。 现在,你的应用已准备就绪,可以在设备上进行测试了。 ### 第 7 步:进行测试购买 \{#step-7-make-test-purchase\} 打开应用,通过付费墙完成测试购买。 完成后,请前往[验证测试购买](validate-test-purchases)文章查看结果。 ### 第八步:继续测试 \{#keep-testing\} 现在,您的测试环境已全部配置完成。如需再次测试,请[清除沙盒账号的购买记录](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings/)。 ## 测试问题 \{#testing-issues\} 以下是测试应用时可能遇到的常见问题。 ### TestFlight 问题 \{#testflight-issues\} **如果你在 TestFlight 中未使用沙盒测试账号**,将无法清除购买记录,从而导致各种问题和错误的测试结果。 如果你不小心忘记[切换到沙盒测试账号](#step-4-switch-to-sandbox-test-account)就打开了应用,哪怕只打开过一次,TestFlight 也会将你的购买记录关联到正式 Apple 账号,进而引发意想不到的问题。 请按以下步骤解决: 1. 从测试设备上删除该应用。 2. 按照[沙盒测试](#sandbox-testing)的步骤进行操作。 :::note 不仅要重新安装应用,还需要切换到沙盒测试账户、清除购买记录,并使用沙盒测试账户启动应用。 ::: ### 共享访问等级问题 \{#shared-access-levels-issues\} 如果你用同一个沙盒测试账号反复测试,可能会遇到测试用户的[共享访问等级](sharing-paid-access-between-user-accounts)出现异常行为的情况。 要检查用户是否继承了访问等级,请在 Adapty 看板中前往 [Profiles & Segments](https://app.adapty.io/profiles/users),然后打开该用户的用户画像。 <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果用户拥有继承的访问等级,请按以下步骤操作以获得准确的测试结果: 1. 删除父用户画像。 2. 从测试设备上卸载应用。 3. [从 TestFlight 下载应用](#step-3-download-the-app-from-testflight)。 4. [切换到沙盒测试账号](#step-4-switch-to-sandbox-test-account)。 5. [清除购买记录](#step-5-clear-purchase-history)。 6. [打开应用并完成测试购买](#step-6-make-test-purchase)。 :::note 清除购买记录会重置商店端的购买历史。删除父级用户画像只会移除 Adapty 端的记录。若要了解为何重复使用的账户仍保留访问权限,以及哪些重置操作真正有效,请参阅[重置测试者的订阅](#resetting-a-testers-subscription)。 ::: ### 在 TestFlight 中更新应用 \{#updating-app-in-testflight\} 如果 TestFlight 上的应用已更新: 1. 从测试设备上删除该应用。 2. [从 TestFlight 下载应用](#step-3-download-the-app-from-testflight)。 3. [切换到沙盒测试账号](#step-4-switch-to-sandbox-test-account)。 4. [清除购买记录](#step-5-clear-purchase-history)。 5. [打开应用并进行测试购买](#step-6-make-test-purchase)。 ## 重置测试者的订阅 \{#resetting-a-testers-subscription\} 在沙盒环境中,购买记录归属于 **Apple 沙盒账号**,而非 Adapty 用户画像。对用户画像执行的操作(如删除或修改其访问等级)不会从 Store 账号中移除购买记录。下次重新安装或同步时,SDK 会重新关联相同的交易,测试者将再次获得访问权限。 下表列出了每种重置操作所影响的内容,以及测试者在此之后看到的结果。 | 操作 | Adapty 用户画像 | Apple 沙盒账号 | 测试者后续访问情况 | | :--- | :--- | :--- | :--- | | 在 Adapty 看板中删除用户画像 | 已删除 | 不受影响 | **恢复** — 重装后,新用户画像会重新关联相同的交易链 | | 通过 [Delete profile API](api-adapty/operations/deleteProfile) 删除用户画像 | 已删除 | 不受影响 | **恢复** — 与在看板中删除效果相同 | | 通过 **Add access level** 添加一个过去的到期日期 | 下次同步时被覆盖 | 不受影响 | **恢复** — 下次续订时,有效订阅会重新应用一个未来的到期日期 | | 调用 [Revoke access level API](api-adapty/operations/revokeAccessLevel) | 立即到期,触发 `access_level_updated`(`is_active=false`) | 不受影响 | **恢复** — 下次续订或重装时恢复,不能作为可靠的沙盒重置手段 | | 在沙盒账号中取消订阅 | 无直接变更 | 订阅已取消 | 续订停止,当前订阅期到期后失去访问权限,测试者可重新购买该产品 | | 使用全新的 Apple 沙盒账号登录 | 新用户画像 | 全新的空账号 | **干净** — 推荐用于重复测试 | ### 将测试人员重置为干净状态 \{#reset-a-tester-to-a-clean-state\} 如需反复测试购买流程,建议每次使用全新的 Apple 沙盒账号,而不是重置用户画像。按照[第 1 步](#step-1-create-sandbox-test-account-in-app-store-connect)创建账号,再按照[第 4 步](#step-4-switch-to-sandbox-test-account)在设备上切换到该账号。如果要复用已有的沙盒账号,请先[清除其购买记录](#step-5-clear-purchase-history)——删除 Adapty 用户画像并不会清除购买记录。 ### 撤销现有测试用户的访问权限 \{#remove-access-from-an-existing-tester\} 如需撤销测试用户的访问权限,请不要回溯修改过期时间,也不要调用 Revoke access level API。在沙盒环境中,订阅每隔几分钟就会自动续订,每次续订都会在同一交易链上恢复一个未来的过期时间,因此访问权限会自动恢复。Revoke access level API 确实会触发 `access_level_updated`(`is_active=false`)事件,但下一次续订会将其覆盖。 要真正停止访问权限,需要在商店侧取消订阅。在测试设备上,进入 **Settings > Developer > Sandbox Apple Account**,选择 **Manage**,然后取消订阅。续订将停止,访问权限将在当前订阅周期到期后终止。 ### 为什么删除用户画像后访问权限又回来了 \{#why-deleting-the-profile-brings-access-back\} 当测试人员重新安装应用时,Adapty 会接收沙盒账户的购买记录,并将新安装与已有购买关联起来。购买记录绑定的是商店账户,而不是你删除的用户画像。 - **匿名用户画像**:在未设置 `customer_user_id` 的情况下重新安装,无论你的[付费访问共享](sharing-paid-access-between-user-accounts)设置如何,都会继承该商店账户的访问等级。 - **已识别用户画像**:访问等级是否会转移到新的 `customer_user_id`,取决于你的付费访问共享设置。 关于 Adapty 如何将这些用户画像关联成链,请参阅[用户画像的工作原理](how-profiles-work#parent-and-inheritor-profiles)。 ## 测试订阅 \{#test-subscriptions\} 在使用沙盒测试账号测试应用时,你可以为每位测试人员单独设置沙盒中的订阅续订频率。详情请参阅 [Apple 官方文档](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings)中关于编辑订阅续订频率的说明。 默认情况下,订阅最多续订 12 次后停止,具体时间表如下: | 订阅时长 | 1 周 | 1 个月 | 2 个月 | 3 个月 | 6 个月 | 1 年 | | :----------------------------- | :--------- | :--------- | :--------- | :--------- | :--------- | :--------- | | 订阅续订速度 | 3 分钟 | 5 分钟 | 10 分钟 | 15 分钟 | 30 分钟 | 1 小时 | | 计费重试时长 | 10 分钟 | 10 分钟 | 10 分钟 | 10 分钟 | 10 分钟 | 10 分钟 | | 计费宽限期时长 | 3 分钟 | 5 分钟 | 5 分钟 | 5 分钟 | 5 分钟 | 5 分钟 | :::note 请注意,测试交易最多需要 10 分钟才能出现在[事件流](validate-test-purchases)中。 ::: 使用沙盒来验证你的应用和后端能否正确处理续订、账单重试和宽限期——而不是用来预测生产环境的续订时间。上述加速且有上限的时间表与生产环境并不一致。如需在服务器上重放交易以进行后端测试,请使用 [Set transaction API](api-adapty/operations/setTransaction)。 ## 测试优惠 \{#test-offers\} 测试优惠要求删除所有用户收据,以确保资格判断正常生效。 最可靠的测试方式是使用全新的[沙盒测试账号](#step-1-create-sandbox-test-account-in-app-store-connect)。使用同一个沙盒测试账号反复测试可能会导致意外行为。 :::danger 如果要使用同一个沙盒测试账号重复测试,请务必先[清除购买历史记录](#step-5-clear-purchase-history),以避免资格判断方面的问题。 ::: --- # File: local-sk-files --- --- title: "在 Xcode 中进行 StoreKit 测试" description: "在沙盒环境中测试购买流程,确保交易顺畅。" --- 在 Xcode 中进行 StoreKit 测试,无需设置沙盒账户即可在本地测试应用内购买。 进行此类测试,您需要: 1. [在 Adapty 中创建产品](quickstart-products)并为其分配 **App Store product ID**。 2. 在 Xcode 中,创建一个本地 [StoreKit 配置文件](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode)并向其中添加产品。产品 ID 必须与 Adapty 中的 **App Store product ID** 相同。 3. 将 StoreKit 配置文件添加到您的构建方案并构建应用。在模拟器或设备上启动它。 ## 我应该使用 Xcode 中的 StoreKit 测试吗?\{#should-i-use-storekit-testing-in-xcode\} 如果您是应用开发者,希望随时测试构建版本或使用 Xcode 功能测试不同的购买场景,这种测试方式最为方便。 但请注意,这种测试是本地进行的,因此不会在 Adapty 看板上显示任何变更。在将应用发布到生产环境之前,我们建议您在[沙盒环境](test-purchases-in-sandbox)中测试[用户画像相关功能](ios-quickstart-identify)。 **应该**使用 StoreKit 测试的场景: - 测试购买逻辑 - 使用 Xcode 工具重现不同的购买场景(例如取消付款或退款) - 使用模拟器进行测试 **不应该**使用 StoreKit 测试的场景: - 测试用户画像相关逻辑 - 查看应用中的操作是否显示在 Adapty 看板中 - 与非开发团队共享应用进行测试 ## 第一步:创建 StoreKit 配置文件\{#step-1-create-a-storekit-configuration-file\} 在 Xcode 中创建 StoreKit 配置文件: 1. 点击 **File > New > File from template**,然后选择 **StoreKit Configuration File** 并点击 **Next**。 2. 为文件命名。然后,根据您是否已在 App Store Connect 中创建了产品进行选择: - 勾选 **Sync this file with an app in App Store Connect**:创建一个包含所有 App Store Connect 产品的配置文件,以便在本地测试。 - 不勾选 **Sync this file with an app in App Store Connect**:创建一个空配置文件,需手动添加产品。 点击 **Next**。 3. 不要将应用添加为目标,直接继续。如果您使用的是从 App Store Connect 同步的产品,请跳至[第二步](#step-2-add-the-configuration-file-to-the-build-scheme)。 4. 如果您的产品未从 App Store Connect 同步,点击左下角的 **+** 并选择产品类型。 5. 输入订阅组名称并点击 **Next**。 6. 输入参考名称。在 **Product ID** 字段中,输入您在 Adapty 中产品的 **App Store product ID**。 7. 在配置文件中配置定价、优惠及其他产品设置,或继续添加更多产品。 ## 第二步:将配置文件添加到构建方案\{#step-2-add-the-configuration-file-to-the-build-scheme\} 要使用此配置文件构建应用,您需要将其添加到构建方案中。最佳实践是将测试方案与生产方案分开,因此我们建议为测试创建一个新方案: 1. 在顶部点击应用名称并选择 **New scheme**。 2. 输入方案名称并点击 **OK**。 3. 再次点击应用名称并选择 **Edit scheme**。在 **StoreKit configuration** 中,选择您的本地配置文件,这样构建时将使用该文件。 ## 第三步:构建并测试\{#step-3-build--test\} 现在,您可以构建应用并测试应用内购买,无需连接到 App Store 后端。您可以在本地购买产品并获取访问等级。这些更改不会反映在 Adapty 看板中,但您仍可以在本地测试解锁付费功能。 [了解更多](https://developer.apple.com/documentation/xcode/testing-in-app-purchases-with-storekit-transaction-manager-in-code)关于在 Xcode 中进行 StoreKit 测试的其他可用功能。 --- # File: testing-on-android --- --- title: "在 Google Play Store 中测试应用内购买" description: "使用 Adapty 在 Android 上测试订阅购买。" --- 在将应用发布给用户之前,测试 Android 应用中的应用内购买(IAP)是至关重要的一步。沙盒测试是一种安全高效的方式,让你无需向用户实际收费即可测试 IAP。本指南将带你了解如何在 Google Play Store 上对 Android 应用进行沙盒测试。 :::note **沙盒交易不会显示在任何分析数据图表中。** 它们仍会出现在各个用户画像页面和事件流中。 ::: ## 测试环境 \{#testing-environment\} 为确保 Android 应用的最佳性能,建议您在真实设备上进行测试,而非使用模拟器。虽然我们已成功在模拟器上进行了测试,但 Google 建议使用真实设备。 如果您决定使用模拟器,请确保其已安装 Google Play,这有助于确保应用正常运行。 ## 1. 为应用测试设置测试账号 \{#1-set-up-test-account-for-app-testing\} 为了在后续开发阶段方便测试,你需要为应用内购买测试设置一个测试用户。该用户将是你在 Android 测试设备上首次登录的账号。 请注意,Android 设备的主账号只能通过恢复出厂设置来更换,而这会清除所有数据。因此,务必提前正确配置好测试用户账号,以免日后不得不执行恢复出厂设置。 :::important 设置测试账号的方式取决于你使用的设备类型: - 如果你有专用测试设备,请创建一个**独立测试账号(新 Gmail 账号)**。 - 如果没有专用测试设备,可以使用自己的**个人账号**,并为其临时开启**License testing**。 - 如果完全没有 Android 设备,可以**创建独立测试账号并在模拟器上使用**。但不推荐这种方式,因为它无法覆盖所有真机可能出现的问题。 ::: ## 2. 启用许可证测试 \{#2-enable-license-testing\} 完成测试用户账号设置后,还需要为你的应用配置许可证测试。具体步骤如下: 1. 在 Google Play Console 侧边栏中,进入 **Settings**,然后在 **Monetization** 部分选择 **License testing**。 <img src="/assets/shared/img/android-license-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择一个已有的测试许可账号列表,或新建一个。 <img src="/assets/shared/img/android-testers.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 将用于测试的账号添加到列表中并保存更改。如果团队成员也需要测试应用,可以将他们的邮箱一并添加到列表,这样整个团队都能获得访问权限。 <img src="/assets/shared/img/android-list.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 3. 创建封闭测试轨道并添加测试账号 \{#3-create-closed-track-and-add-test-account-to-it\} 要开始测试,你需要将已签名的应用版本发布到封闭测试轨道: 1. 打开你的应用,在菜单中选择 **Test and release > Testing > Closed testing**,然后点击 **Create track**。 <img src="/assets/shared/img/android-closed-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 输入封闭测试轨道名称,然后点击 **Create track**。 3. 向该轨道添加测试人员列表。 4. 在 **How testers join your test** 部分,复制链接并将其发送到已登录测试账号的设备。在测试设备上打开该链接,即可将该用户设置为测试人员。 <img src="/assets/shared/img/android-link.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning 请注意以下几点,以确保测试顺利进行: - 打开 opt-in URL 会将你的 Play 账号标记为测试账号。如果跳过此步骤,产品将无法加载。 - 开发者通常会为测试版本使用不同的应用 ID。这会导致问题,因为 Google Play Services 依赖应用 ID 来查找应用内购买项目。 - 在某些情况下,如果测试设备未设置 PIN 码,测试用户可能只能购买消耗型商品,而无法购买订阅。这种情况可能会出现一条含糊的"Something went wrong"错误提示。请确保测试设备已设置 PIN 码,并且已登录 Google Play Store。 ::: ## 4. 上传已签名的 APK 到封闭测试轨道 \{#4-upload-a-signed-apk-to-the-closed-track\} 生成已签名的 APK,或使用 Android App Bundle,将已签名的 APK 上传到你刚创建的封闭测试轨道。你甚至不需要发布版本,只需上传 APK 即可。更多信息请参阅[此支持文章](https://support.google.com/googleplay/android-developer/answer/9859348?visit_id=638929100639477968-3849460621&rd=1)。 :::important 如果你的应用是新应用,可能需要先在你所在的国家或地区开放下载。请前往 **Testing > Closed testing**,点击你的测试轨道,然后进入 **Countries/regions** 添加所需的国家和地区。 ::: ## 5. 测试应用内购买 \{#5-test-in-app-purchases\} 上传 APK 后,请等待几分钟以使版本完成处理。然后,在测试设备上使用您添加到测试人员列表的电子邮件账号登录。之后,您可以像在正式应用中一样测试应用内购买。 <img src="/assets/shared/img/a8d2da9-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 延伸阅读 \{#read-more\} 请阅读以下资源,了解有关在 Android 应用中测试应用内购买的更多信息: - [沙盒中的续订周期](https://developer.android.com/google/play/billing/test#subs) - [测试一次性购买](https://developer.android.com/google/play/billing/test#one-time) --- # File: validate-test-purchases --- --- title: "验证测试购买" description: "在 Adapty 中验证测试购买,确保交易顺畅无误。" --- 在将移动应用发布到生产环境之前,全面测试应用内购买至关重要。请参阅我们的[在 Apple App Store 中测试应用内购买](test-purchases-in-sandbox)和[在 Google Play Store 中测试应用内购买](testing-on-android)主题,了解详细的测试指南。开始测试后,您需要验证测试购买是否成功。 每次在移动设备上完成测试购买后,请在 Adapty 看板的 [**Event Feed**](https://app.adapty.io/event-feed) 中查看对应的交易记录。如果购买未出现在 **Event Feed** 中,则说明 Adapty 未能追踪到该购买。 ## 测试购买成功 \{#test-purchase-is-successful\} 如果测试购买成功,其交易事件将显示在 **Event Feed** 中: <img src="/assets/shared/img/9ade2d5-event_feed_sandbox.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果交易按预期正常运行,请继续查看[发布检查清单](release-checklist),然后发布应用。 ## 测试购买未成功 \{#test-purchase-is-not-successful\} 如果 10 分钟内未观察到任何交易事件,或在移动应用中遇到错误,请参阅[故障排除](troubleshooting-test-purchases)以及各平台的错误处理文章:[iOS](ios-sdk-error-handling)、[Android](android-sdk-error-handling)、[React Native](react-native-handle-errors)、[Flutter](error-handling-on-flutter-react-native-unity)、[Unity](unity-handle-errors) 和 [Kotlin Multiplatform](kmp-handle-errors),以寻找可能的解决方案。 <img src="/assets/shared/img/31a79b2-no_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: troubleshooting-test-purchases --- --- title: "测试购买故障排查" description: "在 Adapty 中排查测试购买问题并解决常见的应用内交易问题。" --- 如果您遇到交易问题,请首先确保您已完成[发布检查清单](release-checklist)中列出的所有步骤。如果您已完成所有步骤但仍遇到问题,请按照以下指南进行解决: ## 移动应用中返回错误 \{#an-error-is-returned-in-the-mobile-app\} 请参阅适用于您平台的错误列表:[iOS](ios-sdk-error-handling)、[Android](android-sdk-error-handling)、[React Native](react-native-troubleshoot-purchases)、[Flutter](error-handling-on-flutter-react-native-unity) 和 [Unity](unity-troubleshoot-purchases),并按照我们的建议解决问题。 ## 事件流中没有交易记录,但移动应用中也未返回错误 \{#transaction-is-absent-from-the-event-feed-although-no-error-is-returned-in-the-mobile-app\} 要解决此问题,请检查以下几点: 1. **iOS 专属**:确保您使用的是真实设备而非模拟器。 2. 确保您的应用的 `Bundle ID`/`Package name` 与 [**App settings**](https://app.adapty.io/settings/general) 中的一致。 3. 确保您的应用中的 `PUBLIC_SDK_KEY` 与 Adapty 看板中的 **Public SDK key** 一致:[**App settings** -> **General** 标签页 -> **API keys** 子章节](https://app.adapty.io/settings/general)。 4. 确保您使用的是沙盒账户,而非[本地 StoreKit 配置文件](local-sk-files)。如果您之前使用过本地 StoreKit 配置文件进行测试,请确保当前构建版本中未使用该文件。 ## 我的测试用户画像中没有事件 \{#no-event-is-present-in-my-testing-profile\} 这是正常行为。当以下情况发生时,Adapty 会自动创建新的用户画像记录: - 用户首次运行您的应用时 - 用户退出您的应用时 **原因说明:** 所有交易和事件都与生成第一笔交易的用户画像绑定。这样可以将完整的交易历史记录(试用、购买、续订)关联到同一个用户画像。 **您将看到的情况:** 新的用户画像记录(称为"非原始用户画像")可能会出现但没有事件,但会保留访问等级。您可能会看到 `access_level_updated` 事件。这是预期行为。 **测试建议:** 为避免出现多个用户画像,每次重新安装应用时请创建新的测试账户(沙盒 Apple ID)。 更多详情请参阅[用户画像创建](how-profiles-work#profile-creation)。 以下是一个非原始用户画像的示例。请注意 **User history** 中没有事件,但存在访问等级。 <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 价格与 App Store Connect 中设置的实际价格不符 \{#prices-do-not-reflect-the-actual-prices-set-in-app-store-connect\} 在沙盒环境和使用沙盒环境进行应用内购买的 TestFlight 中,重要的是验证购买流程是否正常运行,而不是关注价格的准确性。值得注意的是,Apple 的 API 偶尔会提供不准确的数据,尤其是当设备或账户配置了不同地区时。由于价格直接来自商店,Adapty 后端不会以任何方式影响购买价格,因此在通过 Adapty 测试购买期间,您可以忽略价格上的任何不准确之处。 因此,请优先测试购买流程本身,而非价格的准确性,以确保其按预期运行。 ## 事件流中的交易时间不正确 \{#the-transaction-time-in-the-event-feed-is-incorrect\} **Event Feed** 使用的是 **App Settings** 中设置的时区。要使事件时区与您的本地时间一致,请在 [**App settings** -> **General** 标签页](https://app.adapty.io/settings/general) 中调整 **Reporting timezone**。 ## 付费墙和产品加载时间过长 \{#paywalls-and-products-take-a-long-time-to-load\} 如果您的测试账户有较长的交易历史记录,可能会出现此问题。我们强烈建议每次都创建新的测试账户,具体步骤请参阅[在 App Store Connect 中创建沙盒测试账户(沙盒 Apple ID)](test-purchases-in-sandbox#step-1-create-sandbox-test-account-in-app-store-connect)章节。 如果您无法创建新账户,可以按照以下步骤在 iOS 设备上清除当前账户的交易历史记录: 1. 打开**设置**,点击 **App Store**。 2. 点击您的 **Sandbox Apple ID**。 3. 在弹出窗口中,选择 **Manage**。 4. 在 **Account Settings** 页面,点击 **Clear Purchase History**。 更多详情,请查阅 [Apple 开发者文档](https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox)。 --- # File: test-devices --- --- title: "测试设备" description: "了解如何在 Adapty 中管理测试设备,以便高效地进行应用测试。" --- 出于测试目的,您可以将设备指定为测试设备,这将禁用缓存并确保您的更改立即生效。 :::note 测试设备支持从以下特定 SDK 版本开始: - iOS: 2.11.1 - Android: 2.11.3 - React Native: 2.11.1 Flutter 和 Unity 的支持将在稍后添加。 ::: ## 将您的设备标记为测试设备 \{#mark-your-device-as-test\} 1. 在 Adapty 看板中打开 [**App settings**](https://app.adapty.io/settings/general)。 2. 在 **General** 选项卡中向下滚动至 **Test devices** 部分。 <img src="/assets/shared/img/14c581d-test_device_add.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Add test device** 按钮。 <img src="/assets/shared/img/f86d5e2-test_users_add_device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **Add test device** 窗口中,输入: | 字段 | 说明 | |:-----------------------------------------| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Test device name** | 测试设备的名称,供您参考。 | | **ID used to identify this test device** | 选择用于标识测试设备的标识符类型。请参阅下方[应使用哪种标识符](test-devices#which-identifier-you-should-use)部分中的建议,选择最佳选项。 | | **ID value** | 输入标识符的值。 | 5. 请记得点击 **Add test device** 按钮以保存更改。 ## 应使用哪种标识符 \{#which-identifier-you-should-use\} 要标识设备,您可以使用多种标识符。我们推荐以下方式: - **Customer User ID**:适用于 iOS 和 Android 设备,前提是您已在 Adapty 中<InlineTooltip tooltip="identify your users in Adapty">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>识别用户。这是最佳选择,尤其是当您的应用中同一账户拥有多个测试设备时。如果将 Customer User ID 用作 **ID used to identify this test device**,则与该账户关联的所有设备都将被标记为测试设备。 - **IDFA(iOS)** 和 **Advertising ID(Android)**:这些广告标识符分别是 iOS 和 Android 设备的理想选择,前提是您已向用户请求访问权限的同意。即使您已有 Customer User ID,如果在测试过程中需要切换账户,使用广告标识符可能更为方便。此外,如果同一账户同时拥有测试设备和个人设备,且您不希望将个人设备标记为测试设备,这些标识符也非常有用。 还有其他选项,例如 Adapty Profile ID、IDFV 和 Android ID,这些选项使用起来不够方便,但在无法使用 Customer User ID、IDFA 或 Advertising ID 的情况下可以使用。 下面详细介绍所有可用选项。 ### 适用于所有平台的标识符 \{#identifiers-for-all-platforms\} | 标识符 | 用途 | |----------|-----| | Customer User ID | <p>由您设置的唯一标识符,用于在您的系统中识别用户。可以是用户的电子邮件、您的内部 ID 或任何其他字符串。要使用此选项,您必须在 Adapty 中<InlineTooltip tooltip="Identify your users in Adapty">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>识别用户。</p><p></p><p>这是标识测试设备的最佳选择,尤其是当同一账户使用多个设备时。该账户下的所有设备都将被视为测试设备。</p> | | Adapty profile ID | <p>Adapty 中[用户画像](profiles-crm)的唯一标识符。</p><p></p><p>如果无法使用 Customer User ID、iOS 的 IDFA 或 Android 的 Advertising ID,则可使用此选项。请注意,Adapty Profile ID 在重新安装应用或重新登录后可能会发生变化。</p> | #### 如何获取 Customer User ID 和 Adapty profile ID \{#how-to-obtain-customer-user-id-and-adapty-profile-id\} 两种标识符均可在 Adapty 看板的**用户画像**详情中获取: 1. 在 [**Adapty Profiles** -> **Event feed** 选项卡](https://app.adapty.io/event-feed)中找到用户的用户画像。 :::note 要找到确切的用户画像,请进行一次不常见类型的交易。这样,一旦该交易出现在 [**Event Feed**](https://app.adapty.io/event-feed) 中,您就能轻松识别它。 ::: 2. 在用户画像详情中复制 **Customer user ID** 和 **Adapty ID** 字段的值: <img src="/assets/shared/img/345d308-test_users_CUID_adapty_ID.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Apple 标识符 \{#apple-identifiers\} | 标识符 | 用途 | |----------|-----| | IDFA | <p>广告标识符(IDFA)是 Apple 分配给用户设备的唯一设备标识符。</p><p></p><p>它非常适合 iOS 设备,因为它不会自行改变,但您可以手动重置。</p><p>**注意**:自 iOS 14.5 推出以来,广告商必须请求用户同意才能访问 IDFA。请确保您的应用已请求同意,并且您已在测试设备上授予同意。</p> | | IDFV | 供应商标识符(IDFV)是 Apple 为同一发布商/供应商在单一设备上的所有应用分配的唯一字母数字标识符。如果您重新安装或更新应用,它可能会发生变化。 | #### 如何获取 IDFA \{#how-to-obtain-the-idfa\} Apple 默认不提供 IDFA。请从 Adapty 看板的用户画像归因中获取: 1. 在 [**Adapty Profiles** -> **Event feed** 选项卡](https://app.adapty.io/event-feed)中找到用户的用户画像。 :::note 要找到确切的用户画像,请进行一次不常见类型的交易。这样,一旦该交易出现在 [**Event Feed**](https://app.adapty.io/event-feed) 中,您就能轻松识别它。 ::: 2. 打开用户画像详情,在 **Attributes** 部分复制 **IDFA** 字段的值: <img src="/assets/shared/img/ce4a63f-test_users_idfa.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 您也可以[在 App Store 上找到能够显示您的 IDFA 的应用](https://www.apple.com/us/search/idfa?src=globalnav)。 #### 如何获取供应商标识符(IDFV) \{#how-to-obtain-the-identifier-for-vendors-idfv\} 要获取 IDFV,请让您的开发人员在您的应用中使用以下方法请求并将收到的标识符显示在日志或调试面板中。 ```swift showLineNumbers title="Swift" UIDevice.current.identifierForVendor ``` ### Google 标识符 \{#google-identifiers\} | 标识符 | 用途 | |----------|-----| | Advertising ID | <p>广告 ID 是 Google 分配给用户设备的唯一设备标识符。</p><p>它非常适合 Android 设备,因为它不会自行改变,但您可以手动重置。</p><p>**注意**:要使用它,如果您使用的是 Android 12 或更高版本,请在 **Ads** 设置中关闭 **Opt out of Ads Personalization**。</p>| | Android ID | Android ID 是每个应用签名密钥、用户和设备组合的唯一标识符。在 Android 8.0 及更高版本上可用。 | #### 如何获取 Advertising ID \{#how-to-obtain-advertising-id\} 要查找您设备的广告 ID: 1. 在 Android 设备上打开 **Settings** 应用。 2. 点击 **Google**。 3. 在 **Services** 下选择 **Ads**。您的广告 ID 将显示在屏幕底部。 #### 如何获取 Android ID \{#how-to-obtain-android-id\} 要获取 Android ID,请让您的开发人员在您的应用中使用以下方法请求 [ANDROID_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID),并将收到的标识符显示在日志或调试面板中。 ```kotlin showLineNumbers title="Kotlin/Java" android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID); ``` --- # File: release-checklist --- --- title: "发布检查清单" description: "遵循 Adapty 的发布检查清单,确保应用更新过程顺畅无误。" --- 我们非常高兴您决定使用 Adapty!希望集成过程一切顺利。本指南将引导您完成确保应用准备好在商店发布所需的各个步骤,让您确信变现流程运行正常。 ## 起飞前必备事项 \{#pre-flight-essentials\} 开始验证前您需要准备: - 一台配置了沙盒账号的真实设备 - 访问 Adapty 看板的权限 - 访问 App Store Connect / Google Play Console 的权限 :::note 虽然沙盒购买可以在模拟器上运行,但要完整测试所有流程(包括支付对话框和生物识别提示),仍需要真实设备。 ::: <Button id="test-purchases-in-sandbox"> App Store 测试指南 </Button> <Button id="testing-on-android"> Google Play 测试指南 </Button> ## 通用验证 \{#universal-validations\} - [ ] **商店连接**:确保已将 Adapty 连接至 App Store 和/或 Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **订阅事件推送**:确认服务器通知已配置: - [ ] [App Store 服务器通知](enable-app-store-server-notifications) - [ ] [实时开发者通知(RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **用户画像识别**:验证用户识别逻辑,确保购买记录关联到正确的用户画像: - [ ] [检查应用代码中的识别逻辑是否符合你的使用场景](ios-quickstart-identify) - [ ] [了解用于在用户画像之间共享付费访问权限的父级/继承逻辑](sharing-paid-access-between-user-accounts) - [ ] **优惠活动**:如果应用中包含 App Store 促销活动,请确保已将内购密钥[添加到主字段和 **App Store promotional offers** 部分](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers)。 - [ ] **数据收集**:确保符合隐私合规要求: - [ ] 如需遵守 GDPR、CCPA 等隐私法规,或应用面向儿童用户,请控制是否[启用 IDFA 和 IP 的收集与共享](sdk-installation-ios#data-policies)。 - [ ] 如果应用使用了 AppTrackingTransparency,请确保已[将授权状态发送给 Adapty](ios-deal-with-att)。 - [ ] **隐私标签**:[了解更多](apple-app-privacy) Adapty 收集的数据,以及审核时需要设置哪些标志。 ## 购买验证 \{#purchase-validations\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 在正式上线之前,请确保应用内购买功能正常运行,且付费墙已准备好通过应用商店审核。 验证应用内购买的方式取决于你的具体实现方案: - 你展示的是通过 Adapty 付费墙编辑工具创建的付费墙 - 你实现了自定义付费墙,并在其中使用 `makePurchase` 方法处理购买 - 你以观察者模式使用 Adapty(无论是配合付费墙编辑工具还是自定义付费墙) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **目标**:Adapty 渲染付费墙,用户可以购买产品、解锁访问权限,并且恢复购买流程正常运行。 - [ ] 你的应用从即将上线的同一[版位展示付费墙](ios-present-paywalls)。 - [ ] 付费墙能正常显示在屏幕上。如果加载时间过长(例如你或用户网络不稳定),请考虑[调整获取策略](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder)。 - [ ] 付费墙显示的是预期的实验变体(如适用,包含目标受众/语言区域)。如有需要,可[调整目标受众优先级](change-audience-priority)。 - [ ] 付费墙上能正常显示产品和价格。注意,Apple 的 API 在测试期间(尤其是配置了不同地区时)偶尔会返回不准确的价格,因此请优先测试购买流程的功能,而非价格准确性——Adapty 不会影响商店价格。 - [ ] 沙盒购买成功完成,并收到购买成功的回调。 - [ ] 访问权限已解锁且持续有效。确认[基于当前 Adapty 用户画像授予付费访问权限](ios-check-subscription-status#connect-profile-with-paywall-logic)。 - [ ] 购买完成后,Adapty 用户画像拥有有效的访问等级。 - [ ] 当用户画像包含对应访问等级时,付费功能解锁(而不仅仅依赖购买回调)。 - [ ] 恢复购买功能正常。重新安装应用或在新设备上安装时,自动恢复购买按照[在用户账号之间共享付费访问权限](sharing-paid-access-between-user-accounts)的设置运行。如果没有后端身份验证,购买将无论该设置如何都自动恢复。其他情况下,请确保用户在重新安装应用后能够恢复购买。 - [ ] 应用商店审核要求: - [ ] 付费墙上有**恢复购买**按钮。你可以在付费墙编辑工具中添加该按钮,点击后将自动处理购买恢复。 - [ ] 付费墙页面上可以访问使用条款和隐私政策,点击相关链接可在浏览器中打开。 </TabItem> <TabItem value="makepurchase" label="Custom paywall (makePurchase)" default> **目标**:您负责渲染 UI;Adapty 负责处理购买、用户画像更新和恢复购买。 - [ ] 产品 ID 未硬编码在应用代码中。你只需硬编码[版位](placements) ID。 - [ ] 你的应用从实际发布时使用的同一版位[获取产品](fetch-paywalls-and-products)。 - [ ] 产品列表加载成功。如果加载时间过长(例如你或用户网络不稳定),请考虑[调整获取策略](fetch-paywalls-and-products#fetch-paywall-information)。 - [ ] 获取到的产品与预期的实验变体(目标受众/语言区域,如适用)匹配。如有需要,可[调整目标受众优先级](change-audience-priority)。 - [ ] 产品和价格正确显示在付费墙上。请注意,Apple 的 API 在测试期间(尤其是使用不同地区配置时)偶尔会返回不准确的价格,因此请优先验证购买流程是否正常,而非价格是否准确——Adapty 不会影响商店价格。 - [ ] 使用 [makePurchase](making-purchases) 完成沙盒购买: - [ ] 购买成功的结果已正确处理。 - [ ] 待处理/失败/取消等情况已妥善处理。 - [ ] 如果你[使用了远程配置](present-remote-config-paywalls),其值已正确应用到付费墙。 - [ ] 付费墙展示时,调用了 [`logShowFlow`(iOS SDK v4+)/ `logShowPaywall` 方法](present-remote-config-paywalls#track-paywall-view-events)。 - [ ] 沙盒购买成功完成,并收到购买成功的回调。 - [ ] 访问权限已解锁且持续有效。确认[根据当前 Adapty 用户画像授予了付费访问权限](ios-check-subscription-status#connect-profile-with-paywall-logic)。 - [ ] 购买后,Adapty 用户画像中存在有效的访问等级。 - [ ] 当用户画像中包含对应访问等级时,付费功能才解锁(而不仅仅依赖购买回调)。 - [ ] 恢复购买功能正常。重新安装应用或在新设备上安装时,自动恢复购买功能按照[共享付费访问权限](sharing-paid-access-between-user-accounts)的设置运行。如果没有任何后端身份验证,无论该设置如何,购买都会自动恢复。其他情况下,请确保用户在重装应用后能够恢复其购买记录。 - [ ] 商店审核要求: - [ ] **Restore purchases** 按钮可访问,且[恢复购买功能](restore-purchase)正常工作。 - [ ] 付费墙页面中可访问"使用条款"和"隐私政策",点击链接后能在浏览器中打开。 </TabItem> <TabItem value="observer" label="观察者模式"> **目标**:您自行处理购买、用户画像更新和恢复操作;Adapty 负责接收交易报告。 - [ ] **您的应用使用自有购买流程完成购买**(StoreKit / BillingClient / 后端): - [ ] 沙盒购买在商店 UI 中成功完成。 - [ ] 应用能妥善处理待处理/失败/取消等异常情况。 - [ ] **交易已上报至 Adapty**。 - [ ] 已在应用代码中[启用观察者模式](implement-observer-mode)。 - [ ] 购买记录出现在 Adapty Event Feed 中。 - [ ] 续订、取消和退款情况能随时间推移得到正确反映(如适用)。 - [ ] **付费墙展示已被追踪**。在付费墙展示时调用 [`logShowFlow`(iOS SDK v4+)/ `logShowPaywall` 方法](present-remote-config-paywalls#track-paywall-view-events)。 - [ ] **恢复购买功能在您的实现中正常工作**。重新安装应用或切换设备后,访问权限能正确恢复。 - [ ] **商店审核要求**: - [ ] **恢复购买**操作可访问,并能触发您的恢复流程。 - [ ] 使用条款和隐私政策可从付费墙或购买界面访问,并在浏览器中打开。 </TabItem> </Tabs> 如有任何关于集成 Adapty SDK 的问题,请使用右下角的 AI 聊天机器人,或发送邮件至 [support@adapty.io](mailto:support@adapty.io) 联系我们。 --- # File: submit-app-to-app-store --- --- title: "将您的 iOS 应用提交至 App Store" description: "将您的构建版本上传到 App Store Connect,并将您的 iOS 订阅应用提交给苹果审核。" --- 一旦您的 Adapty 集成经过测试并正常运行,您就可以将构建版本上传到 App Store Connect,并将应用提交给苹果进行审核。 :::tip 在提交之前,请确保您已完成[发布检查清单](release-checklist),以验证您的 Adapty 集成、购买流程以及商店审核要求。 ::: ## 将构建版本上传到 App Store Connect \{#upload-your-build-to-app-store-connect\} ### 步骤 1:在 Xcode 中归档应用并上传到 App Store Connect \{#step-1-archive-your-app-in-xcode-and-upload-it-to-app-store-connect\} 1. 在 Xcode 中,将构建目标设置为 **Any iOS Device (arm64)**。 <img src="/assets/shared/img/build-target.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 2. 从顶部菜单栏选择 **Product** > **Archive**。 <img src="/assets/shared/img/xcode-archive.webp" style={{ border: '1px solid #727272', width: '500px', display: 'block', margin: '0 auto' }} /> 3. 等待归档过程完成。**Organizer** 窗口会自动打开。选择您的归档文件,然后点击 **Distribute App**。 <img src="/assets/shared/img/distribute-app.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 4. 选择 **App Store Connect** 作为分发方式。按照提示完成上传。 :::note 如果缺少必要资源(例如应用图标或启动界面),上传可能会失败。请查看 Xcode 错误日志了解详细信息。 ::: <img src="/assets/shared/img/distribution-method.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ### 步骤 2:在 App Store Connect 中检查构建版本 \{#step-2-check-the-build-in-app-store-connect\} 1. 前往 [App Store Connect](https://appstoreconnect.apple.com) 并打开您的应用。 2. 滚动到 **Build** 部分。确认您刚刚上传的构建版本已显示在此处。 :::note 上传后,构建版本可能需要几分钟才能出现在 App Store Connect 中。 ::: <img src="/assets/shared/img/app-store-build.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## 提交应用和产品以供审核 \{#submit-your-app-and-products-for-review\} 构建版本出现在 **Build** 部分后,请附加您的应用内订阅并将应用提交给苹果审核。 ### 步骤 1:将产品附加到提交内容 \{#step-1-attach-products-to-the-submission\} 在附加订阅之前,每个订阅在 App Store Connect 中必须具有 **Ready to Submit** 状态。如果订阅仍处于草稿状态或缺少元数据,它将不会出现在列表中。 1. 在同一页面上,滚动到 **In-App Purchases and Subscriptions** 部分。 2. 点击 **Select in-app purchases or subscriptions**。 <img src="/assets/shared/img/app-store-select-products.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 3. 选择您想要包含在此次提交中的所有产品,然后点击 **Done**。 ### 步骤 2:提交审核 \{#step-2-submit-for-review\} 1. 填写页面上所有必填字段(描述、截图、关键词等)。 2. 在 **App Store Version Release** 部分,选择在应用获批后是自动发布、手动发布还是按计划发布。 3. 点击 **Add for Review**,然后点击 **Submit to App Review**。 苹果通常在 1–2 天内完成审核,但审核时间可能有所不同。 ## 在生产环境中验证您的应用 \{#verify-your-app-in-production\} 苹果批准您的应用后: 1. 进行一笔真实购买(或等待您的第一位用户购买)。 2. 在 Adapty 看板中打开 [**Event Feed**](https://app.adapty.io/event-feed),确认生产环境中的交易事件已出现。 3. 检查订阅事件(续订、取消)是否正常流转——这取决于是否配置了 [App Store 服务器通知](enable-app-store-server-notifications)。 如果生产环境事件未出现,请验证您的 [App Store 连接配置](app-store-connection-configuration)。 ## 后续步骤 \{#next-steps\} 您的应用已上线。开始增加您的订阅收入: - **[A/B 测试](ab-tests)**:尝试不同的付费墙,找出转化率最高的方案。 - **[数据分析](charts)**:跟踪 MRR、流失率和转化率等订阅数据图表。 - **集成**:将订阅事件发送到[数据分析](analytics-integration)和[归因](attribution-integration)平台。 --- # File: general --- --- title: "应用设置" description: "探索 Adapty 中的常规设置与配置,实现顺畅使用。" --- 您可以前往 App Settings 页面的 **General** 标签页,管理应用的行为、外观和收益分成。在这里,您可以自定义应用名称和图标、管理 Adapty SDK 和 API 密钥、设置小型企业计划状态,以及为应用的分析和数据图表选择时区。 ## 1. 应用详情 \{#1-app-details\} <img src="/assets/shared/img/8fa2929-CleanShot_2023-04-21_at_15.16.222x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 为您的应用选择一个独特的名称和图标,以便在 Adapty 界面中识别。请注意,此处设置的应用名称和图标不会影响该应用在 App Store 或 Google Play 中显示的名称和图标。此外,请务必选择一个准确反映应用用途和内容的**应用分类**,这有助于用户发现您的应用,并确保其出现在应用商店的相应分类中。 ## 2\. 小企业计划成员与降低服务费 \{#member-of-small-business-program-and-reduced-service-fee\} <img src="/assets/shared/img/825e2be-CleanShot_2023-04-19_at_13.43.292x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果您的组织已加入 Apple 的[小企业计划](app-store-small-business-program)或 Google 的[降低服务费计划](google-reduced-service-fee),您的应用将享受较低的应用商店佣金比例。 如果您的应用加入了佣金减免计划,请在"Reduced Store Fee"部分注明相关状态,以确保 Adapty 正确计算数据。 减免费率设置仅对未来的交易生效。请在生效**前**更新状态,Adapty 将自动调整佣金比例。 :::warning * 如果您延续了减免费率计划的参与资格,请**新增一个资格有效期**。 * 如果您失去了计划资格,请**修改当前有效期的到期日期**。 ::: 以下文章对此主题进行了深入探讨: * [App Store 小型企业计划](app-store-small-business-program) * [Google 降低服务费](google-reduced-service-fee) ## 3\. 报告时区 \{#reporting-timezone\} <img src="/assets/shared/img/47227f9-CleanShot_2023-04-19_at_13.45.302x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 选择与您所在地区或应用数据分析最相关地区对应的时区。我们建议使用与您的 App Store Connect 或 Google Play Console 账户相同的时区,以确保数据一致性。请注意,此时区设置不会影响 Adapty 系统中的第三方集成,这些集成使用 UTC 时区。 您可以在 **App Settings** 页面 **General** 标签页的 **Reported timezone** 部分访问时区设置。您也可以勾选相应的复选框,为 Adapty 账户中的所有应用设置统一的时区。 ## 4\. 分析中的安装定义 \{#4-installs-definition-for-analytics\} 选择在分析中将什么定义为新安装事件: | 基准 | 说明 | |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 新增 device_ids | <p>(推荐)用户在设备上从应用商店每安装一次应用,均计为一次新增安装,包括首次安装和重新安装。</p><p>安装次数按设备 ID 统计,与用户认证状态无关。创建用户画像(在 SDK 激活或退出登录时)、登录或升级应用均不会产生额外的安装事件。</p><p>例如,同一应用安装在 5 台不同设备上,分析数据中将显示 5 次安装。</p> | | 新增 customer_user_ids | <p>此选项适用于在 Adapty 中<InlineTooltip tooltip="识别用户的应用">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)、[Kotlin Multiplatform](kmp-quickstart-identify)、[Capacitor](capacitor-quickstart-identify)</InlineTooltip>。</p><p>对于已登录用户,只有与某个 customer user ID 关联的首次安装才计为一次安装,在其他设备上的安装不计为新增安装。</p><p>匿名用户(未登录的用户)不计入分析数据。</p><p>重新安装应用或再次登录不会产生额外的安装记录。</p><p>应用商店和归因平台(如 App Store Connect、Google Play Console 和 AppsFlyer)均采用基于设备的方式统计安装量。若您在 Adapty 中按 customer user ID 统计安装量,结果可能与这些外部服务存在差异。</p><p>⚠️ 如果您未在 Adapty 中识别用户,启用此选项后将不会统计任何安装量。</p> | | Adapty 中的新用户画像(旧版) | (旧版)每次应用安装、重新安装,以及退出登录时创建的匿名用户画像,均计为新增安装。 | 请注意,此选项仅影响 [**Analytics**](https://app.adapty.io/analytics) 页面,不影响 [**Overview**](https://app.adapty.io/overview) 页面——后者可以单独配置视图。 ## 5. App Store 价格上涨逻辑 \{#5-app-store-price-increase-logic\} 为了保持数据准确、避免 Adapty 数据分析与 App Store Connect 结果出现偏差,在 App Store Connect 中调整价格上涨相关配置时,请务必选择合适的选项。 你可以选择 Adapty 处理订阅价格上涨时所采用的逻辑: <img src="/assets/shared/img/b766c8b-CleanShot_2023-07-18_at_19.28.18_22x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **为现有用户保留订阅价格:** 选择此选项后,即使您在 App Store Connect 中修改了价格,现有订阅者仍将按原价计费。 - **在 App Store Connect 中修改订阅价格后,现有订阅者同步更新:** 选择此选项后,在 App Store Connect 中所做的任何价格调整都将同步应用于现有订阅者,即现有订阅者将按 App Store Connect 中的最新价格计费。 :::warning 请注意,所选选项不仅会影响 Adapty 中的数据分析,还会影响集成功能和整体交易处理行为。 ::: 请确保选择与您处理现有订阅者订阅价格方式相符的选项。这有助于确保 Adapty 数据分析与 App Store Connect 结果之间的数据准确性和同步性。 ## 6. 在用户账户之间共享付费访问权限 \{#6-sharing-paid-access-between-user-accounts\} :::link 主要文章:[在用户账户之间共享付费访问权限](sharing-paid-access-between-user-accounts) ::: **Sharing paid access between user accounts** 设置决定了当多个[用户画像](identifying-users)尝试访问同一购买时 Adapty 的处理方式。您可以为[沙盒环境](test-purchases-in-sandbox)单独指定访问共享设置。 **已启用(默认)** 已识别用户(即设置了 [Customer User ID](identifying-users#set-customer-user-id-on-configuration) 的用户)在设备登录相同 Apple/Google ID 的情况下,可以共享 Adapty 提供的同一[访问等级](access-level)。这在用户重新安装应用并使用不同邮箱登录时非常有用——他们仍然可以访问之前的购买内容。使用此选项时,多个已识别用户可以共享同一访问等级。 尽管访问等级是共享的,但所有过去和未来的交易都会作为事件记录在原始 Customer User ID 下,以保持数据一致性并完整保留交易历史——包括试用期、订阅购买、续订等,均关联到同一用户画像。 **将访问权限转移给新用户** 已识别用户可以继续访问 Adapty 提供的[访问等级](access-level),即使他们使用不同的 [Customer User ID](identifying-users#set-customer-user-id-on-configuration) 登录或重新安装应用,只要设备登录的是相同的 Apple/Google ID 即可。 与上一选项不同,Adapty 会在已识别用户之间转移购买记录。这确保购买内容始终可用,但同一时间只有一个用户能拥有访问权限。例如,如果 UserA 购买了订阅,而 UserB 在同一设备上登录并恢复了交易,则 UserB 将获得该订阅的访问权限,UserA 的访问权限将被撤销。 如果其中一个用户(无论新用户还是旧用户)未被识别,Adapty 中这些用户画像之间的访问等级仍会共享。 尽管访问等级会被转移,但所有过去和未来的交易都会作为事件记录在原始 Customer User ID 下,以保持数据一致性并完整保留交易历史——包括试用期、订阅购买、续订等,均关联到同一用户画像。 切换到**将访问权限转移给新用户**后,用户画像之间的访问等级不会立即转移。每个特定访问等级的转移流程仅在 Adapty 收到来自商店的事件时触发,例如订阅续订、恢复购买或验证交易时。 **已禁用** 第一个获得访问等级的已识别用户画像将永久保留该访问等级。如果你的业务逻辑要求购买记录必须绑定到单个 Customer User ID,这是最佳选项。 请注意,访问等级在匿名用户之间仍会共享。 你可以通过[删除所有者的用户画像](https://adapty.io/docs/zh/api-adapty/operations/deleteProfile)来"解绑"购买记录。删除后,访问等级将归属于第一个声明它的用户画像,无论是匿名用户还是已识别用户。 禁用共享仅影响新用户。已在用户之间共享的订阅在禁用此选项后仍会继续共享。 :::warning Apple 和 Google 要求在用户之间共享或转移应用内购买,因为这些购买是依赖 Apple/Google ID 进行关联的。如果不启用共享,用户在重新安装应用后可能无法恢复购买。 禁用共享可能导致用户登录后无法重新获得访问权限。 我们建议仅在用户**必须先登录**才能进行购买的情况下禁用共享。否则,已识别用户可能在购买订阅后登录另一个账号,从而永久失去访问权限。 ::: ### 应该选择哪个设置?\{#which-setting-should-i-choose\} | 我的应用…… | 推荐选项 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | 没有登录系统,仅使用 Adapty 的匿名用户画像 ID。 | 使用默认选项,因为对于所有三个选项,匿名用户画像 ID 之间的访问等级始终是共享的。 | | 有可选登录系统,允许用户在创建账号之前进行购买。 | 选择**将访问权限转移给新用户**,确保未登录账号就完成购买的用户之后仍能恢复交易。 | | 要求用户在购买前创建账号,但允许购买记录关联到多个 Customer User ID。 | 选择**将访问权限转移给新用户**,确保同一时间只有一个 Customer User ID 拥有访问权限,同时允许用户使用不同 Customer User ID 登录而不丢失已付费的访问权限。 | | 要求用户在购买前创建账号,并严格规定购买记录只能绑定到单个 Customer User ID。 | 选择**已禁用**,确保交易记录永远不会在账号之间转移。 | ## 7. SDK 和 API 密钥 \{#7-sdk-and-api-keys\} 使用 Public SDK key 将 Adapty SDK 集成到您的应用中,使用 Secret Key 访问 Adapty 的 Server API。您可以根据需要生成新密钥或撤销现有密钥。要为 Developer CLI 创建令牌,请前往 **Settings → Developer API**。请参阅[身份验证](developer-cli-authentication)。 ## 8. 测试设备 \{#8-test-devices\} 指定用于测试的设备,确保它们能够即时获取付费墙或版位更改的更新,绕过任何缓存延迟。更多信息,请参阅[测试设备](test-devices)。 ## 9. 跨版位实验变体固定时长 \{#cross-placement-variation-stickiness\} 定义测试结束后,用户继续看到测试中实验变体的时长。这会影响数据分析的准确性和用户体验——如果向用户展示与之前不同的优惠,可能会影响他们的购买决策。 最大固定时长(也是默认值)为 90 天。 :::warning 请注意以下几点: - 修改此设置会影响所有之前已被分配到某个实验变体的用户。这些用户在下次触达版位时将立即获得新的付费墙,这可能会干扰正在进行的 A/B 测试结果。 - 如果某个用户的粘性期已结束,他们可能会看到新的付费墙或 A/B 测试。但即便如此,他们也永远无法再参与任何其他跨版位测试。 ::: ## 10. 删除应用 \{#delete-the-app\} 如果某个应用不再需要,可以将其从 Adapty 中删除。 :::warning 请注意,此操作不可撤销,删除后将无法恢复该应用及其数据。 ::: --- # File: ios-settings --- --- title: "Apple App Store 凭据" description: "在 Adapty 中配置 iOS 设置,以实现无缝的订阅管理。" --- 要配置 App Store 凭据并确保 Adapty iOS SDK 的最佳功能,请导航至 Adapty 看板 App Settings 页面中的 [iOS SDK](https://app.adapty.io/settings/ios-sdk) 选项卡,然后配置以下参数: <img src="/assets/shared/img/3d4087e-CleanShot_2023-06-26_at_13.27.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | 字段 | 描述 | |----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Bundle ID** | 您的[应用 Bundle ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id)。 | | **In-app purchase API (StoreKit 2)** | 用于启用应用内购买交易历史记录请求的安全身份验证和验证的[密钥](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id)。 | | **App Store Server Notifications** | 用于启用 App Store 向 Adapty 发送[服务器到服务器通知](enable-app-store-server-notifications)的 URL,以便监控和响应用户订阅状态变更。 | | **App Store Promotional Offers** | 用于在 Adapty 中为特定产品创建[促销活动](generate-in-app-purchase-key)的订阅密钥。 | | **Apple app ID** | 您在 App Store 中的应用 ID。查找方式:在 App Store Connect 中打开您的应用页面,从左侧菜单进入 **App Information** 页面,复制 **Apple ID**。 | | **App Store Connect shared secret (LEGACY)** | <p>**适用于 Adapty SDK v2.9.0 之前版本的旧版密钥**</p><p></p><p>用于收据验证和防止应用内欺诈的[密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。</p> | --- # File: google-play-store-connection-configuration --- --- title: "配置 Google Play 商店集成" description: "在 Adapty 中配置 Google Play 商店连接,以顺畅处理应用内购买。" --- 本节介绍通过 Google Play 销售的移动应用与 Adapty 的集成流程。您需要将应用在 Play 商店中的配置数据填写到 Adapty 看板中。此步骤对于在 Adapty 中验证购买及接收来自 Play 商店的订阅更新至关重要。 您可以在初始用户引导期间完成此流程,也可以稍后在 Adapty 看板的 **App Settings** 中进行修改。 :::danger 配置更改仅应在您发布集成了 Adapty 付费墙的移动应用之前进行。发布后进行更改将导致集成中断,付费墙将无法在您的移动应用中显示。 ::: ## 步骤 1. 提供包名 \{#step-1-provide-package-name\} 包名是您的应用在 Google Play 商店中的唯一标识符。这是 Adapty 基本功能(如订阅处理)所必需的。 1. 打开 [Google Play 开发者控制台](https://play.google.com/console/u/0/developers)。 2. 选择您需要获取 ID 的应用,**Dashboard** 窗口将会打开。 <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在应用名称下方找到产品 ID 并复制。 4. 从 Adapty 顶部菜单打开 [**App settings**](https://app.adapty.io/settings/android-sdk)。 <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在 **App settings** 窗口的 **Android SDK** 标签页中,粘贴已复制的 **Package name**。 ## 步骤 2. 上传账号密钥文件 \{#step-2-upload-the-account-key-file\} 1. 将您在[创建服务账号密钥文件](create-service-account)步骤中创建的 JSON 格式服务账号私钥文件上传到 **Service account key file** 区域。 <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 请不要忘记点击 **Save** 按钮以确认更改。 **下一步** - [在 Google Play 控制台中启用实时开发者通知(RTDN)](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "在 Google Play Console 中启用实时开发者通知 (RTDN)" description: "通过在 Google Play Console 中为 Adapty 启用实时开发者通知 (RTDN),及时了解关键事件并保持数据准确性。了解如何设置 RTDN 以接收来自 Play Store 的退款及其他重要事件的即时更新" --- 设置实时开发者通知 (RTDN) 对于确保数据准确性至关重要,它能让您即时接收来自 Play Store 的更新,包括退款及其他事件的信息。 ## 启用通知 \{#enable-notifications\} 1. 确保已启用 **Google Cloud Pub/Sub**。打开[此链接](https://console.cloud.google.com/flows/enableapi?apiid=pubsub)并选择您的应用项目。如果尚未启用 **Google Cloud Pub/Sub**,请在此处启用。 <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 从 Adapty 顶部菜单进入 [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk),复制 **Google Play RTDN topic name** 标题旁 **Enable Pub/Sub API** 字段的内容。 <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note 如果 **Enable Pub/Sub API** 字段的内容格式不正确(正确格式以 `projects/...` 开头),请参阅[修复 Enable Pub/Sub API 字段格式错误](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field)部分获取帮助。 ::: 3. 打开 [Google Play Console](https://play.google.com/console/),选择您的应用,然后前往 **Monetize with Play** -> **Monetization setup**。在 **Google Play Billing** 部分,勾选 **Enable real-time notifications** 复选框。 4. 将您在 Adapty **App Settings** 中复制的 **Enable Pub/Sub API** 字段内容粘贴到 **Topic name** 字段中。 5. 在 Google Play Console 中点击 **Save changes**。 <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 测试通知 \{#test-notifications\} 要验证您是否已成功订阅实时开发者通知: 1. 在 Google Play Console 设置中保存更改。 2. 在 Google Play Console 的 **Topic name** 下方,点击 **Send test notification**。 <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 Adapty 中进入 [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk)。如果测试通知已发送,您将在主题名称上方看到其状态。 <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 修复 Enable Pub/Sub API 字段格式错误 \{#fixing-incorrect-format-in-enable-pubsub-api-field\} 如果 **Enable Pub/Sub API** 字段的内容格式不正确(正确格式以 `projects/...` 开头),请按以下步骤排查并解决问题: ### 1. 验证 API 启用状态与权限 \{#1-verify-api-enablement-and-permissions\} 请仔细确认所有必需的 API 已启用,且权限已正确授予服务账号。即使您已完成这些步骤,也请再次逐一核查,确保没有遗漏任何子步骤。请重复以下各节中的步骤: 1. [在 Google Play Console 中启用开发者 API](enabling-of-devepoler-api) 2. [在 Google Cloud Console 中创建服务账号](create-service-account) 3. [在 Google Play Console 中授予服务账号权限](grant-permissions-to-service-account) 4. [在 Google Play Console 中生成服务账号密钥文件](create-service-account-key-file) 5. [配置 Google Play Store 集成](google-play-store-connection-configuration) ### 2. 调整域策略 \{#2-adjust-domain-policies\} 更改 **Domain restricted contacts** 和 **Domain restricted sharing** 策略: 1. 打开 [Google Cloud Console](https://console.cloud.google.com/),选择您用于管理应用的服务账号所在的项目。 2. 在 **Quick Access** 部分,选择 **IAM & Admin**。 <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在左侧面板中,选择 **Organization Policies**。 4. 找到 **Domain restricted contacts** 策略。 <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Actions** 列中的省略号按钮,选择 **Edit policy**。 6. 在策略编辑窗口中: 1. 在 **Policy source** 下,选择 **Override parent's policy** 单选按钮。 2. 在 **Policy enforcement** 下,选择 **Replace** 单选按钮。 3. 在 **Rules** 下,点击 **ADD A RULE** 按钮。 <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **New rule** -> **Policy values** 下,选择 **Allow All**。 <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **SET POLICY**。 7. 对 **Domain restricted sharing** 策略重复步骤 4-6。 最后,重新生成 **Google Play RTDN topic name** 标题旁 **Enable Pub/Sub API** 字段的内容。该字段现在将显示正确的格式。 成功启用实时开发者通知 (RTDN) 后,请务必将已更新策略的 **Policy source** 切换回 **Inherit parent's policy**。 ## 原始事件转发 \{#raw-events-forwarding\} 有时,您可能仍希望接收来自 Google 的原始 S2S 事件。如需在使用 Adapty 的同时继续接收这些事件,只需将您的端点添加到 **URL for forwarding raw Google events** 字段,我们将原样转发来自 Google 的原始事件。 <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **下一步** 为以下平台配置 Adapty SDK: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: apple-search-ads --- --- title: "Apple Ads" description: "将 Apple Ads 与 Adapty 集成,优化订阅转化率。" --- :::important **App settings** 中的 Apple Ads 集成仅用于基础分析以及 SplitMetrics Acquire 和 Asapty 集成。 [Adapty Ads Manager](adapty-ads-manager) 使用单独的连接方式。请在 [Adapty Ads Manager](adapty-ads-manager-get-started) 中连接您的 Apple Ads 账户。 ::: Adapty 可以帮助您获取 Apple Ads 的归因数据,并通过广告系列和关键词细分来分析您的数据图表。Adapty 通过其 SDK 和 AdServices 框架自动收集 Apple Ads 的归因数据。 完成 Apple Ads 集成设置后,Adapty 将开始接收来自 Apple Ads 的归因数据。您可以在用户画像页面轻松访问和查看这些数据。 <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 设置集成 \{#set-up-integration\} ### 将 Adapty 连接到 AdServices 框架 \{#connect-adapty-to-the-adservices-framework\} 通过 [AdServices](https://developer.apple.com/documentation/adservices) 使用 Apple Ads 需要在 Adapty 看板中进行一些配置,同时也需要在应用端启用该功能。按照以下步骤,通过 Adapty 使用 AdServices 框架完成 Apple Ads 的设置: #### 步骤 1:获取公钥 \{#step-1-obtain-public-key\} 在 Adapty 看板中,前往 [Settings -> Apple Ads。](https://app.adapty.io/settings/apple-search-ads) 找到预先生成的公钥(Adapty 会为您提供一对密钥)并复制。 <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note 如果您使用其他服务或自有方案进行 Apple Ads 归因,可以上传您自己的私钥。 ::: #### 第二步:在 Apple Ads 上配置用户管理 \{#step-2-configure-user-management-on-apple-ads\} 在您的 [Apple Ads 账户](https://ads.apple.com/app-store)中,前往 **Settings > User Management** 页面。为使 Adapty 能够获取归因数据,您需要邀请另一个 Apple ID 账户并授予其 API Account Manager 访问权限。您可以使用任何有权限的账户,或专门创建一个新账户。重要的是,您必须能够使用该 Apple ID 登录 Apple Ads。 <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 步骤 3:生成 API 凭据 \{#step-3-generate-api-credentials\} 接下来,在 Apple Ads 中登录新添加的账户,进入 Apple Ads 界面中的 Settings -> API,将之前复制的公钥粘贴到指定字段中,然后生成新的 API 凭据。 #### 步骤 4:在 Adapty 中配置 Apple Ads 凭据 \{#step-4-configure-adapty-with-apple-ads-credentials\} 从 Apple Ads 设置中复制 Client ID、Team ID 和 Key ID 字段。在 Adapty 看板中,将这些凭据粘贴到对应字段中。 <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 将您的应用连接到 AdServices 网络 \{#connect-your-app-to-the-adservices-network\} 完成 [AdServices 框架设置](#connect-the-adservices-framework)后,Adapty 会自动开始收集 Apple Search Ad 归因数据。您无需添加任何 SDK 代码。 对于 iOS 应用,此归因数据将**始终**优先于其他来源的数据。如果不需要此行为,请按照以下说明*禁用* ASA 归因。 ## 禁用集成 \{#disable-integration\} 要关闭 Apple Search Ads 归因,请打开 [**App Settings** -> **Apple Search Ads** 标签页](https://app.adapty.io/settings/apple-search-ads),然后关闭 **Receive Apple Search Ads attribution** 开关。 <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning 请注意,禁用此选项将完全停止接收 ASA 分析数据。因此,ASA 将不再用于数据分析,也不会发送至任何集成。此外,SplitMetrics Acquire 和 Asapty 也将停止运行,因为它们依赖 ASA 归因才能正常工作。 此更改之前已接收的归因数据不受影响。 ::: ## 上传您自己的密钥 \{#uploading-your-own-keys\} :::note 可选 这些步骤不是 Apple Ads 归因所必需的,仅用于与 Asapty 等其他服务或您自己的解决方案配合使用。 ::: 如果您使用其他服务或自己的 ASA 归因解决方案,可以使用您自己的公私密钥对。 ### 第 1 步 \{#step-1\} 在终端中生成私钥 ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` 在 Adapty Settings -> Apple Ads 中上传(点击 Upload private key 按钮) ### 第 2 步 \{#step-2\} 在终端中生成公钥 ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` 您可以在具有 API Account Manager 角色的账户的 Apple Ads 设置中使用此公钥。这样您就可以将生成的 Client ID、Team ID 和 Key ID 值用于 Adapty 和其他服务。 --- # File: account --- --- title: "账户详情与计费" description: "管理您的 Adapty 账户,优化设置以更好地追踪订阅数据。" --- **Account** 页面让您可以管理用户画像、团队成员和计费信息。 该页面包含三个标签页: - [通用](#general-settings) - [订阅与计费](#billing-info) - [成员](#members) 要访问账户设置,请点击右上角的 **Account**,或前往 [app.adapty.io/account](https://app.adapty.io/account)。 <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 常规设置 \{#general-settings\} **General** 标签页包含用户画像、账户设置、显示偏好以及报告配置。 - **Profile**:输入您的名字、姓氏和公司名称。公司名称最多可包含 256 个字符。 - **Account settings**:查看您的注册邮箱地址并修改密码。 - **Date & Time formats**:选择 Adapty 中日期和时间的显示方式: - **American format**:January 31, 2022,以及 12 小时制(AM/PM) - **European format**:31 January, 2022,以及 24 小时制(16:00) - **Email reports**:为一个或所有应用设置每日、每周或每月报告。可以同时接收所有应用的汇总报告,也可以为每个选定的应用单独获取详细报告。 <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 订阅与账单 \{#billing-info\} **Subscription & Billing** 选项卡让您管理支付信息和功能访问权限: - 添加或更新支付详情 - 查看账单信息 - 购买额外的付费功能 了解更多关于[功能与定价](https://adapty.io/pricing)的信息。 ## 成员 \{#members\} 您可以在账户设置中管理团队成员。要添加团队成员,请通过电子邮件邀请他们并为其分配角色。 阅读更多关于管理团队成员及其访问权限的内容,请点击[此处](members-settings)。 --- # File: members-settings --- --- title: "成员" description: "在 Adapty 看板中管理成员设置和权限。" --- :::note 本页面介绍 Adapty 看板成员相关内容 如果您想为应用的用户设置不同的访问等级,请查看[访问等级](access-level)。 ::: Adapty 看板成员系统允许您为每位成员授予不同级别的 Adapty 访问权限,并指定其可访问的应用。 ## 角色 \{#roles\} 以下角色可在 Adapty 看板中分配给成员: | 角色 | 访问账单 | 添加新成员 | 修改任何内容 | 访问所有模块 | |-------------|----------|-----------|--------------|--------------| | Owner | ✅ | ✅ | ✅ | ✅ | | Admin | ❌ | ✅ | ✅ | ✅ | | Developer | ❌ | ❌ | ✅ | ❌ | | Viewer | ❌ | ❌ | ❌ | ✅ | | Support | ❌ | ❌ | ❌ | ❌ | | ASA manager | ❌ | ❌ | ❌ | ❌ | - **Owner(所有者):** Owner 是 Adapty 账户的原始创建者,拥有最高级别的访问权限和控制权。Owner 可以完全访问 Adapty 账单,管理付款信息和订阅计划。此外,只有 Owner 和 Admin 才能为新成员指定应用访问权限。每个 Adapty 账户只能有一位 Owner。 - **Admin(管理员):** 拥有 Admin 角色的成员可以完全访问所选应用。他们可以执行各种管理任务,包括创建和修改付费墙、开展 A/B 测试、分析数据以及管理这些应用内的成员。 - **Developer(开发者):** 拥有 Developer 角色的成员可以完全访问所有实体,但数据分析和账户成员管理除外。他们无法访问任何账单设置。此角色适合负责配置付费墙、A/B 测试及其他实体并将 Adapty 集成到应用中、但不应查看财务数据的人员。 - **Viewer(查看者):** 拥有 Viewer 角色的成员对所选应用拥有只读访问权限。他们可以查看信息,但无法创建或修改付费墙、A/B 测试及其他功能,也无法邀请新用户、创建新应用或更改应用设置。 - **Support(客服):** 拥有 Support 角色的成员只能访问所选应用中的用户画像。但他们无法添加新成员或访问 Adapty 的其他任何板块。此角色特别适合需要协助用户处理订阅相关咨询或问题排查的客服团队或个人。 - **ASA manager(ASA 管理员):** 拥有 ASA manager 角色的成员只能访问 [Adapty Ads Manager](adapty-ads-manager) 看板。 ## 添加成员 \{#add-a-member\} 在 Adapty 中,你最多可以邀请 256 名团队成员,添加新成员免费。 :::note 你只能邀请尚未在 Adapty 注册的邮箱地址。如果你的同事已有独立账户,请使用其他邮箱地址邀请,或联系 Adapty 支持团队删除其现有账户。 ::: 添加团队成员的步骤: 1. 点击右上角的 **Account**,打开 **Members** 标签页。 2. 点击 **Invite member**。 <img src="/assets/shared/img/invite-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 输入成员的电子邮件地址。 4. 从列表中选择一个[角色](#roles)。 5. 选择要授权访问的应用。 6. (可选)启用 **Always allow access to new apps**,以便自动为未来新增的应用授予访问权限。 7. 点击 **Save**。 <img src="/assets/shared/img/add-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 转让账户所有权 \{#transfer-account-ownership\} 如需转让整个**账户所有权**,请通过 [support@adapty.io](mailto:support@adapty.io) 联系我们的支持团队。 如需转让**应用所有权**,请阅读[专项指南](transfer-apps)了解详情。 --- # File: set-up-app-store-connect --- --- title: "设置 App Store Connect" description: "面向首次开发者的指南,介绍如何注册 Apple 开发者计划并设置 App Store Connect 以支持应用内购买。" --- 如果您正在**构建第一个 iOS 应用**,必须先设置 Apple 开发者账户和 App Store Connect,然后再集成 Adapty。 :::note 如果您已拥有 Apple 开发者账户并已在 App Store Connect 中注册了应用,可以跳过本指南,直接前往[与 App Store 的初始集成](initial_ios)。 ::: ## 第一步:注册 Apple 开发者计划 \{#step-1-enroll-in-apple-developer-program\} 要在 App Store 上分发应用并销售应用内购买,您必须加入 [Apple 开发者计划](https://developer.apple.com/programs/)。 ### 选择注册类型 \{#choose-enrollment-type\} Apple 提供两种注册类型: | | 个人 | 组织 | |-----------------------------|--------------------|------------------------------| | **适用对象** | 独立开发者 | 公司、团队、非营利组织 | | **是否需要 D-U-N-S 编号** | 否 | 是 | | **应用发布名义** | 您的个人姓名 | 您的组织名称 | | **团队管理** | 不支持 | 支持 | :::tip 如果您以组织身份注册,需要一个 **D-U-N-S 编号** —— 由邓白氏公司颁发的唯一九位数企业标识符。您可以[查询您的组织是否已有编号](https://developer.apple.com/enroll/duns-lookup/),或申请新编号——申请链接位于查询页面底部。获取 D-U-N-S 编号最多需要 5 个工作日。 ::: ### 注册 \{#enroll\} 1. 前往 [Apple 开发者计划注册页面](https://developer.apple.com/programs/enroll/)。 2. 使用您的 Apple ID 登录。如果没有 Apple ID,请先创建一个。 3. 按照适合您注册类型(个人或组织)的步骤操作。 4. 支付年费。 Apple 处理完您的注册申请后,您将获得 [App Store Connect](https://appstoreconnect.apple.com) 的访问权限。注册通常需要最多 48 小时。对于组织注册,如果需要 D-U-N-S 验证,可能需要更长时间。 ## 第二步:在 App Store Connect 中设置您的应用 \{#step-2-set-up-your-app-in-app-store-connect\} 在销售应用内购买之前,需要在 App Store Connect 中完成初始设置,包括签署协议、添加付款信息以及注册应用。 ### 签署付费应用协议 \{#sign-the-paid-applications-agreement\} Apple 要求您在 App Store 上销售之前签署付费应用协议。无论是付费应用还是免费应用中的应用内购买,均需签署此协议。 1. 前往 [App Store Connect](https://appstoreconnect.apple.com/business) 中的 **Business** 页面。 2. 找到 **Paid Apps** 协议,点击 **Review and Agree**。 3. 填写所需信息: - **Banking information**:添加银行账户,Apple 将向该账户汇入您的收益。 - **Tax information**:填写您希望销售的国家/地区的税务表格。 - **Contact information**:提供您的联系方式。 :::important 您必须完成全部三个部分(银行信息、税务信息、联系信息),协议才能生效。协议未生效之前,您无法销售应用内购买。 ::: ### 创建 Bundle ID \{#create-a-bundle-id\} Bundle ID 在 Apple 生态系统中唯一标识您的应用。您需要它来在 App Store Connect 中注册应用,以及配置 Adapty 集成。 1. 打开 [Apple 开发者门户](https://developer.apple.com/account)。 2. 前往 **Certificates, Identifiers & Profiles** → **Identifiers**。 3. 点击 **+** 注册新标识符。 4. 选择 **App IDs**,点击 **Continue**。 5. 选择 **App** 作为类型,点击 **Continue**。 6. 填写以下字段: - **Description**:帮助您识别此 Bundle ID 的名称(例如 "My Subscription App")。 - **Bundle ID**:选择 **Explicit**,并以反向域名格式输入唯一标识符(例如 `com.yourcompany.yourapp`)。 7. 在 **Capabilities** 部分,向下滚动并勾选 **In-App Purchase**。 8. 点击 **Continue**,然后点击 **Register**。 ### 在 App Store Connect 中注册您的应用 \{#register-your-app-in-app-store-connect\} 1. 前往 [App Store Connect](https://appstoreconnect.apple.com/apps) 中的 **Apps** 页面。 2. 点击 **+** → **New App**。 3. 填写所需字段: - **Platforms**:选择 **iOS**。 - **Name**:您的应用名称,将在 App Store 上显示。 - **Primary language**:应用元数据的默认语言。 - **Bundle ID**:选择您在上一步中创建的 Bundle ID。 - **SKU**:您应用的唯一标识符(用户不可见)。例如 `my_subscription_app_2025`。 4. 点击 **Create**。 您的应用现已在 App Store Connect 中注册,可以进行 Adapty 集成了。 ## 后续步骤 \{#whats-next\} - [与 App Store 的初始集成](initial_ios):将您的 App Store 应用连接到 Adapty - [SDK 集成](quickstart-sdk):将 Adapty SDK 集成到您的应用代码中 - [沙盒测试](test-purchases-in-sandbox):在发布前测试您的应用内购买 - [将您的 iOS 应用提交至 App Store](submit-app-to-app-store):上传构建版本并提交 Apple 审核 - [App Store 小型企业计划](app-store-small-business-program):将您的 App Store 佣金从 30% 降至 15% --- # File: app-store-products --- --- title: "App Store 中的产品" description: "使用 Adapty 的订阅工具高效管理 App Store 产品。" --- 本页面提供了在 App Store Connect 中创建产品的指导。尽管这些信息可能与 Adapty 的功能没有直接关系,但如果您在 App Store Connect 账号中创建产品时遇到困难,这将是一份有价值的参考资料。 要创建一个将与 Adapty 关联的产品: 1. 打开 **App Store Connect**。在左侧菜单中前往 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) 部分。 <img src="/assets/shared/img/148c3b5-subscriptions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 如果您尚未创建订阅组,请点击 **Subscription Groups** 标题下的 **Create** 按钮开始创建流程。App Store Connect 中的[订阅组](https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions)用于对您的产品进行分类和管理,让用户可以在不同产品之间无缝切换。请注意,无法在订阅组之外创建订阅。 3. 在弹出的 **Create Subscription Group** 窗口中,在 **Reference Name** 字段中输入新的订阅组名称。参考名称是一个用户自定义的标签或标识符,帮助您区分和管理应用中不同的订阅组。 参考名称对用户不可见,主要供您内部使用和组织管理。它使您能够在 App Store Connect 界面中轻松识别和引用特定的订阅组。如果您有多个订阅产品,或希望按照对应用结构有意义的方式进行分类,这将特别有用。 <img src="/assets/shared/img/3f93c44-create_subscription_group.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Create** 按钮确认创建订阅组。 5. 订阅组已创建并打开。现在您可以在该组中创建订阅。点击 **Subscriptions** 标题下的 **Create** 按钮。如果您要向现有组添加新订阅,请点击 **Subscriptions** 标题旁的 **Plus** 按钮。 <img src="/assets/shared/img/22fc643-add_subscription.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 在弹出的 **Create Subscription** 窗口中,在 **Reference Name** 字段中输入名称,在 **Product ID** 字段中输入订阅的唯一代码。 Reference Name 是您的应用内订阅在 App Store Connect 中的专属标识符,对 App Store 上的用户不可见。我们建议使用清晰、易于理解的描述,以准确表达您要创建的具体订阅。请注意,该名称不得超过 64 个字符。 Product ID 是一个唯一的字母数字标识符,在开发阶段访问您的产品以及将其与 Adapty(一项用于管理应用内订阅的服务)同步时必不可少。Product ID 中只允许使用字母数字字符、句点和下划线。 <img src="/assets/shared/img/04aca55-create_subscription.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击 **Create** 按钮确认创建订阅。 8. 订阅已创建并打开。现在在 **Subscription Duration** 列表中选择订阅时长。即使订阅名称中已经包含了时长信息,也请记得填写 **Subscription Duration** 字段。 <img src="/assets/shared/img/f56cf0f-subscription_duration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. 现在是设置订阅价格的时候了。点击 Subscription Prices 标题下的 **Add Subscription Price** 按钮。您可能需要向下滚动才能找到该按钮。 10. 在弹出的 **Subscription Price** 窗口中,在 **Country or Region** 列表中选择基准国家,在 **Price** 列表中选择基准货币。之后,Apple 将根据该基准价格和最新汇率自动计算所有 175 个国家或地区的价格。 <img src="/assets/shared/img/de1cec8-subscription_price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. 点击 **Next** 按钮。在弹出的 **Price by Country or Region** 窗口中,您可以看到所有国家自动重新计算后的价格。如有需要,您可以对其进行修改。 <img src="/assets/shared/img/2a047a6-price_by_country.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 12. 更新各地区价格后,点击窗口底部的 **Next** 按钮继续。 13. 在弹出的 **Confirm Subscription Price?** 窗口中,仔细核对最终价格。如需修正价格,可点击 **Back** 按钮返回 **Price by Country or Region** 窗口进行更新。确认价格无误后,点击 **Confirm** 按钮。 <img src="/assets/shared/img/d2b2031-confirm_prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 14. 关闭 **Confirm Subscription Price?** 窗口后,请记得点击订阅窗口中的 **Save** 按钮。否则,订阅将不会被创建,所有已输入的数据都将丢失。 请注意,目前提供的步骤侧重于配置自动续期订阅。但是,如果您打算设置其他类型的应用内购买,可以点击侧边栏中的 **In-App Purchases** 标签,而不是"Subscriptions"。这将引导您进入可以管理和创建各种类型应用内购买的部分。 <img src="/assets/shared/img/5663d85-in-app_purchases.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 将产品添加到 Adapty \{#add-products-to-adapty\} 在 App Store Connect 中完成应用内购买、订阅和优惠的添加后,下一步是[将这些产品添加到 Adapty](create-product)。 --- # File: apple-app-privacy --- --- title: "Apple App Privacy" description: "了解 Apple 应用隐私政策及其对您的订阅应用的影响。" --- Apple 要求所有新应用及应用更新在 App Store Connect 的 **App Privacy** 部分以及应用清单文件中进行隐私披露。Adapty 是您应用的第三方依赖项,因此您需要披露如何在用户数据方面使用 Adapty。 ## Apple 应用隐私清单 \{#apple-app-privacy-manifest\} [隐私清单文件](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests)(命名为 `PrivacyInfo.xcprivacy`)描述了您的应用使用了哪些私有数据以及使用原因。作为每位应用所有者,您必须为自己的应用创建清单文件。此外,如果您集成了额外的 SDK,请确保那些出现在[需要隐私清单和签名的 SDK](https://developer.apple.com/support/third-party-SDK-requirements/) 列表中的 SDK 的清单文件已被包含。构建应用时,Xcode 会将所有这些清单文件合并为一个。 尽管 Adapty 不在[需要隐私清单和签名的 SDK](https://developer.apple.com/support/third-party-SDK-requirements/) 列表中,但 Adapty SDK 2.10.2 及更高版本已为方便起见包含了该文件。请确保更新 SDK 以获取清单。 虽然 Adapty 不要求在清单文件(也称为应用隐私报告)中包含任何数据,但如果您使用 Adapty 的 `customerUserId` 进行追踪,则需要在清单文件中按如下方式指定: 1. 在隐私信息文件的 `NSPrivacyCollectedDataTypes` 数组中添加一个字典。 2. 向该字典添加 `NSPrivacyCollectedDataType`、`NSPrivacyCollectedDataTypeLinked` 和 `NSPrivacyCollectedDataTypeTracking` 键。 3. 在 `NSPrivacyCollectedDataTypes` 字典的 `NSPrivacyCollectedDataType` 键中添加字符串 `NSPrivacyCollectedDataTypeUserID`(即[清单文件中需报告的数据类别和类型列表](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Describe-the-data-your-app-or-third-party-SDK-collects)中 `UserID` 数据类型的标识符)。 4. 在 `NSPrivacyCollectedDataTypes` 字典的 `NSPrivacyCollectedDataTypeTracking` 和 `NSPrivacyCollectedDataTypeLinked` 键中添加 `true`。 5. 在 `NSPrivacyCollectedDataTypes` 字典的 `NSPrivacyCollectedDataTypePurposes` 键中使用字符串 `NSPrivacyCollectedDataTypePurposeProductPersonalization` 作为值。 如果您将付费墙定向到具有自定义属性的目标受众,请仔细考虑您使用的自定义属性是否与[清单文件中需报告的数据类别和类型](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests)匹配。如果匹配,请对每种数据类型重复上述步骤。 在报告所有收集的数据类型和类别后,请按照 [Apple 文档](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Create-your-apps-privacy-report)中的说明创建应用的隐私报告。 ## App Store Connect 中的 Apple 应用隐私披露 \{#apple-app-privacy-disclosure-in-app-store-connect\} 1. 在 [App Store Connect](https://appstoreconnect.apple.com/) 中,打开您的应用并进入 **App Privacy**。点击 **Get Started**。 <img src="/assets/shared/img/app-privacy-get-started.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择 **Yes, we collect data from this app**,然后点击 **Next**。 <img src="/assets/shared/img/app-privacy-data-collection.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 数据类型 \{#data-types\} 下表列出了 Apple 要求披露的数据类型,并指出了 Adapty 所需的数据类型。**此处仅涵盖 Adapty。** 如果您的应用通过其他 SDK 或自己的代码收集了额外数据,也请选择相应的数据类型。 ✅ = Adapty 必填 👀 = 可能必填(详见下方说明) ❌ = Adapty 不需要——如果您的应用通过其他方式收集此数据,请选择 | 数据类型 | 是否必填 | 说明 | |--------------------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------| | Identifiers | ✅ | <p>如果您使用 customerUserId 标识用户,请选择"User ID"。</p><p></p><p>Adapty 会收集 IDFA,因此您必须选择"Device ID"。</p> | | Purchases | ✅ | Adapty 会从用户处收集购买历史记录。 | | Contact Info,包括姓名、电话号码或电子邮件地址 | 👀 | 如果您通过 **`updateProfile`** 方法传递姓名、电话号码或电子邮件地址等个人数据,则为必填。 | | Usage Data | 👀 | 如果您使用 Amplitude、Mixpanel、AppMetrica 或 Firebase 等分析 SDK,可能需要填写。 | | Location | ❌ | Adapty 不收集精确位置数据。如果您的应用收集,请选择。 | | Health & Fitness | ❌ | Adapty 不收集健康或健身数据。如果您的应用收集,请选择。 | | Sensitive Info | ❌ | Adapty 不收集敏感信息。如果您的应用收集,请选择。 | | User Content | ❌ | Adapty 不收集用户内容。如果您的应用收集,请选择。 | | Diagnostics | ❌ | Adapty 不收集诊断数据。如果您的应用收集,请选择。 | | Browsing History | ❌ | Adapty 不收集浏览历史记录。如果您的应用收集,请选择。 | | Search History | ❌ | Adapty 不收集搜索历史记录。如果您的应用收集,请选择。 | | Contacts | ❌ | Adapty 不收集联系人列表。如果您的应用收集,请选择。 | | Financial Info | ❌ | Adapty 不收集财务信息。如果您的应用收集,请选择。 | ### 必填数据类型 \{#required-data-types\} #### 购买记录 \{#purchases\} 使用 Adapty 时,您必须披露您的应用收集 **Purchase History**。 <img src="/assets/shared/img/feb3b9f-CleanShot_2023-08-25_at_12.32.552x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 标识符 \{#identifiers\} 使用 Adapty 时,您必须披露以下标识符: - **Device ID** — Adapty 收集 IDFA。 - **User ID** — 如果您使用 **`customerUserId`** 标识用户,则为必填。 <img src="/assets/shared/img/93f3daa-CleanShot_2023-08-25_at_12.35.272x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 数据用途 \{#data-usage\} 保存 **Data types** 后,您需要说明数据的用途: 1. 点击 **Purchases** 模块中的 **Set up purchase history**。 <img src="/assets/shared/img/purchase-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 当 Apple 询问购买历史记录数据的用途时,请为 Adapty 选择以下选项: - **Analytics** — Adapty 使用购买历史记录进行收入分析、同期群分析和数据图表统计。 - **Product Personalization** — Adapty 使用购买数据进行目标受众市场细分和付费墙定向。 - **App Functionality** — Adapty 验证购买、管理访问等级并追踪订阅状态。 如果您的应用以其他方式使用购买数据(例如,通过 Adapty 集成将购买事件发送到广告平台),请选择额外的用途。 <img src="/assets/shared/img/purchase-history.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Next**。 4. 对于 **Device ID** 和 **User ID**(如适用): 1. 点击 **User/Device ID** 模块中的 **Set up user/device ID**。 2. 当 Apple 询问标识符数据的用途时,请为 Adapty 选择以下选项: - **App Functionality** — Adapty 使用标识符管理用户画像、关联购买记录并追踪访问等级。 如果您通过 Adapty 集成(例如 AppsFlyer 或 Adjust)向第三方平台发送归因数据,还请选择 **Third-Party Advertising**。如果您的应用以其他方式使用标识符,请选择额外的用途。 <img src="/assets/shared/img/user-id-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Next**。 --- # File: apple-family-sharing --- --- title: "Apple 家庭共享" description: "在 Adapty 中启用 Apple 家庭共享以支持共享订阅。" --- Apple 的家庭共享功能允许在家庭成员之间分发应用内购买,为视频流媒体服务和儿童应用等面向群体的应用用户提供了一种便捷的方式,无需共享 Apple ID 即可分摊订阅费用。通过允许最多五名家庭成员使用同一订阅,[家庭共享](https://developer.apple.com/documentation/storekit/supporting-family-sharing-in-your-app)可以有效提升应用的用户互动度和留存率。 本指南将介绍如何为订阅开启家庭共享,并说明 Adapty 如何管理家庭内共享的购买行为。 要为特定产品启用家庭共享,请前往 [App Store Connect](https://appstoreconnect.apple.com/)。家庭共享对新旧应用内购买项目均默认关闭,因此需要为每个应用内购买项目单独启用。您可以进入**应用页面**,导航到对应的应用内购买页面,然后在"家庭共享"部分选择**开启**选项来完成操作。 请注意,一旦为某个产品启用家庭共享,**将无法再次关闭**,因为这会影响已与家庭成员共享订阅的用户体验。此外,请注意只有非消耗型商品和订阅才可以被共享。 <img src="/assets/shared/img/6db165a-CleanShot_2023-03-28_at_17.15.342x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 在弹出的对话框中,点击**确认**按钮完成设置。完成后,家庭共享部分将显示消息:"此订阅可由家庭群组中的所有人共享。"这表明订阅已成功启用家庭共享,最多可与五名家庭成员共享。 Adapty 让您无需任何额外操作即可轻松支持家庭共享。您只需[配置您的产品](app-store-products)(来自 App Store),一旦您在 App Store Connect 中**启用****家庭共享**,它将自动在 **Adapty** 中生效,并以事件形式通过 webhook 接收。 :::note 请注意,沙盒环境不支持家庭共享。 ::: 需要注意的是,当用户购买订阅并与家庭成员共享时,家庭成员最多需要等待**一小时**才能使用该订阅。Apple 设计此延迟是为了让用户有时间改变主意并取消共享。但是,如果订阅续订,家庭成员可立即使用,无需等待。 当用户购买支持家庭共享的应用内产品时,该交易将照常出现在其收据中,但会新增一个名为 `in_app_ownership_type` 的字段,其值为 `PURCHASED`。此外,系统将为所有家庭成员创建新的交易,这些交易与原始购买相比具有不同的 `web_order_line_item_id` 和 `original_transaction_id`,以及值为 `FAMILY_SHARED` 的 `in_app_ownership_type` 字段。 为确保收入计算准确,Adapty 分析仅统计 `in_app_ownership_type` 为 `PURCHASED` 的交易。`FAMILY_SHARED` 交易不计入收入和转化数据图表。 **家庭共享交易触发的事件。** `FAMILY_SHARED` 交易仅触发 **Access level updated** 事件,家庭成员不会触发各产品的订阅事件。 | 事件 | `FAMILY_SHARED` | `PURCHASED` | | --- | --- | --- | | **访问等级已更新** | 是 | 是 | | **订阅已开始** | 否 | 是 | | **试用已开始** | 否 | 是 | | **订阅已续期** | 否 | 是 | | **订阅已到期** | 否 | 是 | | **订阅已退款** | 否 | 是 | | **检测到账单问题** | 否 | 是 | 如果您的下游分析系统以 **订阅已开始** 作为关键事件,家庭成员将不会出现在其中。请使用 **访问等级已更新** 来检测活跃的家庭共享成员。 要在 Adapty 中识别其他家庭成员,您可以在事件详情中找到相关信息。首先,找到原始的家庭购买交易,然后查看该交易的事件详情,重点关注具有相同产品、购买日期和到期日期的记录。通过分析事件详情,您可以识别与原始购买相关联的其他家庭成员交易。 --- # File: app-store-small-business-program --- --- title: "App Store 小型企业计划" description: "了解 Apple 的小型企业计划、其对您收入的影响以及 Adapty 分析的相关内容" --- :::link 如需了解 Play Store 的对应计划,请参阅 [Google 降低服务费计划](google-reduced-service-fee)。 ::: 每年从 App Store 获得不超过 100 万美元收益的机构,均可申请加入苹果的[小型企业计划](https://developer.apple.com/app-store/small-business-program/)。加入后,标准 30% 的商店佣金将降至 **15%**。 计划成员必须**更改 Adapty 设置**,以确保收益计算和集成事件处理的准确性。 --- title: "小型企业计划" description: "了解如何在 Adapty 中配置小型企业计划,以及如何申请加入该计划以降低商店佣金。" metadataTitle: "小型企业计划 | Adapty 文档" --- 本文介绍以下内容: * [如果您的应用已加入小型企业计划,如何配置 Adapty](#configure-adapty) * [如果您想降低商店佣金,如何申请加入该计划](#apply-for-the-program) ## 配置 Adapty \{#configure-adapty\} Adapty 可以将折扣佣金率应用于您的[数据分析](analytics)和[集成事件](analytics-integration)。要启用此功能,请按应用逐个设置小型企业计划状态。 :::warning **在获得批准后,请立即**在 Adapty 中配置您的 SBP 状态。事后修改无法重写已推送的 webhook 事件([详情](#retroactive-setting-changes))。 ::: 1. 打开 [**App Settings** → **General**](https://app.adapty.io/account) 2. 找到 **Small Business Program** 部分。 3. 点击 **Add period**。 4. 选择加入计划的开始日期。 5. 选择结束日期,或勾选 **At the current moment** 以无限期延续此状态。如果您将来[失去资格](#losing-eligibility),可以修改结束日期。 6. 点击 **Apply**。 如果您的组织仍符合该计划的资格要求,其会员资格将自动延续到下一个日历年。但会员状态**仅适用于您指定的日期范围**。 * 点击 **Add period** 添加新的会员期。 * 如需将此状态设置为无限期,请勾选 **At the current moment**。 如需验证配置是否正确,请打开[收入数据图表](revenue)并选择 **Proceeds after store commission**,确认显示的收益已反映出降低后的佣金比例。 ## 申请加入计划 \{#apply-for-the-program\} ### 资格要求 \{#eligibility-requirements\} Apple 根据您的**年度收益**来确定小型企业计划资格——即上一个日历年扣除商店佣金和税款**后**的销售额。 要获得资格,您的组织及其<InlineTooltip tooltip="关联开发者账户">您或您的组织持有多数所有权(>50%)或具有决策权的账户。</InlineTooltip>的年度收益总计必须不超过 100 万美元。 新创建的组织可自动申请该计划。 ### 申请前准备 \{#before-you-apply\} 请确保您: - 是 Apple 开发者计划的账户持有人 - 已在 App Store Connect 中接受最新的付费应用合同 - 能够列出所有关联开发者账户 ### 注册 \{#enrollment\} 1. 前往 [App Store 小型企业计划注册页面](https://developer.apple.com/app-store/small-business-program/)。 2. 点击 **Enroll** 并使用您的 Apple 开发者账户登录。 3. 检查预填信息(姓名、电子邮件、Team ID)并提交。 ### 审核 \{#review\} 审核过程可能超过一个月。如果您符合条件,将收到 Apple 发出的审批邮件。 获批后,需要等待一段时间。降低后的佣金率将在 Apple [下一个财务周期](https://adapty.io/apple-fiscal-calendar/)的第 15 天生效,不适用于之前的交易。 ### 失去资格 \{#losing-eligibility\} 当你当前日历年度的总收益超过 100 万美元时,你将失去计划成员资格,Apple 将开始按标准 30% 的销售佣金收费。 :::important 如果你的业务退出了小企业计划,请**立即在设置中更改退出日期**。否则,Adapty 将继续按优惠费率计算佣金。 ::: 当你的年度收益重新降至 100 万美元以下后,**次年**即可重新申请该计划。详情请参阅[官方计划条款](https://developer.apple.com/app-store/small-business-program/)。 ## 追溯性设置变更 \{#retroactive-setting-changes\} 当您在 Adapty 中更改佣金减免状态并设置了追溯生效日期时,新佣金率会按不同的时间安排在 Adapty 各处数据中生效: | 生效位置 | 更改佣金率后的处理方式 | | --- | --- | | 分析看板(Revenue、Proceeds、MRR、ARR) | Adapty 在每日重新计算时(24 小时内)应用新佣金率。 | | S3、GCS 和 BigQuery 导出 | Adapty 在下次计划导出时应用新佣金率。 | | 已投递的 Webhook 事件 | Adapty 无法在投递后修改 Webhook 事件,这些事件将保留旧佣金率。 | 如果您的数据仓库存储了来自 Webhook 事件的收入数据,这些记录将保留旧佣金率。如需对账,请从分析看板中检索受影响时段的数据,或重新导出至 S3、GCS 或 BigQuery。 --- # File: android-products --- --- title: "Play Store 中的产品" description: "使用 Adapty 管理 Android 产品,简化应用内购买流程,优化变现策略。" --- 本页面提供在 Play Store 中创建产品的指导。虽然这些信息可能与 Adapty 的功能没有直接关系,但如果您在 Google Play 控制台创建产品时遇到问题,本文可作为有价值的参考资源。 产品是指您在 Play Store 应用中提供的数字商品或服务,通常可供购买。这些产品可以包括应用内商品,例如一次性购买、订阅或用户在使用您的应用时可以获取的其他数字商品。 在 [Google 的计费系统](https://developer.android.com/google/play/billing/compatibility)中,订阅可以包含多个基础方案,每个方案提供不同的折扣或优惠。该结构由三个主要组件构成: - **订阅:** 代表用户在特定时间段内可以享受的一组权益(即所售商品)。例如,为订阅者提供高级功能的"黄金等级"。 - **基础方案:** 代表特定的计费周期、续费类型和价格配置(即商品的销售方式)。例如"按年自动续费"或"按月预付"。 - **优惠:** 面向符合条件的用户提供的折扣,用于调整基础方案的价格。例如"新用户 14 天免费试用"。 ## 如何在 Play Store 中创建产品? \{#how-to-create-a-product-in-play-store\} 产品是指您在应用中提供的数字商品或服务,通常可供购买。这些产品可以包括应用内商品,例如一次性购买、订阅或用户在使用您的应用时可以获取的其他数字商品。 为 Android 设备设置产品: 1. 在 Google Play 控制台左侧菜单中,打开 [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) 或 [**Monetize** -> **In-app products**](https://console.cloud.google.com/iam-admin/serviceaccounts) 部分。 <img src="/assets/shared/img/6eff1d1-subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击 **Create subscription** 按钮。 <img src="/assets/shared/img/af7fe02-create_subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在打开的 **Create subscription** 窗口中,在 **Product ID** 字段输入订阅 ID,在 **Name** 字段输入订阅名称。 Product ID 必须唯一,且必须以数字或小写字母开头,还可以包含下划线(\_)和句点(.)。它用于在开发过程中访问您的产品,并与 Adapty 进行同步。一旦在 Google Play 控制台中将 Product ID 分配给某个产品,即使该产品被删除,该 ID 也无法再用于任何其他应用。 在命名产品 ID 时,建议遵循标准化格式。我们推荐使用更简洁的方式,将产品命名为 `<subscription name>.<access level>`。然后,您可以通过基础方案(如每周、每月等)来控制时长和计费频率。 Name 仅供您参考,将显示在您的 Google Play 商店列表中,您可以使用任何描述性名称。名称限制为 55 个字符。 4. 点击 **Create** 按钮确认创建订阅。 :::note Adapty 中的 Google Play 订阅产品 Adapty 产品对应 Google Play 订阅的基础方案,因为这些才是客户实际购买的产品。Adapty 会无缝处理现有 Google Play 订阅及其对应基础方案的迁移,无需您进行任何额外操作。但是,当您在 Adapty 中添加新产品时,您需要同时提供基础方案 ID 和产品 ID。 ::: ### 创建基础方案 \{#create-a-base-plan\} 对于订阅产品,您需要添加基础方案。基础方案决定了客户购买订阅时的计费周期、价格和续费类型。请注意,客户不会直接购买订阅产品,而是始终购买订阅内的基础方案。 创建基础方案: 1. 在 Google Play 控制台左侧菜单中,打开 [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) 部分。找到您要添加基础方案的订阅。 2. 点击该订阅旁边的 **View subscription** 按钮。 <img src="/assets/shared/img/4072a2a-subscriptions_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 订阅详情打开后,点击 **Base plans and offers** 标题下的 **Add base plan** 按钮。您可能需要向下滚动才能找到它。 <img src="/assets/shared/img/b493b60-add_base_plan.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在打开的 **Add base plan** 窗口中,在 **Plan ID** 字段输入基础方案的唯一标识符。它必须以数字或小写字母开头,可以包含数字(0-9)、小写字母(a-z)和连字符(-),并填写所有必填字段。 <img src="/assets/shared/img/8146763-CleanShot_2023-07-20_at_16.51.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 按地区指定价格。 <img src="/assets/shared/img/8b26e1d-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 点击 **Save** 按钮完成设置。 7. 点击 **Activate** 按钮使基础方案生效。 请注意,在 Adapty 中,订阅产品只能有一个具有固定时长和续费类型的基础方案。 ### 备用产品 \{#fallback-products\} :::warning 支持非向后兼容基础方案 旧版本的 Adapty SDK 不支持 Google Billing Library v5+ 的特性,特别是每个订阅产品的多个基础方案和优惠。只有在 Google Play 控制台中标记为 **[向后兼容](https://support.google.com/googleplay/android-developer/answer/12124625?hl=en#backwards_compatible)** 的基础方案才能在这些 SDK 版本中使用。请注意,每个订阅只能有一个基础方案被标记为向后兼容。 ::: <img src="/assets/shared/img/b5e70cb-CleanShot_2023-07-20_at_17.03.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 为了充分利用 Adapty 中增强的 Google 订阅配置和功能,我们提供了设置向后兼容备用产品的能力。该备用产品专门用于使用旧版本 Adapty SDK 的应用。在创建 Google Play 产品时,您现在可以选择是否在 Play 控制台中将该产品标记为向后兼容。Adapty 会利用此信息来判断该产品是否可以被旧版本 SDK(2.5 及以下版本)购买。 假设您有一个名为 `subscription.premium` 的订阅,它提供两个基础方案:每周(向后兼容)和每月。如果您将 `subscription.premium:weekly` 产品添加到 Adapty,则无需指定向后兼容产品。但是,对于 `subscription.premium:monthly` 产品,您需要指定一个向后兼容产品。如果不这样做,可能会导致在 Google 第四代计费库中意外购买 `subscription.premium:weekly` 产品。为了解决这种情况,您应该创建一个单独的产品,其基础方案也是每月且标记为向后兼容。这样可以确保选择 `subscription.premium:monthly` 选项的用户按照预期的频率正确扣费。 ## 将产品添加到 Adapty \{#add-products-to-adapty\} 在 App Store Connect 中完成添加应用内购买、订阅和优惠之后,下一步是[将这些产品添加到 Adapty](create-product)。 --- # File: google-play-data-safety --- --- title: "Google Play 数据安全" description: "确保在 Adapty 中符合 Google Play 数据安全政策。" --- Google Play 上提供的数据安全部分为应用开发者提供了一种简便方法,用于向用户说明应用收集或共享的数据,并突出显示应用的关键隐私和安全措施。这些信息能够帮助用户在选择下载和使用哪些应用时做出更明智的决定。 以下是关于 Adapty 收集的数据的简短指南,帮助您向 Google Play 提供所需信息。 ## 数据收集与安全 \{#data-collection-and-security\} <img src="/assets/shared/img/3508c24-image4.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **您的应用是否收集或共享任何所需的用户数据类型?** 选择"是",因为 Adapty 会收集用户的购买历史记录。 **您的应用收集的所有用户数据在传输过程中是否都经过加密?** 选择"是",因为 Adapty 会对传输中的数据进行加密。 **您是否提供了让用户请求删除其数据的方式?** 如果选择"是",请确保您的用户有办法联系您的支持团队以请求删除数据。您可以直接从 Adapty 看板或通过 REST API 删除用户。 ## 数据类型 \{#data-types\} 以下是 Google 要求报告的数据类型列表,我们已说明 Adapty 是否收集了各类特定数据。 | 数据类型 | 详情 | | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 位置 | Adapty 不收集 | | 健康与健身 | Adapty 不收集 | | 照片与视频 | Adapty 不收集 | | 文件与文档 | Adapty 不收集 | | 日历 | Adapty 不收集 | | 联系人 | Adapty 不收集 | | 用户内容 | Adapty 不收集 | | 浏览历史记录 | Adapty 不收集 | | 搜索历史记录 | Adapty 不收集 | | 应用信息与性能 | Adapty 不收集 | | 网页浏览 | Adapty 不收集 | | 联系信息 | Adapty 不收集 | | 财务信息 | Adapty 收集用户的购买历史记录 | | 个人信息与标识符 | 如果您明确将相关信息传递给 Adapty SDK,Adapty 会收集用户 ID 及其他可识别联系信息,包括姓名、电子邮件地址、电话号码等。 | | 设备及其他标识符 | Adapty 收集设备 ID 数据。 | ## 数据使用与处理 \{#data-usage-and-handling\} ### 用户 ID \{#user-ids\} **1. 此数据是被收集、共享,还是两者皆有?** 此数据由 Adapty 收集。如果您正在使用 Adapty 与非服务提供商第三方之间设置的集成,您可能还需要在此处披露"共享"。 **2. 此数据是否以临时方式处理?** 选择"否"。 **3. 此数据对您的应用是必需的,还是用户可以选择是否收集?** 此数据收集是必需的,无法关闭。 <img src="/assets/shared/img/2c60161-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **4. 为什么收集此用户数据?/ 为什么共享此用户数据?** 勾选"应用功能"和"分析"复选框。 <img src="/assets/shared/img/07a3c9e-image2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 财务信息 \{#financial-info\} 如果您正在使用 Adapty,您必须在 Google Play Console 的数据类型部分中披露您的应用会收集"购买历史记录"信息。 <img src="/assets/shared/img/1057870-image7.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 设备或其他 ID \{#device-or-other-ids\} <img src="/assets/shared/img/d10f132-CleanShot_2023-03-01_at_17.55.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/ccb1a2a-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 后续步骤 \{#next-steps\} 完成数据安全选项后,Google 将显示您应用隐私部分的预览。如果您已选择前文提到的"财务信息"和"设备或其他 ID",您的隐私信息应与以下示例类似: <img src="/assets/shared/img/e8d9b73-image3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果您已准备好提交应用进行审核,请参阅我们的[发布检查清单](release-checklist)文档,以获取有关准备提交应用的更多指导。 --- # File: google-reduced-service-fee --- --- title: "Google 降低服务费" description: "了解 Google 降低服务费计划、其对收入的影响以及 Adapty 的分析处理方式" --- :::link 如需了解 App Store 的对应计划,请参阅 [App Store 小型企业计划](app-store-small-business-program)。 ::: Google Play 的[降低服务费计划](https://support.google.com/googleplay/android-developer/answer/112622?hl=en)将每年前 100 万美元收益的佣金从 30% 降低至 **15%**。同一日历年内超过 100 万美元的收益仍按标准 30% 费率收取。 :::note 自 2022 年 1 月 1 日起,Google 对所有自动续订订阅统一收取 15% 的费率,与该计划无关。降低服务费计划主要适用于非订阅类应用内购买和付费应用。 ::: 项目成员必须**修改 Adapty 设置**,以确保收入计算和集成事件处理的准确性。 本文介绍: * [如果您的应用已加入减免服务费项目,如何配置 Adapty](#configure-adapty) * [如果您希望降低商店佣金,如何加入该项目](#enroll-in-the-program) ## 配置 Adapty \{#configure-adapty\} Adapty 可以将降低的佣金率应用到[数据图表](analytics)和[集成事件](analytics-integration)中。要启用此功能,请按应用逐一设置你的降低服务费状态。 :::warning **一旦注册**,请立即在 Adapty 中配置你的降低服务费状态。事后修改不会重写已发送的 webhook 事件([详情](#retroactive-setting-changes))。 ::: 1. 打开 [**App Settings** → **General**](https://app.adapty.io/account)。 2. 找到 **Reduced Service Fee** 部分。 3. 点击 **Add period**。 4. 选择会员资格的开始日期。 5. 选择结束日期,或勾选 **At the current moment** 以无限期延续此状态。如果你的[年收入超过 100 万美元](#exceeding-the-threshold),可以修改结束日期。 6. 点击 **Apply**。 会员状态**仅适用于您指定的日期范围**,且每个自然年重置一次。 * 点击 **Add period** 可添加新的会员期。 * 若要无限期延续该状态,请勾选 **At the current moment**。 要验证配置是否正确,请打开[收入数据图表](revenue)并选择 **Proceeds after store commission**,确认显示的收益已反映折扣后的佣金比例。 ## 加入计划 \{#enroll-in-the-program\} ### 资格要求 \{#eligibility-requirements\} Google 根据您在 <InlineTooltip tooltip="Account Group">一组开发者账户,其收益合并计算。您需要将您的开发者账户指定为主开发者账户,并将所有关联账户添加到该群组中。</InlineTooltip> 中所有账户的**年收入**来确定资格。 15% 的费率适用于合并年收入的前 100 万美元。超出该阈值的收入将按 30% 收取。 ### 加入前的准备 \{#before-you-enroll\} 请确保您已: - 设置了[付款资料](https://support.google.com/googleplay/android-developer/answer/10632485) - 能够列出所有关联的开发者账户 ### 注册流程 \{#enrollment\} 1. 前往 [Google Play Console](https://play.google.com/console/)。 2. 创建账户群组,并将您的开发者账户设为主开发者账户。 3. 将所有关联的开发者账户链接到该群组。 4. 接受减免服务费计划条款。 完成以上步骤后,Google 将自动为您注册。无需人工审核,也不会收到审批邮件。有关详细说明,请参阅 Google 的[注册指南](https://support.google.com/googleplay/android-developer/answer/10632485)。 ### 超出门槛 \{#exceeding-the-threshold\} 当你的年收入合计超过 100 万美元时,Google 将对当年剩余时间内超出门槛的部分收取 30% 的费用。 :::important 如果你的年收入超过 100 万美元,请**立即在 Adapty 设置中修改退出日期**。否则,Adapty 将继续按优惠费率计算佣金。 ::: 该计划每个日历年重置一次。如果您某年的收入超过 100 万美元,下一年的前 100 万美元将自动再次适用 15% 的费率,无需重新注册。详情请参阅[官方计划条款](https://support.google.com/googleplay/android-developer/answer/112622?hl=en)。 ## 追溯性设置更改 \{#retroactive-setting-changes\} 当您在 Adapty 中更改佣金减免状态并设置了追溯生效日期时,新佣金率会按不同的时间安排在 Adapty 各处数据中生效: | 生效位置 | 更改佣金率后的处理方式 | | --- | --- | | 分析看板(Revenue、Proceeds、MRR、ARR) | Adapty 在每日重新计算时(24 小时内)应用新佣金率。 | | S3、GCS 和 BigQuery 导出 | Adapty 在下次计划导出时应用新佣金率。 | | 已投递的 Webhook 事件 | Adapty 无法在投递后修改 Webhook 事件,这些事件将保留旧佣金率。 | 如果您的数据仓库存储了来自 Webhook 事件的收入数据,这些记录将保留旧佣金率。如需对账,请从分析看板中检索受影响时段的数据,或重新导出至 S3、GCS 或 BigQuery。 --- # File: google-play-quota-increase --- --- title: "申请提高 Google Play Developer API 配额" description: "如果您在历史数据导入期间或因订阅用户数量庞大而超出默认限制,请申请提高 Google Play Developer API 的配额。" --- Adapty 使用 [Google Play Developer API](https://developers.google.com/android-publisher) 来验证购买并同步订阅数据。该 API 的默认配额为每分钟 3,000 次查询。如果您的应用超过此限制,Google 将向您发送电子邮件通知。这种情况通常发生在[历史数据导入](importing-historical-data-to-adapty)期间,或在拥有大量活跃订阅用户的应用中。 为避免服务中断,请在运行大规模导入之前,或在收到配额超限通知后,向 Google 申请提高配额。 ## 开始之前 \{#before-you-start\} 如果尚未启用,请先启用[实时开发者通知 (RTDN)](enable-real-time-developer-notifications-rtdn)。RTDN 通过推送通知而非轮询方式传递订阅更新,从而减少 API 消耗。如果未启用 RTDN,Google 可能会拒绝配额提升申请。 ## 收集所需信息 \{#gather-required-information\} 在打开申请表单之前,请收集以下信息: - **开发者账号 ID**:请在 [Google Play Console](https://play.google.com/console/) 中前往 **Settings > Developer account > Account details**。ID 显示在页面顶部。 - **应用软件包名称**:您的 Android 应用的软件包名称(例如 `com.example.app`)。可在 Google Play Console 的应用 **Dashboard** 页面中找到。 - **Google Cloud 项目编号**:请在 [Google Cloud Console](https://console.cloud.google.com/) 中选择您的项目。项目编号显示在 **Dashboard** 页面上。 ## 申请提高配额 \{#request-the-quota-increase\} 1. 打开 [Google Play Developer API 配额提升申请表单](https://support.google.com/googleplay/android-developer/contact/apiqr)。 2. 输入您的开发者账号 ID、应用软件包名称和 Google Cloud 项目编号。 3. 选择需要提升配额的 API 和配额桶。如果您收到了 Google 关于超出配额的电子邮件,其中会注明具体是哪个配额桶。 4. 在理由说明字段中,说明您使用了第三方订阅管理服务,该服务需要通过 API 访问来验证购买并同步订阅数据。 5. 在请求配额字段中,输入您所需的数量。如果不确定需要申请多少,请在 [Google Cloud Console](https://console.cloud.google.com/) 的 **IAM & Admin > Quotas** 下查看当前使用情况(筛选"Google Play Android Developer API"),然后将您的使用数据以及计划导入的历史条目数量发送至 [support@adapty.io](mailto:support@adapty.io),我们可以帮助您确定合适的申请数量。 6. 提交表单。 Google 通常会在几个工作日内处理配额提升申请。 --- # File: prepare-your-app-for-store-review --- --- title: "准备您的应用接受商店审核" description: "帮助您的应用通过 App Store 和 Google Play Store 审核的建议" --- 本文介绍商店审核应用提交的流程,并提供加快应用审核通过的技巧。内容参考自官方提交指南: * [App Store 提交指南](https://developer.apple.com/app-store/review/guidelines/) * [Google Play Store 提交指南](https://play.google/developer-content-policy/) :::important 两个应用商店遵循相似的审核流程。当某项政策仅适用于其中一个商店时,文章会明确指出该商店的名称。 ::: Adapty 用户应特别关注[付费墙和应用内购买](#iap-related-requirements)相关的合规问题,这些是应用被拒的最常见原因之一。 ## 开始之前 \{#before-you-begin\} 确认您的应用已准备好提交。 Adapty 提供了一份[发布检查清单](release-checklist),帮助您为应用发布做好准备。 Google Play Store 要求首次发布的开发者在提交应用前先[测试应用](https://support.google.com/googleplay/android-developer/answer/14151465?hl=en)。测试应至少涉及 12 人,并持续至少 14 个连续天数。此要求于 2025 年引入,旨在减少进入 Google 审核团队的存在问题的应用数量。 ## 审核流程概述 \{#review-process-overview\} #### 第一步:自动检测 \{#step-1-the-automated-screening\} App Store 和 Google Play Store 的审核流程基本相似,都分为两个步骤。提交后,系统会立即对应用进行自动扫描,这一过程可能持续数小时。 两家应用商店都会对你的应用进行恶意软件扫描,Google 在这方面尤为严格。它会检测恶意行为的特征,例如与可疑服务器的通信以及对用户数据的无正当理由访问。如果应用被判定为潜在有害,将被标记并转交人工安全分析师审核。[Google Play Protect 文档](https://developers.google.com/android/play-protect/cloud-based-protections#machine-learning)中列出了此步骤中执行的大致检查项目。 应用商店还会确认必要元数据是否完整、是否存在有害或严重过时的依赖项,以及构建包的完整性。 #### 第二步:人工审核 \{#step-2-the-human-review\} 通过自动化审查后,您的应用将由人工审核员进行检查。根据应用的复杂程度和当前审核队列的长度,此步骤可能需要数天时间。涉及敏感数据处理的应用审核周期通常更长。 ## 一般要求 \{#general-requirements\} ### 稳定性 \{#stability\} 在审核期间崩溃的应用将被拒绝。审核员可能会故意模拟不可靠的网络条件,因此应用必须能够良好处理这些情况。 ### 完整性 \{#completeness\} Apple 和 Google 都对应用商店内容提出了*完整性*("最低功能")要求。 * 占位符、"即将推出"屏幕以及功能缺失会导致 iOS 应用被拒。 * Google [更为宽松](https://support.google.com/googleplay/android-developer/answer/9898783?hl=en),尤其是当你的应用处于[抢先体验](https://knowledge.workspace.google.com/admin/users/access/turn-early-access-apps-on-or-off-for-users)阶段时。 * 两大应用商店都会**拒绝**功能极少甚至没有实质功能的应用,包括仅显示单张图片、PDF 文件或网页的应用。 内容缺失同样属于这一类情况。 * 如果应用实际功能与宣传内容不符,将会被拒绝。 * 如果你在看板中配置了应用内购买,但未将其包含在构建版本中,应用将会被拒绝。 ### 元数据准确性 \{#metadata-accuracy\} 描述、截图和其他元数据中存在误导性、不准确或不一致的信息可能导致拒绝。 不要使用商店列表来宣传未来的应用功能。 如果应用不是为普通大众设计的,审核员将寻找解释其工作流程的额外文档。请在应用的元数据中提供清晰的说明。 ### 内容分级 \{#content-rating\} 应用内的内容必须与其声明的分级相符。 ### 法律方面 \{#legal-aspects\} * 您的应用隐私政策应可在应用内部访问。您可以使用付费墙编辑工具的[链接按钮](paywall-buttons#links)。 * 要求用户在法律协议**生效之前**阅读并接受。 * 在应用中披露广告的存在。不这样做可能导致被拒绝。 * 如果您的 iOS 应用包含应用内购买,您必须在 App Store Connect 看板中接受**付费应用协议**。 ### 身份验证 \{#authentication\} 如果您的应用中有部分内容仅在身份验证后可用,请为商店审核员提供有效的访问凭据。无法完整访问内容将导致被拒绝。 如果您的应用允许用户创建账户,也应允许他们删除账户。将用户引导至基于电子邮件的支持或网站不满足此要求。 ### 访问与隐私 \{#access-and-privacy\} 应用的元数据必须清楚说明每项请求权限的原因。最敏感的权限(例如访问短信和通话记录)可能需要视频演示。 同样的原则适用于敏感用户数据:如果您请求这些数据,请解释原因。 ## 应用内购买相关要求 \{#iap-related-requirements\} 商业政策违规是应用被拒绝的最常见原因之一。如果您的应用主要通过订阅和应用内购买变现,将面临更严格的审查。 ### 付费墙要求 \{#paywall-requirements\} 应用审核人员期望付费墙简洁明了、易于理解。 如果被怀疑存在用户欺骗行为,应用将被拒绝上架。如果多次审核发现欺骗性行为的证据,您的账号可能被停用,应用也可能遭到[暂停](https://support.google.com/googleplay/android-developer/community-guide/287283557/app-suspended-for-repeated-rejections?hl=en)。Google Play 采用[违规警告制度](https://support.google.com/googleplay/android-developer/answer/9899234?hl=en),严重时可导致您的所有应用被下架。 在设计付费墙时,请遵循以下规范: - **保持透明,提前告知用户。** 在引导用户购买之前,清晰展示产品的实际价格、扣费频率、权益内容以及取消条件。 明确区分一次性购买和需要定期付款的产品。 如果产品附带免费试用,请清楚说明试用时长和相关条件。 不得使用故意混淆视听的语言误导用户。 - **保持一致。** 产品价格必须在 App Store 列表、应用内页面、订阅管理页面和营销内容之间保持一致。任何价格差异,无论多小,都可能导致审核被拒。 Adapty 的付费墙编辑工具会自动将付费墙上的价格与 App Store Connect 产品同步。如果你的付费墙是手动编写代码的,则需要从数据数组中[获取每个产品的价格](fetch-paywalls-and-products)。 - **平等展示所有档位。** 不得预先选中最贵的选项,也不得隐藏较便宜的选项。 - **避免"暗黑模式"。** 不得制造虚假的紧迫感或稀缺感。 不得通过故意让免费功能变得不便或难以找到来强迫用户购买。 ### 访问保障 \{#access-guarantee\} 应用必须保障用户访问其已购内容的权利。 * **立即开通访问权限** 购买成功后,应立即解锁对应产品的访问权限,不得出现明显延迟。 支付授权的中间状态不应引发错误或破坏用户体验。 购买成功后,付费墙应立即隐藏。若在购买完成后继续显示付费墙,用户将无法访问其已付费的内容。 * **访问恢复** 用户应该能够从新设备恢复对产品的访问权限。请将恢复按钮放置在显眼位置。 如果您使用 [Flow Builder](adapty-flow-builder) 构建付费墙,恢复按钮会自动触发恢复流程。如果您[手动实现了付费墙](ios-implement-paywalls-manually),请添加调用 [restorePurchases](restore-purchase) 方法的代码。Adapty 将恢复用户的访问等级,**除非**您以[观察者模式](observer-vs-full-mode)使用 SDK。 应用应能识别从产品商店页面或应用商店其他位置发起的应用内购买。 ### 适当的支付方式 \{#appropriate-payment-methods\} 两大应用商店均禁止通过应用内购买销售实物商品,且要求大多数数字商品使用应用商店内的计费方式。 但在部分地区(包括美国和欧盟),应用商店内计费的要求并不适用。根据所在国家/地区,你可能可以[完全绕过应用商店计费](https://support.google.com/googleplay/android-developer/answer/16497028),或[向用户提供选择](https://support.google.com/googleplay/android-developer/answer/13821247),让其在应用商店计费和第三方计费之间自行决定。 某些应用类别(如电子书阅读器或交友应用)即使在上述地区之外,也可能符合使用替代支付方式的条件。详情请查阅各应用商店的官方指南。 :::tip 与 [Google](https://support.google.com/googleplay/android-developer/answer/13821247) 不同,Apple 并未提供允许替代计费方式的国家/地区完整列表。随着更多司法管辖区通过类似法规,可用范围将持续扩大。在操作之前,请务必阅读与您所在国家/地区相关的文档。 ::: 请注意,两大应用商店都对支付提供商集成有明确的规定,并且会对使用这些服务的交易继续收取佣金。 ## 处理被拒情况 \{#handling-rejection\} 如果你的应用遭到拒审,审核人员会注明违反了哪些条款。请仔细阅读相关条款并加以修改: * [App Store 提交审核指南](https://developer.apple.com/app-store/review/guidelines/) * [Google Play 商店提交审核指南](https://play.google/developer-content-policy/) 如果你认为拒审不合理,有权提出申诉。请提供合规证明并联系应用商店。 * 应用审核期间不要更新应用。 * 每次提交审核时,审核人员可能不同。这可能对你有利,也可能不利。 * 不要逐个修复问题。等所有问题都修复后,再重新提交审核。 * 如果 Google Play 因违反政策而拒绝了你的应用,请在所有渠道(包括已暂停或未激活的渠道)中更新相关数据。 * 后续审核通常比首次审核花费的时间更短。 * 针对严重 bug 和紧急截止日期,可能可以申请加急审核——请谨慎使用。 ## 通过审核后:持续监控 \{#after-the-review-continuous-monitoring\} 应用商店在应用通过审核后仍会持续监控。 如果你的应用在获批后功能发生变化(例如通过动态加载代码实现),将会被标记并下架。大量负面用户反馈同样可能引发额外审查。 2024 年至 2025 年间,Google [将 Play Store 中 47% 的应用下架](https://techcrunch.com/2025/04/29/google-play-sees-47-decline-in-apps-since-start-of-last-year/),以提升整体应用质量。 放弃维护应用同样存在风险。[Google](https://www.cnet.com/tech/mobile/google-play-store-will-hide-apps-that-havent-been-updated-in-years/) 和 [Apple](https://developer.apple.com/support/app-store-improvements/#:~:text=Developers%20of%20apps%20that%20have,launch%20will%20be%20removed%20immediately.) 都会将长期未更新或无人下载的应用下架。 ## 另请参阅 \{#see-also\} * [沙盒测试](test-purchases-in-sandbox) * [发布检查清单](release-checklist) --- # File: firebase-apps --- --- title: "Firebase 应用" description: "将 Firebase 与 Adapty 集成,以增强您移动应用的用户分析和订阅跟踪能力。" --- 本页面介绍如何在基于 Firebase 的应用中集成 Adapty。 :::note 快速入门 这并不是 Adapty 正常工作所需的全部步骤,只是一些与 Firebase 集成的实用提示。如果您想在应用中集成 Adapty,请先阅读[快速入门指南](quickstart)。 ::: ## 用户识别 \{#user-identification\} 如果您正在使用 Firebase 身份验证,以下代码片段可以帮助您保持 Firebase 与 Adapty 之间的用户同步。请注意,这只是一个示例,您应根据应用的具体身份验证逻辑进行调整。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS with Firebase" default> ```swift showLineNumbers @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Configure Adapty before Firebase Adapty.activate("YOUR_API_KEY") Adapty.delegate = self // Configure Firebase FirebaseApp.configure() // Add state change listener for Firebase Authentication Auth.auth().addStateDidChangeListener { (auth, user) in if let uid = user?.uid { // identify Adapty SDK with new Firebase user Adapty.identify(uid) { error in if let e = error { print("Sign in error: \(e.localizedDescription)") } else { print("User \(uid) signed in") } } } } return true } } extension AppDelegate: AdaptyDelegate { // MARK: - Adapty delegate func didReceiveUpdatedPurchaserInfo(_ purchaserInfo: PurchaserInfoModel) { // You can optionally post to the notification center whenever // purchaser info changes. // You can subscribe to this notification throughout your app // to refresh tableViews or change the UI based on the user's // subscription status NotificationCenter.default.post(name: NSNotification.Name(rawValue: "com.Adapty.PurchaserInfoUpdatedNotification"), object: purchaserInfo) } } ``` </TabItem> <TabItem value="kotlin" label="Android with Firebase" default> ```kotlin showLineNumbers class App : Application() { override fun onCreate() { super.onCreate() // Configure Adapty Adapty.activate(this, "YOUR_API_KEY") Adapty.setOnPurchaserInfoUpdatedListener(object : OnPurchaserInfoUpdatedListener { override fun onPurchaserInfoReceived(purchaserInfo: PurchaserInfoModel) { // handle any changes to subscription state } }) // Add state change listener for Firebase Authentication FirebaseAuth.getInstance().addAuthStateListener { auth -> val currentUserId = auth.currentUser?.uid if (currentUserId != null) { // identify Adapty SDK with new Firebase user Adapty.identify(currentUserId) { error -> if (error == null) { //success } } } else { Adapty.logout { } } } } } ``` </TabItem> </Tabs> --- # File: refund-saver --- --- title: "退款拯救器" description: "使用 Adapty 退款拯救器,减少退款、最大化营收。" --- 每当用户申请退款时,Apple 都会进行调查。为了判断**退款是否合理**,它会向开发者索取该用户的活动信息。如果没有这些证据,即使是使用频率很高的订阅也很可能被退款。 **Refund Saver** 会[自动](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information-v1)响应 Apple 的消费信息请求,保护您的收入,并**提高不合理退款请求被拒绝的概率**。它适用于所有 Apple 应用内购买类型——自动续订订阅、一次性订阅、消耗型商品及非消耗型商品(包括永久授权产品)。 ## 退款拦截器的工作原理 \{#how-refund-saver-works\} 1. 当用户发起退款请求时,App Store 会发送通知,要求提供交易和使用详情。 如果您**忽略**或**延迟**响应,Apple 很可能会**批准退款**。 2. Adapty 退款拦截器会自动处理这些通知,向 Apple 提供所需数据。 这一自动化机制能够减少不必要的退款,同时节省时间并保护您的收益。 3. Adapty 会记录每次结果——退款成功或拒绝退款。这些数据将驱动看板中的退款拦截器分析报告。 :::info 通过 Refund Saver,您最多可以挽回退款请求中 40% 的收入。 ::: ## 使用 Refund Saver 的前提条件 \{#requirements-to-use-refund-saver\} 要使用此功能,请确保满足以下前提条件: 1. **在 App Store Connect 中更新您的隐私政策:** 您的应用隐私政策必须披露消费数据的收集和使用方式,确保用户在下载前了解您应用的隐私实践。请参阅 [Apple 的应用隐私详情](https://developer.apple.com/app-store/app-privacy-details/) 获取指导。 2. **在您的应用中获取用户对数据共享的同意:** Apple 要求您在将用户个人数据共享给 Apple 之前,必须获得用户的有效同意。作为开发者,您负责获取此同意,因为数据共享由您发起。详情请参阅 Apple 的[指导方针](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information)。 3. **启用 Server Notifications V2:** 请确保在您的 Apple Developer 账户中已激活 Server Notifications V2,并已在 Adapty 中正确配置,因为不支持 V1 通知。如果尚未激活,请按照[启用 App Store 服务器通知](enable-app-store-server-notifications)指南中的步骤操作。 ## 开启 Refund Saver \{#turn-on-refund-saver\} 1. 在 Adapty 看板中打开 [Refund Saver](https://app.adapty.io/refund-saver) 部分。 2. 点击 **Turn on Refund Saver** 以激活该功能。 ## 设置默认退款行为 \{#set-a-default-refund-behavior\} Apple 允许开发者在响应退款请求时,为每条请求指定一个倾向性处理结果。此设置的目的是在拒绝和接受退款请求之间找到合理的平衡,确保只批准合理的退款。请注意,该设置仅用于影响处理结果,最终决定权仍归 Apple 所有。 Adapty 支持设置此偏好项,但我们会对所有退款请求使用同一个值。 1. 若需更改偏好设置,请点击 **Edit refund preference**。 2. 在 **Edit refund preference** 窗口中,选择您的 **Default refund request preference** 选项: | 选项 | 描述 | | -------------------------------------------- | ------------------------------------------------------------ | | Always decline | (默认)这是默认选项,通常在减少退款方面效果最佳。 | | Decline first refund request, grant all next | 对于 Refund Saver 遇到的每笔交易,系统最初会请求 Apple 拒绝退款。但如果同一笔交易再次出现,Refund Saver 将始终建议批准退款。这种方式有助于减少用户因退款被不公平拒绝而产生的不满——用户只需再次申请退款,通常就能成功。 | | Always refund | 建议 Apple 批准所有退款申请。 | | No preference | 不向 Apple 提供任何建议。在这种情况下,Apple 将根据其内部政策和用户历史记录自行决定退款结果,不受您设置的影响。此选项是最中立的处理方式。 | ## 在看板中为特定用户设置退款行为 \{#set-refund-behavior-for-a-specific-user-in-the-dashboard\} 即使您已为整个应用配置了默认的 Refund Saver 行为,也可以为特定用户单独设置偏好。在 Adapty 看板中,您可以从用户的用户画像页面进行操作。请使用左下角的 **Refund Saver Preferences** 部分。 :::note 单用户偏好设置会覆盖应用级别的默认设置,包括"拒绝首次退款请求,允许后续所有请求"的行为。 ::: ## 在 SDK 中为特定用户设置退款行为 \{#set-refund-behavior-for-a-specific-user-in-the-sdk\} 您可以在应用代码中,根据用户的具体操作,为每次安装单独设置退款偏好。使用以下代码片段来设置偏好: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers code do { try await Adapty.updateRefundPreference(<PREFERENCE_VALUE>) // possible values: .noPreference, .grant, .decline } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```javascript showLineNumbers code try { // possible values: AdaptyRefundPreference.noPreference, AdaptyRefundPreference.grant, AdaptyRefundPreference.decline await Adapty().updateRefundPreference(<PREFERENCE_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateRefundPreference(<PREFERENCE_VALUE>); // possible values: RefundPreference.NoPreference, RefundPreference.Grant, RefundPreference.Decline } catch (error) { // handle the `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreRefundPreference(<PREFERENCE_VALUE>, (error) => { if (error != null) { // handle the error return; } }); ``` </TabItem> </Tabs> :::note 您也可以使用服务器端 API 来[设置单独的退款偏好](api-adapty/operations/setRefundSaverSettings): - 当偏好设置与客户端交互直接相关时(例如用户点击按钮来配置偏好),请使用 SDK。 - 当需要进行服务器端处理,或与您的应用架构更契合时,请使用 API。 ::: ## 获取用户同意 \{#obtain-user-consent\} 如何收集用户的数据共享同意由你自行决定,但 Apple 要求在向其共享任何个人数据之前必须获得有效的用户同意。Apple 建议采用**选择加入(opt-in)**的方式,即通过应用内弹窗说明数据的使用方式,并要求用户明确操作以表示同意。如果用户忽略或拒绝了弹窗,则视为未同意。更多详情请参阅 Apple 的[使用指南](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information)。 如果在您的应用中显式征得用户同意不太可行,可以考虑采用**选择退出**方式。这种方式需要在服务条款中加入数据共享条款,说明用户通过接受条款即表示同意数据共享。请务必清楚说明用户如何撤销其同意。 以下是选择退出方式的示例条款,包含您可能共享的数据类型。这仅供参考,用于帮助您起草自己的文本。您有责任确保最终版本符合所有适用法律及 Apple 的要求。 *"如果我们收到应用内购买的退款请求,可能会向 Apple 提供该用户的应用内购买活动信息。这可能包括:距应用安装的时间、应用总使用时长、匿名账户标识符、应用内购买是否已完全消费、是否包含试用期、总消费金额以及总退款金额。"* 根据您选择的方案,在 **Edit refund preferences** 菜单中设置 **Default consent policy** 选项: <p> </p> | 选项 | 描述 | | ------- | ------------------------------------------------------------ | | Opt-out | (默认)如果 Adapty 不知道用户的同意状态,则假定用户**已授权**,退款保护功能**将向** Apple 共享退款相关数据。 | | Opt-in | 如果 Adapty 不知道用户的同意状态,则假定用户**未授权**,退款保护功能**不会**向 Apple 共享任何数据。这是 Apple 推荐的方式。 | ## 在 SDK 中更新用户同意状态 \{#update-user-consent-in-the-sdk\} 如需告知 Adapty 某个用户是否已提供同意,请使用 `updateCollectingRefundDataConsent` 方法。该值会按用户画像持久化存储在服务端,因此只需在同意状态发生变化时调用此方法。 <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers do { try await Adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>) // true = 明确提供同意,false = 明确撤销同意 } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```dart showLineNumbers try { // true = user gave consent, false = user revoked consent await Adapty().updateCollectingRefundDataConsent(<CONSENT_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>); // true = 明确提供同意,false = 明确撤销同意 } catch (error) { // 处理 `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreCollectingRefundDataConsent(<CONSENT_VALUE>, (error) => { if (error != null) { // 处理错误 return; } }); ``` </TabItem> </Tabs> :::note 您也可以使用服务端 API 来[设置个人数据共享偏好](api-adapty/operations/setRefundSaverSettings): - 当偏好设置与用户操作直接相关时(例如用户点击按钮进行配置),请使用 SDK。 - 当需要进行服务端处理,或更符合您的应用架构时,请使用 API。 ::: ## 检查用户同意状态 \{#check-user-consent\} 您可以随时查看用户当前的同意状态。在 Adapty 看板中,打开用户画像,在左下角的 **Refund Saver Preferences** 区域找到 **Allow data sharing** 设置即可。 :::note 您也可以使用服务端 API [获取单个用户的退款和数据共享偏好设置](api-adapty/operations/getRefundSaverSettings)。 ::: ## 限制 \{#limitations\} - **仅限 Apple App Store:** Refund Saver 仅适用于向 Apple App Store 提交的退款申请。Google Play 不提供针对退款的消费数据分析。Google Play 上的退款决策完全基于 Google 的政策及用户提供的信息。 - **需要 Server Notifications V2:** Refund Saver 与 App Store Server Notifications V1 不兼容。如果您目前在 Adapty 中使用的是 V1,需要切换至 V2,请参阅 [向 Adapty 发送 App Store 服务器通知](enable-app-store-server-notifications) 指南了解详情。切换至 V2 还能为 Adapty 提供更准确、更全面的数据,从而提升您的分析质量。 --- # File: meta-create-campaign --- --- title: "在 Meta Ads 中推广您的应用" --- 在本分步指南中,您将学习如何在 Meta 中为您的应用创建和设置广告,以便轻松对其进行优化并跟踪其效果。 ## Meta 中的广告结构 \{#how-ads-in-meta-are-structured\} 在 Meta Ads 中投放广告时,您需要配置三个层级: - **广告系列(Campaign)**:广告系列定义您的广告目标。 - **广告组(Ad set)**:广告组指定您的目标受众和版位——决定向谁展示广告以及在何处展示。每个广告系列可以包含多个广告组。 - **广告(Ads)**:广告是用户看到并与之互动的实际创意素材。每个广告组可以包含多条广告;但是,建议每个广告组最多设置五条广告,以获得最佳效果。 ## 步骤一:创建 Meta Ads Manager 账户 \{#step-1-create-meta-ads-manager-account\} 要开始使用 Meta Ads,你需要拥有一个 Facebook 商业主页,因为个人主页无法投放广告。 因此,你需要将商业主页关联到 Meta Ads 商业投资组合: 1. 前往 [business.facebook.com](https://business.facebook.com/)。如果商业投资组合中还没有商业主页,你需要先添加一个。点击 **Go to settings**。 2. 从左侧边栏进入 **Account > Pages**。点击 **Add**,选择 **Add an existing Facebook page** 或 **Create a new Facebook page**。如果你还没有主页,请参阅[创建主页指南](https://www.facebook.com/business/help/473994396650734)。 3. 可选步骤:在设置页面的 **Account > Instagram accounts** 中绑定你的 Instagram 账号。 连接好主页后,即可继续后续操作。 ## 步骤 2:添加 Meta 像素 \{#step-2-add-meta-pixel\} 你需要一个 Meta 像素,将广告系列数据与收入关联起来,从而获得更好的效果。 在连接数据和创建 pixel 之前,您需要准备以下内容: - 企业主页 – 在 [**Settings > Accounts > Pages**](https://business.facebook.com/latest/settings/pages) 中将其添加到您的商业主页组合 - Business Manager 账户 – 您必须拥有该商业主页组合的完全控制权 - 企业邮箱 – 在 [**Settings > Business info**](https://business.facebook.com/latest/settings/business_info) 中进行设置 - 广告账户 – 在 [**Settings > Accounts > Ad accounts**](https://business.facebook.com/latest/settings/ad_accounts) 中将其添加到您的商业主页组合 准备好后,创建一个像素: 1. 前往 [**Events Manager**](https://www.facebook.com/events_manager2),点击 **Connect data**。 2. 选择 **Web** 作为数据源类型。 3. 为你的数据集命名,然后点击 **Create**。 4. 对于 [Adapty 归因](adapty-user-acquisition),你无需完成像素的完整安装。因此,当系统询问集成方式时,直接点击设置窗口中的 **x** 即可,刷新页面后你的像素仍会出现在列表中。 5. 当你的数据集出现在列表中后,即可继续创建广告系列。 ## 第三步:创建广告系列 \{#step-3-create-campaign\} 在 Meta Ads Manager 中创建广告系列: 1. 前往 [Meta Ads Manager](https://adsmanager.facebook.com/adsmanager/manage)。在 **Campaign** 标签页中,点击 **Create**。 2. 选择 **Sales** 作为广告系列目标,然后点击 **Continue**。 3. 在 **Campaign name** 部分为您的广告系列命名。 4. 在 **Budget** 部分的 **Budget strategy** 中,选择预算控制方式: - **Campaign budget**:如果您不确定哪种方案效果最好,这是最简单的选项。选择此项后,Meta Ads 将自动识别表现最佳的广告,并为效果更好的广告组分配更多预算。 然后,选择你需要的预算类型:**Daily**(每日预算)或 **Lifetime**(总预算),并以你的货币输入限额。**Daily** 预算在你还在摸索阶段时更灵活,可以从小金额开始,随时调整。你也可以选择 **Schedule budget increase**,设置规则,按金额或百分比自动增加预算。 - **Ad set budget**:如果你想手动控制各受众群体获得的预算比例,请选择此选项。如果不太确定,可以选择 **Share some of your budget with other ad sets**,允许 Meta 在有利于广告效果时自动调整各广告组预算(幅度最高 20%)。 5. 在 **Campaign bid strategy** 中,根据你的目标选择最合适的选项: - **Highest volume(默认)**:最容易上手的选项。选择后,由 Meta 优化点击成本,在预算范围内取得最佳效果。 - **Cost per result goal**:如果你了解自己的基准数据,可以设定目标单次结果成本。 - **Bid cap**:设置你愿意出价的最高上限。 6. Adapty 支持全面的 [A/B 测试](ab-tests)。如有需要,你也可以在 Meta Ads 中启用 A/B 测试。关于 Meta Ads Manager 中 A/B 测试的更多内容,请参阅[此处](https://www.facebook.com/business/help/1159714227408868)。 7. 现在,是时候为你的广告系列添加第一个广告组了。点击 **Next** 继续。 ## 步骤 4:创建广告组 \{#step-4-create-ad-set\} 创建广告组的步骤如下: 1. 在 **Ad set name** 字段中输入广告组名称。 2. 在 **Conversion location** 下拉菜单中,选择 **Website**。 3. 在 **Performance goal** 字段中,如果你有落地页,请选择 **Maximize number of landing page views**;如果你使用智能链接将用户直接导向应用商店,请选择 **Maximize number of link clicks**。 4. 在 **Dataset** 字段中,选择你在[步骤 2](#step-2-add-meta-pixel) 中创建的数据集。 5. 选择一个 **Conversion event**。在我们的示例中,通常选择 **Purchase** 或 **Start trial**。如果看到提示说数据集暂无任何事件,不必担心——这只是说明你的数据集是新建的。 6. 如果在设置广告系列时选择了**Ad set budget**,请选择**Daily**(每日)或**Lifetime**(总预算),并输入对应金额。**Daily** 预算灵活性更高,适合初期探索——可以从小额预算起步,随时灵活调整。 设置广告组的开始日期,以及(如适用)结束日期。例如,如果你想在应用中推广某项促销活动,务必确保广告组的时间范围与该活动的时间保持一致。 7. 在 **Audience controls** 部分,配置受众设置: - **Location**:位置范围可宽可窄,按需设置。你可以在广告组中限制 **Locations**,以配合广告中针对特定地区的内容。 - **Minimum age**:选择看到你广告的用户的最小年龄。某些广告可能有法律要求。全球范围内最小年龄不能低于 18 岁,泰国不能低于 20 岁。 - **Language**:只有当目标语言不是所选国家/地区最常用的语言时,才需要设置 **Language**。例如,在美国无需选择 **English**,但如果你要定向居住在美国的西班牙语用户,则需要选择 **Spanish**。 8. 默认情况下,Meta 会自动找到与你广告最相关的小群体。不过,如果你添加目标受众建议,可以引导 Meta 找到你认为最可能有响应的人群。在 **Advantage+ audience** 部分,你可以调整以下设置: - **Age**:设置特定年龄范围,以便更好地匹配不同年龄段的用户。 - **Gender**:向所有用户展示广告,或按性别定向投放。 - **Detailed targeting**:此设置可让你对广告和/或应用的目标受众进行最精细的控制。你可以根据 **Demographics**(人口统计)、**Interests**(兴趣)或 **Behaviors**(行为)来划分群体。例如,根据你的应用定位,可以聚焦于特定职业人群、某音乐团体的粉丝、新生儿父母,或经常网购的用户。 :::note **Detailed targeting** 中的各项条件默认使用 **Or**(或)运算符。如果你希望使用 **And**(且)运算符组合条件,请点击 **Define further** 并选择新条件。 ::: 9. 在 **Placements** 部分,你可以选择广告的展示位置。默认选中 **Advantage+** 设置,Meta 会根据预期效果,将你的广告组预算分配到多个版位。如果你不确定在哪里投放广告,建议使用此选项。如果你想手动指定具体版位,请选择 **Manual placements** 并进行自定义配置。了解更多请点击[这里](https://www.facebook.com/business/help/965529646866485)。 10. **推荐**:按设备定向投放有助于优化广告支出。在 **Placements** 部分,点击 **Show more settings**。在 **Devices and operating system** 子部分,选择应纳入目标受众的设备、操作系统及系统版本。这样可以确保广告只展示给相关用户——例如,桌面端用户不会看到您的广告,使用您的应用不支持的旧系统版本的用户也会被排除在外。 11. 准备好后,点击 **Next** 继续。 ## 第五步:创建广告 \{#step-5-create-ads\} 在 Meta Ads Manager 中创建广告: 1. 在 **Ad name** 字段中为广告命名。 2. 在 **Identity** 部分,选择用于发布广告的 Facebook 主页。如果您的应用有独立的 Instagram 账号,并已在 [第 1 步](#step-1-create-meta-ads-manager-account) 中通过 Meta Business Suite 完成关联,请在 **Instagram account** 下拉菜单中选择该账号;否则请选择 **Use Facebook page**,这样 Instagram 广告将通过 Facebook 主页发布。 3. 在 **Ad setup** 中,选择广告的发布方式。投放应用广告时,建议选择 **Create ad**,这样帖子会将用户跳转到您的应用,而非 Facebook 主页。在 **Format** 字段中,根据素材数量和展示方式选择合适的选项。 4. 在 **Destination** 部分,将 **Main destination** 保持为 **Website**。在 **Website URL** 字段中,粘贴 `https://api-ua.adapty.io/api/v1/attribution/click`。在 [Adapty Attribution](adapty-user-acquisition) 中,[创建一个 Web 广告活动](ua-facebook),并将 **Click link** 的内容附加在 `https://api-ua.adapty.io/api/v1/attribution/click` 之后,粘贴到 **Tracking** 部分的 **URL parameters** 字段中。 5. 在 **Ad creative** 部分,点击 **Set up creative**,选择 **Image ad** 或 **Video ad**。此操作将打开一个新窗口,提示您上传媒体文件、裁剪图片并添加文案。 6. 如需自动翻译广告文案,请在 **Languages** 部分点击 **Add languages**。先添加主要语言——系统会自动从您的素材中提取文案;然后添加目标翻译语言以完成自动翻译。 7. 准备就绪后,点击 **Publish** 发布广告。 ## 下一步 \{#whats-next\} 如需激活广告,请先添加付款方式(如果尚未添加)。 之后,你可以[在 Adapty Attribution 看板中查看广告活动对应用收入的影响](adapty-user-acquisition)。 还没有使用 Adapty Attribution?[与我们预约通话](https://calendly.com/tnurutdinov-adapty/30min),了解它如何帮助你追踪和优化广告活动。 --- # File: tiktok-create-campaign --- --- title: "在 TikTok for Business 中推广您的应用" --- 本分步指南将帮助您了解如何在 TikTok for Business 中为您的应用创建并设置广告,以便轻松优化广告并追踪其效果。 ## 第一步:添加商业信息 \{#step-1-add-business-info\} 如果您刚开始使用 TikTok for Business,需要先添加您的商业信息: 1. 前往 [https://ads.tiktok.com](https://ads.tiktok.com/business/) 并点击 **Get started**。 2. 使用您的电子邮件或 TikTok 账户注册。 3. 输入您的商业信息,并按照屏幕上的提示操作。 商业账户审核通过后,您将被重定向至创建首个广告系列的页面。 ## 第二步:创建像素 \{#step-2-create-a-pixel\} 您需要 TikTok 像素才能将广告系列数据与收入关联,从而获得更好的效果: 1. 前往 [**Events Manager**](https://ads.tiktok.com/i18n/events_manager/home),点击 **Connect data source**。 2. 选择 **Web** 作为数据来源类型。 3. 在 **Add your website** 窗口中,点击 **Skip**。 4. 选择 **Manual setup**,然后点击 **Next**。 5. 选择 **TikTok pixel + Events API**,然后点击 **Next**。 6. 为您的像素命名,然后点击 **Create**。 7. 对于 [Adapty 归因](adapty-user-acquisition),您不需要完成像素的完整安装,直接关闭设置窗口即可,您的像素会出现在列表中。 8. 要使此像素可在广告系列中使用,您需要通过 [Adapty 归因](adapty-user-acquisition) 向其发送一个测试事件: 1. [创建新的 TikTok 广告系列](ua-tiktok)。 2. 展开特定平台的板块,例如 iOS。 3. 从下拉菜单中选择一个像素。 4. 点击 **Send test event**。 5. 在下拉菜单中,选择您将用于广告优化的事件。 6. 在 TikTok for Business 中,打开您的像素并切换到 **Test events** 标签页,复制 `test_event_code`。 7. 将其粘贴到 Adapty 的 **Test event code** 字段中,然后点击 **Send**。 9. 测试事件将在几分钟内出现在 TikTok 中。当您在像素详情中看到它后,即可继续在 TikTok Ads Manager 中完成广告系列设置。 ## 第三步:选择广告系列目标 \{#step-3-select-the-campaign-objective\} :::important 本教程使用 TikTok Ads Manager 中的快速设置视图。部分推荐设置仅在完整视图中显示,我们会在相关步骤中加以说明。 ::: 前往 Ads Manager 中的[广告创建页面](https://ads.tiktok.com/i18n/nb_creation/create/objectives)。 在第一个页面上,选择广告目标并点击 **Continue**。 选择 **Sales > Website conversion**。 ## 第四步:填写活动信息 \{#step-4-fill-in-the-campaign-info\} 接下来,填写活动信息: 1. 在 **Campaign name** 字段中为你的广告系列命名。 2. 在 **Optimization goal** 字段中,选择 **Conversion**。 3. 从下拉菜单中选择你的有效像素,并选择一个 **Optimization event**。请注意,只有处于激活状态的事件才可供选择。如果你需要的事件不可用,请按照[第 2 步](#step-2-create-a-pixel)中的说明发送测试事件。 4. 你的广告将显示在 TikTok 信息流和搜索结果中。如需进行更多设置,点击 **Advanced settings**。在 **Placements** 中,配置版位设置: - **User comment**:如果你希望广告也显示在评论区,请勾选此项。TikTok 建议开启用户评论,以帮助广告获得更多曝光。 - **Allow video download**:允许观众下载你的广告。 - **Allow video sharing**:允许观众分享你的广告。 5. 点击 **Continue**。 ## 步骤 5:添加广告素材 \{#step-5-add-ad-content\} 现在,来设置你的广告素材和目标 URL: 1. 在 **TikTok account** 字段中,选择用于发布广告的账户。 2. 在 [Adapty Attribution](adapty-user-acquisition) 中,[创建网络营销活动](ua-tiktok),并将 **Click link** 粘贴到 **Destination URL** 字段中。 3. 在 **Creatives** 部分,点击 **+ Videos and images**。 4. 如果您想使用 TikTok 帖子作为素材,请在 **TikTok post** 标签页中选择。否则,切换到 **Creative library** 标签页,点击 **Upload**。上传的文件将在此标签页中保留,方便您在其他营销活动中复用。 5. 裁剪素材以适配 TikTok 格式,并选择以单图/视频广告还是轮播广告的形式投放。 6. 展开已上传的素材,点击 **No music selected** 旁边的 **+**,可在此上传您自己的 mp3 文件。添加音乐为必选项。 7. 在 **Add text** 字段中,输入将用作描述的文案。 8. 如需将此广告发布到您的 TikTok 账户,请选择 **Place the ads on this TikTok account as a post**。 9. 在 **Call to action** 字段中,选择或移除与您的广告相关的行动号召选项,TikTok 会自动将其附加到广告中。 10. 点击 **Continue**。 ## 步骤 6:配置定向与预算 \{#step-6-configure-targeting-and-budget\} 最后,设置广告的目标受众及投放预算: 1. 在 **Targeting** 部分,选择 **Automatic** 或 **Custom**。如果你还不太了解自己的受众,**Automatic** 是最简单的选项。如果选择 **Custom**,则可以通过筛选更可能响应广告的用户群来优化投放成本。 2. 如果选择了 **Custom**,请配置以下内容: - **Location**:默认位置为广告账户所在地。如果选择多个目标国家或地区,广告审核结果将分别针对每个地区返回。实际广告投放效果也可能因不同版位所支持的地区而有所差异。 - **Languages**:默认选择所有语言。请根据所选地区最常用的语言来选择目标语言。 - **Gender**:默认选择所有性别。 :::tip 切换到 Full 模式后,你会在 **Targeting** 下看到额外的 **Device** 部分,可以按设备类型、操作系统及系统版本限制受众——如果你的应用有最低版本要求,这个功能会很有用。 ::: 3. 在 **Budget** 部分,选择系统建议的选项之一,或选择 **Custom**。 4. 如果选择了 **Custom**,请选择 **Daily**(每日)或 **Lifetime**(总)预算类型,并以你的货币输入限额。在摸索阶段,**Daily** 预算的灵活性更高,可以从较小金额起步,随时按需调整。 5. 在 **Schedule** 部分,选择 **Continue for at least 7 days** 或 **Custom**。如果广告有时效性,建议选择 **Custom** 排期,以免错过需要停止投放的时机。 6. 如果选择了 **Custom**,请设置广告的开始时间,或同时设置开始和结束时间。请注意,系统将使用你账户的时区。 7. 点击 **Publish**。 完成后,系统会创建一个包含一个广告组的新广告系列。如果你配置了轮播图,该广告组将包含一条广告;如果你将创意添加为独立广告,则会包含多条广告。 ## 第七步:输入付款信息 \{#step-7-enter-payment-details\} 要开始投放广告,在您配置定向和广告预算后,请输入您的付款信息。完成后,您就一切准备就绪了! ## 下一步 \{#whats-next\} 现在,你可以[在 Adapty 归因看板中查看营销活动对应用收入的影响](adapty-user-acquisition)。 还没有使用 Adapty 归因功能?[预约与我们通话](https://calendly.com/tnurutdinov-adapty/30min),了解它如何帮助你追踪和优化广告投放效果。 --- # File: getting-started-with-server-side-api --- --- title: "服务端 API" description: "快速上手 Adapty 服务端 API,管理订阅。" --- :::tip 在使用 AI 编程助手?请查看[从后端检查并授予订阅访问权限](server-side-api-with-ai),一页搞定完整流程。 ::: 通过该 API,你可以: 1. 查看用户的订阅状态。 2. 通过访问等级激活用户的订阅。 3. 获取用户属性。 4. 设置用户属性。 5. 获取并更新付费墙配置。 <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note 如需追踪订阅事件,请在 Adapty 中使用 [Webhook](webhook) 集成,或直接与您现有的服务进行集成。 ::: ## 案例一:同步网页端与移动端的订阅用户 \{#case-1-sync-subscribers-between-web-and-mobile\} 如果你使用 Stripe、ChargeBee 或其他网页支付服务商,可以轻松同步订阅用户。操作步骤如下: 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users)、[Unity](unity-identifying-users)</InlineTooltip>。 2. 通过 API [查询用户订阅状态](api-adapty/operations/getProfile)。 3. 如果用户处于免费增值计划,在你的网站上展示付费墙。 4. 支付成功后,通过 API 在 Adapty 中[更新订阅状态](api-adapty/operations/setTransaction)。 5. 订阅用户的状态将自动与移动端 App 保持同步。 ## 案例 2:授予订阅 \{#case-2-grant-a-subscription\} :::note 出于安全原因,您无法通过移动端 SDK 授予订阅。 ::: 如果您通过自己的线上商店、Amazon Appstore、Microsoft Store 或 Google Play 和 App Store 以外的其他平台进行销售,则需要将这些交易同步到 Adapty,以便授予用户访问权限并在分析中追踪该交易。 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. [在 Adapty 看板中为你的产品设置自定义商店](custom-store)。 3. 使用 [Set transaction](api-adapty/operations/setTransaction) API 请求将交易同步到 Adapty。 ## 情境 3:授予访问等级 \{#case-3-grant-an-access-level\} 假设你正在运行一个提供 7 天免费试用的促销活动,并希望跨平台保持一致的用户体验。要与移动应用同步,请按以下步骤操作: 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. 使用 API [授予高级访问等级](api-adapty/operations/grantAccessLevel),有效期 7 天。 7 天后,未订阅的用户将被降级至免费套餐。 ## 情形 4:同步用户属性和自定义特性 \{#case-4-sync-users-properties-and-custom-attributes\} 如果你的用户有自定义特性(例如语言学习应用中用户已学习的单词数量),同样可以进行同步。 1. <InlineTooltip tooltip="为每位用户分配唯一 ID">[iOS](identifying-users)、[Android](android-identifying-users)、[React Native](react-native-identifying-users)、[Flutter](flutter-identifying-users) 和 [Unity](unity-identifying-users)</InlineTooltip>。 2. 通过 API 或 SDK [更新属性](api-adapty/operations/updateProfile)。 这些自定义属性可用于创建市场细分和运行 A/B 测试。 ## 案例 5:管理付费墙配置 \{#case-5-manage-paywall-configurations\} 你可以[更新付费墙中的远程配置](api-adapty/operations/updatePaywall),无需重新发布应用即可动态调整付费墙的外观和行为。 --- **下一步:** - 继续了解[服务端 API 授权](ss-authorization) - 请求: - [获取用户画像](api-adapty/operations/getProfile) - [创建用户画像](api-adapty/operations/createProfile) - [更新用户画像](api-adapty/operations/updateProfile) - [删除用户画像](api-adapty/operations/deleteProfile) - [授予访问等级](api-adapty/operations/grantAccessLevel) - [撤销访问等级](api-adapty/operations/revokeAccessLevel) - [设置交易](api-adapty/operations/setTransaction) - [验证购买、向用户授予访问等级并导入其交易历史](api-adapty/operations/validateStripePurchase) - [添加集成标识符](api-adapty/operations/setIntegrationIdentifiers) - [获取付费墙](api-adapty/operations/getPaywall) - [列出付费墙](api-adapty/operations/listPaywalls) - [更新付费墙](api-adapty/operations/updatePaywall) --- # File: onboardings --- --- title: "用户引导" --- :::warning 无代码用户引导编辑工具功能完整,但 Adapty 已不再为其添加新功能或发布更新。对于新项目,建议使用 [Adapty Flow Builder](adapty-flow-builder) —— 一款可视化无代码编辑工具,用于构建单屏付费墙和多屏用户引导流程,并在设备上原生渲染: - **任意流程类型**:构建单屏付费墙、包含付费墙的多步骤用户引导,以及介于两者之间的任意形式。 - **原生渲染**:流程通过 Adapty SDK 渲染,无需 Web 视图。 - **无需重新发布即可更新**:随时修改文案、设计或逻辑,更新无需发布新版本即可触达用户。 ::: Adapty 的用户引导功能让非技术团队无需编写代码即可构建用户引导流程。无代码编辑工具可创建一系列向用户介绍应用的页面。你可以通过互动问题和变量对页面进行个性化定制,并运行 A/B 测试以找到效果最佳的流程。 用户引导适用于使用 Adapty SDK v3.8.0+(iOS、Android、React Native、Flutter)、v3.14.0+(Unity)或 v3.15.0+(Kotlin Multiplatform、Capacitor)的应用。 ## 使用方式 \{#how-it-works\} 1. [在无代码编辑工具中设计用户引导。](design-onboarding) 2. [为用户引导创建版位。](create-onboarding#step-2-create-a-placement-for-your-onboarding) 3. 使用 Adapty SDK 将用户引导集成到你的项目中: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) 4. 测试用户引导并向用户发布。 --- # File: create-onboarding --- --- title: "创建用户引导" --- [用户引导](onboardings)向新用户介绍您的移动应用的价值、功能和使用技巧。 ## 第一步:创建用户引导 \{#step-1-create-an-onboarding\} 在 Adapty 看板中创建新的用户引导: 1. 从 Adapty 主菜单进入 **Onboardings** 页面。该页面展示了你已设置的所有用户引导及其数据图表。点击 **Create onboarding**。 <img src="/assets/shared/img/create-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 为您的用户引导填写一个描述性名称,然后点击 **Proceed to build onboarding**。 <img src="/assets/shared/img/create-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 您将被跳转到用户引导编辑工具。 它包含一个默认演示模板,你可以通过研究该模板了解用户引导如何收集数据,以及如何使用变量和测验对其进行个性化定制。你可以随意删除不需要的屏幕,并在此[设计你自己的用户引导体验](design-onboarding)。 <img src="/assets/shared/img/create-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 准备就绪后,点击右上角的 **Preview** 按钮。亲自完成用户引导流程,确保一切正常运行。 <img src="/assets/shared/img/create-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 如果一切正常,点击右上角的 **Publish**。请等待发布完成后再返回 Adapty,否则您的进度将会丢失。 :::danger 如果不点击 **Publish**,SDK 将无法获取你创建的用户引导。 ::: <img src="/assets/shared/img/create-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 发布用户引导后,点击 **Back to Adapty**。你的用户引导已创建完成,接下来可以将其添加到版位中开始使用。 ## 第二步:为用户引导创建版位 \{#step-2-create-a-placement-for-your-onboarding\} 1. 从主菜单进入 **Placements**,切换到 **Onboardings** 标签,点击 **Create placement**。 <img src="/assets/shared/img/create-onboarding6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 输入版位名称和 ID,然后点击 **Run onboarding**,选择要向所有用户展示的用户引导。 3. 如果你为特定用户群体准备了单独的用户引导,请[添加更多目标受众](audience),并为其选择不同的用户引导。 ## 第三步:将用户引导集成到您的应用中 \{#step-3-integrate-the-onboarding-into-your-app\} :::important 用户引导功能适用于使用 Adapty SDK v3.8.0+(iOS、Android、React Native、Flutter)、v3.14.0+(Unity)或 v3.15.0+(Kotlin Multiplatform、Capacitor)的应用。 ::: 要在您的应用中展示用户引导,请使用 Adapty SDK 进行集成: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) 为了了解哪个用户引导效果更好,你也可以运行 [A/B 测试](ab-tests)。 --- # File: design-onboarding --- --- title: "设计用户引导" description: "创建有意义的用户引导。" --- 这款无代码移动应用用户引导编辑工具功能强大、高度可定制,可帮助您为用户提供最佳的用户引导体验。即使您不是开发者或设计师,也能获得出色的效果。 ## 用户引导页面 \{#onboarding-screens\} 用户引导流程由多个页面组成,你可以自由添加和设计这些页面。 用户点击按钮即可在页面之间跳转。 :::tip 如果某些用户需要稍有不同的流程(例如,在健身应用中,你可能希望根据用户性别展示不同的"目标"图片),不必单独创建多个用户引导。 你可以将部分页面默认设为隐藏,仅在特定场景下显示。 ::: ## 用户引导元素 \{#onboarding-elements\} 用户引导元素按显示顺序列在左侧。点击右上角的 **Add** 按钮可添加新元素。 可添加的元素分为以下几组: - **容器**:容器可让您灵活布局。例如,如果想添加两列文本,需要先添加 **Columns**,然后在左侧面板将两个文本块拖入 **Columns** 中。如果要添加轮播图,则需要在 **Media** 元素内添加图片。 - **排版**:添加预格式化文本块,并按需调整其样式。 - **媒体与展示**:除图片和视频外,您还可以添加动态数据图表,直观展示应用价值,吸引用户购买。 支持的**视频格式**为 MP4 和 WebM。**媒体文件大小上限**为 15 MB。 如果您想添加不支持的动画元素(例如 Lottie),可以将其转换为视频(例如使用[此工具](https://www.lottielab.com/lottie/lottie-to-video)),然后以视频形式嵌入。 - **Quiz**:创建包含文字和图片选项的简短问卷,让用户引导体验更加个性化,同时深入了解您的用户。 - **Inputs**:收集用户数据。 - **Buttons**:按钮让用户可以在页面之间导航、关闭用户引导或跳转到付费墙。您还可以添加光泽或动态按钮,吸引用户注意力,将安装转化为购买。 - **Loaders**:动画加载器在流程进行中保持用户的参与度。 - **User engagement**:添加用户评价、用户邮件列表和倒计时。 :::note 作为 **Media & Display** 分组的一部分,如果现有的自定义选项不够用,你也可以添加自定义 HTML 代码。 不过,自定义 HTML 元素既不会预加载也不会被缓存,因此建议仅将 **Raw HTML** 用于小型、轻量的元素。 ::: <img src="/assets/shared/img/design-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 元素 ID 与动作 ID \{#element-id-and-action-id\} 如果你想让按钮执行自定义操作,请为其指定一个**动作 ID**,然后在源代码中使用它。动作 ID 让你可以用同一种方式处理具有相同动作 ID 的不同按钮。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 如果您需要处理用户在某个字段中的输入内容(例如保存其年龄或邮箱),请为该字段分配一个**元素 ID**,然后在源代码中使用它将问题与答案关联起来。元素 ID 在同一个用户引导中只能使用一次。 <img src="/assets/shared/img/design-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 自定义选项 \{#customization-options\} 编辑工具中提供以下自定义选项: - **Styles** 标签页:调整元素的外观样式。 <img src="/assets/shared/img/design-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Element** 标签页:设置元素的属性,例如可见性、按钮点击动作,以及其他与外观无关的属性。 <img src="/assets/shared/img/design-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Screen** 标签页:配置屏幕的全局设置,例如页眉或页面计数器的显示方式。 <img src="/assets/shared/img/design-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 复制屏幕和元素 \{#copy-screens-and-elements\} 如果您已创建了一个用户引导并希望复用其中的部分内容,或者想进行细微修改并运行 A/B 测试,可以将一个或多个屏幕从一个用户引导复制到另一个。 要复制屏幕,请打开用户引导编辑工具,然后执行以下任一操作: - 右键单击单个屏幕并选择 **Copy** - 选中所需屏幕并按 `Ctrl+C`(Windows)或 `⌘+C`(Mac) 您还可以复制单个元素或文本块,可以在同一用户引导内复制,也可以在不同用户引导之间复制。 ## 从网页转应用漏斗复制屏幕 \{#copy-screens-from-web-to-app-funnels\} 如果你在 [FunnelFox](https://funnelfox.com/) 中创建了网页转应用漏斗,并希望在用户引导中使用漏斗里的屏幕,可以直接在漏斗编辑器中复制屏幕,然后粘贴到用户引导编辑器中: 1. 在 FunnelFox 漏斗编辑器中,右键单击某个屏幕并选择 **Copy**,或选中该屏幕后按 `Ctrl+C`/`⌘+C`。 2. 打开用户引导编辑器。 3. 右键单击要插入复制屏幕的位置,选择 **Paste**,或选中该屏幕后按 `Ctrl+V`/`⌘+V`。复制的屏幕将插入到所选屏幕的下方。 <img src="/assets/shared/img/funnel-to-onboarding.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: adapty-paywall-builder --- --- title: "Adapty 付费墙编辑工具(旧版)" description: "使用可视化无代码编辑工具创建付费墙和用户引导流程。" --- :::warning 付费墙编辑工具仍可正常使用,但 Adapty 已停止为其添加新功能或发布更新。对于新项目,建议使用 [Adapty Flow Builder](adapty-flow-builder) —— 一款可视化无代码编辑器,支持单屏付费墙和多屏用户引导流程,并可在设备上原生渲染: - **任意流程类型**:可构建单屏付费墙、包含付费墙的多步骤用户引导,以及介于两者之间的任何形式。 - **原生渲染**:流程通过 Adapty SDK 渲染,无需 Web 视图。 - **无需重新发布即可更新**:随时修改文案、设计或逻辑,更新无需发布新版本即可触达用户。 ::: Adapty **付费墙编辑工具**是一款可视化无代码工具,专为设计自定义付费墙而生。你可以从模板出发,自定义布局,并添加轮播图、卡片、产品列表、页脚等元素。该工具还支持自定义字体、产品标签和本地化。 付费墙编辑工具需要 Adapty SDK v3.0 或更高版本。设计好付费墙后,[将其添加到版位](add-audience-paywall-ab-test)并在应用中展示: - [iOS](ios-quickstart-paywalls) - [Android](android-quickstart-paywalls) - [React Native](react-native-quickstart-paywalls) - [Flutter](flutter-quickstart-paywalls) - [Unity](unity-quickstart-paywalls) - [Capacitor](capacitor-quickstart-paywalls) - [Kotlin Multiplatform](kmp-quickstart-paywalls) --- # File: flutterflow --- --- title: "Adapty FlutterFlow 插件" description: "将 FlutterFlow 与 Adapty 集成,实现更强大的订阅管理功能。" --- Adapty 是一个多功能平台,专为帮助移动应用实现增长而设计。无论您是刚刚起步还是已拥有数千名用户,Adapty 都能让您节省数月的应用内购买集成时间,并通过付费墙管理将订阅收入翻倍。 FlutterFlow 的 Adapty 插件让您无需编写任何代码即可使用 Adapty 的全部功能。您可以在 FlutterFlow 中设计付费墙页面,为其启用购买功能,然后远程控制页面上展示的产品,包括针对特定用户群体进行定向推送或开展 A/B 测试。应用发布后,您可以在我们的看板中立即查看客户购买行为的详细分析数据。 想要更新付费墙上的可用产品?非常简单!只需在 Adapty 看板中点击几下即可完成修改,您的客户将立即看到新产品——无需发布新的应用版本! Adapty 还为您提供以下功能: - **订阅与应用内购买**:Adapty 为您处理服务端收据验证,并在所有平台(包括 Web)之间同步您的客户数据。 - **付费墙 A/B 测试**:测试不同的价格、时长、试用期和视觉元素,以优化您的订阅和一次性购买方案。 - **强大的数据分析**:访问详细的数据图表,更好地了解并提升应用的变现效果。 - **集成能力**:Adapty 可与 Amplitude、AppsFlyer、Adjust、Branch、Mixpanel、Facebook Ads、AppMetrica、自定义 Webhook 等第三方分析工具无缝连接。 --- # File: ff-getting-started --- --- title: "快速入门" description: "通过 Adapty 功能标志开始个性化订阅流程。" --- 通过 Adapty,您可以在移动应用用户旅程的不同节点(例如用户引导、设置等)创建并运行付费墙和 A/B 测试。这些节点称为[版位](placements)。应用中的一个版位可以同时管理多个付费墙或 [A/B 测试](ab-tests),每个付费墙或测试面向特定的用户群体,我们称之为[目标受众](audience)。此外,您还可以对付费墙进行实验,在不发布新版本的情况下随时替换付费墙。唯一需要硬编码到移动应用中的是版位 ID。 <img src="/assets/shared/img/audience.jpg" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 库会根据 Adapty 看板中的最新产品持续更新您的付费墙。它会[获取产品数据](ff-action-flow)并[在付费墙上展示](ff-add-variables-to-paywalls),[处理购买](ff-make-purchase),以及[检查用户的访问等级](ff-check-subscription-status)以确定是否向其开放付费内容。 要开始使用,只需按照以下步骤将 [Adapty 库添加](ff-getting-started#add-the-adapty-library-as-a-dependency)到您的 FlutterFlow 项目中,并[初始化它](ff-getting-started#initiate-adapty-plugin)。 :::warning 开始之前,请注意以下限制: - 适用于 FlutterFlow 的 Adapty 库不支持 Web 应用。请避免使用它编译 Web 应用。 - 适用于 FlutterFlow 的 Adapty 库不支持通过 Adapty 付费墙编辑工具创建的付费墙。您需要在 FlutterFlow 中自行设计付费墙,然后再通过 Adapty 启用购买功能。 ::: ## 将 Adapty 库添加为依赖项 \{#add-the-adapty-library-as-a-dependency\} 1. 在 [FlutterFlow Dashboard](https://app.flutterflow.io/dashboard) 中,打开您的项目,然后从左侧菜单点击 **Settings and Integrations**。在左侧的 **Project setup** 部分,选择 **Project dependencies**。 <img src="/assets/shared/img/main_settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **FlutterFlow Libraries** 部分,点击 **Add Library** 并输入 `adapty-xtuel0`。点击 **Add**。 3. 现在,您需要将 SDK 密钥与库关联。点击库旁边的 **View details**。 <img src="/assets/shared/img/ff_view_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 从 Adapty 看板的 [**App Settings** -> **General** 标签页](https://app.adapty.io/settings/general)复制 **Public SDK key**。 <img src="/assets/shared/FF_img/adaptyapikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 将密钥粘贴到 FlutterFlow 中的 **AdaptyApiKey** 字段。 <img src="/assets/shared/img/ff_apikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty FF 库现在将作为依赖项添加到您的项目中。在 **Adapty** FF 库窗口中,您将找到已导入项目的所有 Adapty 资源。 ## 在应用启动时调用新的激活操作 \{#call-the-new-activation-action-at-application-launch\} 1. 从左侧菜单进入 **Custom Code** 部分,打开 `main.dart`。 <img src="/assets/shared/img/ff_dartmain.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击 **+** 并选择 `activate (Adapty)`。 <img src="/assets/shared/img/ff_activate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Save**。 ## 初始化 Adapty 插件 \{#initiate-adapty-plugin\} 为了让 Adapty 看板识别您的应用,您需要在 FlutterFlow 中提供一个特殊密钥。 1. 在您的 FlutterFlow 项目中,从左侧菜单进入 **Settings and Integrations > Permissions**。 2. 在打开的 **Permissions** 窗口中,点击 **Add Permission** 按钮。 3. 在 **iOS Permission Key** 和 **Android Permission Key** 字段中,均粘贴 `AdaptyPublicSdkKey`。 4. 对于 **Permission Message**,从 Adapty 看板的 [**App Settings** -> **General** 标签页](https://app.adapty.io/settings/general)复制 **Public SDK key**。每个应用都有其专属的 SDK 密钥,如果您有多个应用,请确保获取正确的密钥。 <img src="/assets/shared/img/ff_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 完成以上步骤后,您将能够在 FlutterFlow 应用中调用付费墙,并通过它启用购买功能。 ## 下一步? \{#whats-next\} 1. [创建操作流](ff-action-flow),用于在 FlutterFlow 中处理 Adapty 付费墙产品及其数据。 2. [将获取到的数据映射到付费墙](ff-add-variables-to-paywalls),即您在 FlutterFlow 中设计的付费墙。 3. [设置购买按钮](ff-make-purchase),使其在点击时通过 Adapty 处理交易。 4. 最后,[添加订阅状态检查](ff-check-subscription-status),以确定是否向用户展示付费内容。 --- # File: ff-action-flow --- --- title: "步骤 1. 创建展示付费墙数据的流程" description: "在 Adapty 中设置功能标志操作流程,以个性化用户订阅旅程。" --- :::important 使用 FlutterFlow 插件时,您无法使用在 Adapty 付费墙编辑工具中创建的付费墙。您必须在 FlutterFlow 中自行实现付费墙页面,并将其连接到 Adapty。 ::: 将 Adapty 库作为依赖项添加到您的 FlutterFlow 项目后,接下来需要构建一个流程,用于**从 Adapty 获取付费墙和产品数据,并将其展示在您在 FlutterFlow 中设计的付费墙上**。 首先,我们需要从 Adapty 接收付费墙数据。我们将从请求 Adapty 付费墙开始,然后获取其关联产品,最后检查数据是否成功接收。如果成功,我们将在付费墙页面上显示产品标题和价格;否则,将显示错误消息。 在继续之前,请确保您已完成以下操作: 1. 在 Adapty 看板中[创建至少一个付费墙并向其添加至少一个产品](create-paywall)。 2. 在 Adapty 看板中[创建至少一个版位](create-placement),并[将您的付费墙添加到该版位](add-audience-paywall-ab-test)。 让我们开始吧! ## 步骤 1.1. 请求 Adapty 付费墙 \{#step-11-request-adapty-paywall\} 如前所述,要在您的 FlutterFlow 付费墙中显示数据,我们首先需要从 Adapty 获取数据。第一步是获取 Adapty 付费墙本身。操作如下: 1. 打开您的付费墙屏幕,在右侧面板切换到 **Actions** 部分,然后打开 **Action Flow Editor**。 <img src="/assets/shared/img/ff_action_flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **Select Action Trigger** 窗口中,选择 **On Page Load**。 <img src="/assets/shared/img/ff_action_trigger.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Add Action**,然后搜索 `getPaywall` 自定义操作并选择它。 <img src="/assets/shared/img/ff_getpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 在 **Set Actions Arguments** 部分,输入您在 Adapty 看板中[创建的版位](create-placement)的真实 ID,该版位包含付费墙。在本示例中为 `monthly`。请务必使用您真实的版位 ID! <img src="/assets/shared/img/ff_placementid.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 如果您已在 Adapty 看板中对付费墙进行了[本地化](localizations-and-locale-codes),还可以设置 **locale** 参数。 6. 在 **Action Output Variable Name** 中,创建一个新变量并将其命名为 `getPaywallResult`。我们将在下一步中使用它来引用 Adapty 付费墙并请求其产品。 <img src="/assets/shared/img/ff_getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 1.2. 请求 Adapty 付费墙产品 \{#step-12-request-adapty-paywall-products\} 很好!我们已经获取了 Adapty 付费墙。现在,让我们获取与该付费墙关联的产品: 1. 点击已创建操作下方的 **+** 并选择 **Add Action**。此操作将接收 Adapty 付费墙产品。为此,搜索并选择 `getPaywallProducts`。 2. 在 **Set Actions Arguments** 部分,选择之前创建的 `getPaywallResult` 变量。 <img src="/assets/shared/img/ff_getpaywallproduct.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 按如下方式填写其他字段: - **Available Options**:Data Structured Field - **Select Field**:value - **Available Options**:无需进一步更改 <img src="/assets/shared/img/ff_getpaywallresult2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Confirm**。 5. 在 **Action Output Variable Name** 中,创建一个新变量并将其命名为 `getPaywallProductsResult`。我们将使用它将您在 FlutterFlow 中设计的付费墙与 Adapty 付费墙数据进行映射。 <img src="/assets/shared/img/ff_getpaywallproductsresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 1.3. 添加检查付费墙是否成功加载 \{#step-13-add-check-if-the-paywall-uploaded-successfully\} 在继续之前,让我们验证 Adapty 付费墙是否已成功接收。如果是,我们可以用产品数据更新付费墙;如果不是,我们将处理错误。以下是添加检查的方法: 1. 点击 **+** 并点击 **Add Conditional**。 <img src="/assets/shared/img/ff-add-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 在 **Action Output** 部分,选择之前创建的操作输出变量(在本示例中为 `getPaywallResult`)。 <img src="/assets/shared/img/ff-getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 要验证 Adapty 付费墙是否已接收,请检查是否存在包含值的字段。按如下方式填写字段: - **Available Options**:Has Field - **Field (AdaptyGetPaywallResult)**:value 4. 点击 **Confirm** 以完成条件设置。 ## 步骤 1.4. 记录付费墙查看事件 \{#step-14-log-the-paywall-review\} 为确保 Adapty 分析能够追踪付费墙查看事件,我们需要记录此事件。如果没有此步骤,该查看将不会被计入分析数据。操作如下: 1. 点击 **TRUE** 标签下方的 **+** 并点击 **Add Action**。 2. 在 **Select Action** 字段中,搜索并选择 **logShowPaywall**。 <img src="/assets/shared/img/ff-logshowpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Set Action Arguments** 区域点击 **Value**,然后选择我们创建的 `getPaywallResult` 变量。该变量包含付费墙数据。 4. 按如下方式填写字段: - **Available Options**:Data Structured Field - **Select Field**:value 5. 点击 **Confirm**。 <img src="/assets/shared/img/ff-lohsgowpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 1.5. 如果未收到付费墙则显示错误 \{#step-15-show-error-if-paywall-not-received\} 如果未收到 Adapty 付费墙,您需要[处理错误](error-handling-on-flutter-react-native-unity#system-storekit-codes)。在本示例中,我们将简单地显示一条警告消息。 1. 向 **FALSE** 标签添加一个 **Informational Dialog** 操作。 2. 在 **Title** 字段中,添加您希望作为对话框标题显示的文本。在本示例中为 **Error**。 3. 点击 **Message** 框中的 **Value**。 4. 按如下方式填写字段: - **Set Variable**:我们创建的 `getPaywallProductResult` 变量 - **Available Options**:Data Structure Field - **Select Field**:error - **Available Options**:Data Structure Field - **Select Field**:errorMessage <img src="/assets/shared/img/ff-error.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Confirm**。 6. 向 **FALSE** 流程添加一个 **Terminate action**。 <img src="/assets/shared/img/ff-terminate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击右上角的 **Close**。 恭喜!您已成功接收产品数据。现在,让我们[将其映射到您在 FlutterFlow 中设计的付费墙](ff-add-variables-to-paywalls)。 --- # File: ff-add-variables-to-paywalls --- --- title: "步骤 2. 向付费墙页面添加数据" description: "将 Feature Flag 变量添加到 Adapty 的付费墙中。" --- 在[获取所有必要的产品数据](ff-action-flow)之后,是时候将其映射到您在 FlutterFlow 中设计的精美付费墙上了。在本示例中,我们将映射产品标题及其价格。 ## 步骤 2.1. 向付费墙页面添加产品标题 \{#step-21-add-product-title-to-paywall-page\} 1. 双击付费墙页面上的产品文本。在 **Set from Variable** 窗口中,搜索 `getPaywallProductResult` 变量并选择它。 <img src="/assets/shared/img/ff-paywall-text.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 按如下方式填写字段: - **Available Options**:Data Structured Field - **Select Field**:value - **Available Options**:Item at Index - **List Index Options**:First - **Available Options**:Data Structured Field - **Select Field**:localizedTitle - **Default Variable Value**:null - **UI Builder Display Value**:任意内容,本示例中为 `product.title` <img src="/assets/shared/img/ff-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 点击 **Confirm** 保存更改。 ## 步骤 2.2. 向付费墙页面添加价格文本 \{#step-22-add-price-text-to-paywall-page\} 按照步骤 2.1 中的操作,对价格文本重复以下步骤: 1. 双击付费墙页面上的价格文本。在 **Set from Variable** 窗口中,搜索 `getPaywallProductResult` 变量并选择它。 <img src="/assets/shared/img/ff-price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 按如下方式填写字段: - **Available Options**:Data Structured Field - **Select Field**:value - **Available Options**:Item at Index - **List Index Options**:First - **Available Options**:Data Structured Field - **Select Field**:price - **Default Variable Value**:null - **UI Builder Display Value**:任意内容,本示例中为 `product.price` 3. 点击 **Confirm** 按钮保存更改。 ### 向付费墙页面添加本地货币价格 \{#add-price-in-local-currency-to-paywall-page\} 1. 双击付费墙页面上的价格。在 **Set from Variable** 窗口中,搜索 `getPaywallProductResult` 变量并选择它。 2. 按如下方式填写字段: - **Available Options**:Data Structured Field - **Select Field**:value - **Available Options**:Item at Index - **List Index Options**:First - **Available Options**:Data Structured Field - **Select Field**:price - **Available Options**:Data Structured Field - **Select Field**:amount - **Available Options**:Decimal - **Decimal Type**:Automatic - **Default Variable Value**:null - **UI Builder Display Value**:任意内容,本示例中为 `price.amount` 3. 点击 **Confirm** 保存更改。 大功告成!现在,当您启动应用时,它将直接在付费墙页面上展示来自 Adapty 付费墙的产品数据! 接下来,是时候[让用户购买该产品](ff-make-purchase)了。 --- # File: ff-make-purchase --- --- title: "步骤 3. 启用购买" description: "了解如何使用 Adapty 的功能标志系统进行购买。" --- 恭喜!您已成功[设置付费墙以显示来自 Adapty 的产品数据](ff-add-variables-to-paywalls),包括产品标题和价格。 现在,让我们继续最后一步——让用户通过付费墙进行购买。 ## 步骤 3.1. 启用用户进行购买 \{#step-31-enable-users-to-make-purchases\} 1. 双击付费墙页面上的购买按钮。在右侧面板中,打开 **Actions** 部分(如果尚未打开)。 2. 打开 **Action Flow Editor**。 <img src="/assets/shared/img/ff-action-flow-editor.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Select Action Trigger** 窗口中,选择 **On Tap**。 4. 在 **No Actions Created** 窗口中,点击 **Add Action**。搜索 `makePurchase` 动作并选择它。 <img src="/assets/shared/img/ff-makepurchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 在 **Set Actions Arguments** 部分,选择之前创建的 `getPaywallProductsResult` 变量。 6. 按如下方式填写字段: - **Available Options**: Data Structure Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First <img src="/assets/shared/img/ff-makepurchase-value.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. 点击 `subscriptionUpdateParameters`,搜索 `AdaptySubscriptionUpdateParameters` 并选择它。点击 **Confirm**。 :::info 默认情况下,您可以将所有对象字段留空。如果需要在 Android 应用中将一个订阅替换为另一个订阅,则需要填写这些字段。详情请阅读[此处](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/)。 ::: <img src="/assets/shared/img/ff-subupdate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. 点击 **Confirm**。 9. 在 **Action Output Variable Name** 中,创建一个新变量并将其命名为 `makePurchaseResult`——稍后将用于确认购买是否成功。 <img src="/assets/shared/img/ff-makepurchaseresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 3.2. 检查购买是否成功 \{#step-32-check-if-the-purchase-was-successful\} 现在,让我们设置一个检查,以确认购买是否已完成。 1. 点击 **+** 并点击 **Add Conditional**。 2. 在 **Set Condition for Action** 中,选择 `makePurchaseResult` 变量。 3. 在 **Set Variable** 窗口中,按如下方式填写字段: - **Available Options**: Has Field - **Select Field**: profile <img src="/assets/shared/img/ff-makepurchaseresult-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Confirm**。 ## 步骤 3.3. 打开付费内容 \{#step-33-open-paid-content\} 如果购买成功,您可以解锁付费内容。以下是设置方法: 1. 点击 **TRUE** 标签下的 **+**,然后点击 **Add Action**。 2. 在 **Define Action** 字段中,从 **Navigate To** 列表中搜索并选择您要打开的页面。在此示例中,该页面为 **Questions**。 <img src="/assets/shared/img/ff-questions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 步骤 3.4 购买失败时显示错误消息 \{#step-34-show-error-message-if-purchase-failed\} 如果购买失败,让我们向用户显示一个提示。 1. 向 **FALSE** 标签添加一个 **Informational Dialog** 动作。 2. 在 **Title** 字段中,输入对话框标题的文字,例如 **Purchase Failed**。 <img src="/assets/shared/img/ff-purchase-fail.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Message** 框中点击 **Value**。在 **Set from Variable** 窗口中,搜索 `makePurchaseResult` 并选择它。按如下方式填写字段: - **Available Options**: Data Structure Field - **Select Field**: error - **Available Options**: Data Structure Field - **Select Field**: errorMessage <img src="/assets/shared/img/ff-fail-message.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 点击 **Confirm**。 5. 向 **FALSE** 流程添加一个 **Terminate** 动作。 <img src="/assets/shared/img/ff-terminate-purchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 最后,点击右上角的 **Close**。 恭喜!您的用户现在可以购买您的产品了。作为额外步骤,让我们[在其他地方设置用户对付费内容的访问检查](ff-check-subscription-status),以决定是向他们显示付费内容还是付费墙。 --- # File: ff-check-subscription-status --- --- title: "步骤 4. 检查付费内容访问权限" description: "了解如何使用 Adapty 的功能标志检查订阅状态,以实现更好的用户细分。" --- 在判断用户是否有权访问特定付费内容时,您需要验证其访问等级。这意味着需要检查用户是否至少拥有一个访问等级,以及该等级是否符合要求。 您可以通过检查用户画像来完成此操作,用户画像中包含所有可用的访问等级。 现在,让我们允许用户购买您的产品: 1. 双击应显示付费内容的按钮,并在右侧面板中打开 **Actions** 部分(如果尚未打开)。 2. 打开 **Action Flow Editor**。 <img src="/assets/shared/img/ff-open-paid-content.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 在 **Select Action Trigger** 窗口中,选择 **On Tap**。 4. 在 **No Actions Created** 窗口中,点击 **Add Conditional Action** 按钮。 5. 点击 **UNSET** 以设置操作参数,然后选择 `currentProfile` 变量。这是 Adapty 中保存当前用户画像数据的变量。 <img src="/assets/shared/img/ff-currentprofile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. 按如下方式填写字段: - **Available Options**:Data Structure Field - **Select Field**:accessLevels - **Available Options**:Filter List Items - **Filter Conditions**: 1. 选择 **Conditions -> Single Condition**,然后点击 **UNSET**。 2. 在 **First value** 字段中,将 **Source** 选择为 **Item in list**,并按如下方式填写字段: - **Available Options**:Data Structure Field - **Select Field**:accessLevelIdentifier 3. 将过滤运算符设置为 **Equal to**。 4. 点击 **Second value** 旁边的 **UNSET**,在 **Value** 字段中输入您的访问等级 ID;在本示例中,我们使用 `premium`。 <img src="/assets/shared/img/ff-filter.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 点击 **Confirm**,然后继续填写下方的其他字段。 - **Available Options**:Item at Index - **List Index Options**:First - **Available Options**:Data Structure Field - **Select Field**:accessLevel - **Available Options**:Data Structure Field - **Select Field**:isActive 7. 点击 **Confirm**。 现在,添加后续操作——根据用户是否拥有正确的订阅来决定下一步。可以将其引导至高级订阅用户可访问的页面,或打开付费墙页面让其购买访问权限。 --- # File: ff-resources --- --- title: "Adapty FlutterFlow 插件操作与数据类型" description: "访问 Adapty 的功能标志资源,以简化基于订阅的功能。" --- ## 自定义操作 \{#custom-actions\} 以下是通过 Adapty 插件传递给 FlutterFlow 的 Adapty 方法。它们可以在 FlutterFlow 中用作自定义操作。 | 自定义操作 | 描述 | 操作参数 | Adapty 数据类型 - 操作输出变量 | |---|----|--------|----| | activate | 初始化 Adapty SDK | 无 || | <p id="getPaywall">getPaywall</p> | 获取付费墙。该操作不返回付费墙产品,请使用 `getPaywallProducts` 操作获取实际产品 | <ul><li>[Placement_ID](placements)</li><li>[Locale](localizations-and-locale-codes)</li></ul> | [AdaptyGetPaywallResult](ff-resources#adaptygetpaywallresult)| | <p id="getPaywallProducts">getPaywallProducts</p> | 返回实际付费墙产品列表 | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyGetProductsResult](ff-resources#adaptygetproductsresult) | | <p id="getproductsintroductoryoffereligibility">getProductsIntroductoryOfferEligibility</p> | 检查用户是否符合 iOS 订阅新用户优惠的资格 | [AdaptyPaywallProduct](product) | [AdaptyGetIntroEligibilitiesResult](ff-resources#adaptygetintroeligibilitiesresult) | | <p id="makePurchase">makePurchase</p> | 完成购买并解锁内容。如果付费墙有促销活动,Adapty 会在结账时自动应用 | <ul><li> **product**:从付费墙获取的 AdaptyPaywallProduct 对象。</li><li> **subscriptionUpdateParams**:用于升级或降级订阅的 [`AdaptySubscriptionUpdateParameters`](ff-resources#adaptysubscriptionupdateparameters) 对象(适用于 Android)。</li><li>**isOfferPersonalized**:指定优惠是否针对买家个人定制(适用于 Android)。</li></ul> | [AdaptyMakePurchaseResult](ff-resources#adaptymakepurchaseresult) | | <p id="getprofile">getProfile</p> | <p>获取当前应用用户的用户画像,以便设置访问等级及其他参数。</p><p>如果获取失败(例如因为没有网络),将返回缓存数据。Adapty 会定期更新用户画像缓存,以确保信息尽可能保持最新。</p> | 无 | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | updateProfile | 修改当前用户画像的可选属性,如电子邮件、电话号码等。您可以使用这些属性创建用户[市场细分](segments),或直接在 CRM 中查看 | [AdaptyProfile](ff-resources#adaptyprofile) 的 ID 及需要更新的任意参数 | [AdaptyError](ff-resources#adaptyerror)(可选) | | restorePurchases | 恢复用户已完成的购买 | 无 | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | logShowPaywall | 记录特定付费墙向用户展示的事件 | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyError](ff-resources#adaptyerror)(可选) | | identify | 使用您系统的 `customerUserId` 识别用户 | customerUserId | [AdaptyError](ff-resources#adaptyerror)(可选) | | logout | 将当前用户退出登录 | 无 | [AdaptyError](ff-resources#adaptyerror)(可选) | | presentCodeRedemptionSheet | 显示允许用户兑换码的界面(仅限 iOS) | 无 | 无 | ## 数据类型 \{#data-types\} Adapty 数据类型(数据值的集合)通过 Adapty 插件传递至 FlutterFlow。 ### AdaptyAccessLevel 关于用户[访问等级](access-level)的信息。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | activatedAt | DateTime | 此访问等级的激活时间 | | activeIntroductoryOfferType | String | 当前生效的新用户优惠类型。若已设置,表示在此订阅周期内应用了优惠 | | activePromotionalOfferId | String | 当前生效的促销活动 ID(从 iOS 购买)| | activePromotionalOfferType | String | 当前生效的促销活动类型(从 iOS 购买)。若已设置,表示在此订阅周期内应用了优惠 | | billingIssueDetectedAt | DateTime | 检测到账单问题的时间。订阅可能仍处于有效状态。若付款处理成功,则设为 null | | cancellationReason | String | 订阅被取消的原因 | | expiresAt | DateTime | 访问等级的到期时间(可能已过期,或对于永久授权未设置此字段)| | id | String | 访问等级的标识符 | | isActive | Boolean | 若此访问等级处于激活状态则为 true。通常可通过此属性判断用户是否有权访问高级功能 | | isInGracePeriod | Boolean | 若此自动续期订阅处于[宽限期](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions)则为 true | | isLifetime | Boolean | 若此访问等级为永久授权(无到期日期)则为 true | | isRefund | Boolean | 若此购买已退款则为 true | | offerId | String | 当前生效的促销活动 ID(从 Android 购买)| | renewedAt | DateTime | 访问等级上次续期的时间 | | startsAt | DateTime | 此访问等级的开始时间(可能为将来的时间)| | store | String | 购买发生的商店 | | unsubscribedAt | DateTime | 订阅关闭自动续期的时间。订阅可能仍处于有效状态。若未设置,表示用户已重新激活订阅 | | vendorProductId | String | 解锁此访问等级的商店产品 ID | | willRenew | Boolean | 若此自动续期订阅已设置为续期则为 true | ### AdaptyAccessLevelIdentifiers 此结构体用于替换 `Map<String, AdaptyAccessLevel` [AdaptyAccessLevel](ff-resources#adaptyaccesslevel) 的键值对。 | 字段名 | 类型 | 描述 | |------------|------|-------------| | accessLevelIdentifier | String | 访问等级的 ID | | accessLevel | Data ([AdaptyAccessLevel](ff-resources#adaptyaccesslevel)) | 关联的 [AdaptyAccessLevel](ff-resources#adaptyaccesslevel) | ### AdaptyCustomDoubleAttribute 关于为[用户](ff-resources#adaptyprofile)定义的自定义 double 属性的信息。 | 字段名 | 类型 | 描述 | |------------|------|-------------| | key | String | 自定义 double 属性的 ID | | value | Double | 自定义 double 属性的值 | ### AdaptyCustomStringAttribute 为[用户](ff-resources#adaptyprofile)定义的自定义字符串属性信息。 | 字段名 | 类型 | 描述 | |------------|------|-------------| | key | String | 自定义字符串属性的 ID | | value | String | 自定义字符串属性的值 | ### AdaptyError 包含错误的详细信息。有关错误代码的完整列表,请参阅 [React Native、Flutter、Unity - 错误处理](error-handling-on-flutter-react-native-unity)。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | errorMessage | String | 人类可读的错误描述 | | errorCode | Integer | 标识错误的数字代码 | ### AdaptyGetIntroEligibilitiesResult 包含 `getProductsIntroductoryOfferEligibility` 自定义操作的结果。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | value | List < Data ([AdaptyProductIntroEligibility](ff-resources#adaptyproductintroeligibility)) > | 用户对促销活动的资格列表 | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | 通过 [`AdaptyError`](ff-resources#adaptyerror) 包含错误的详细信息 | ### AdaptyGetPaywallResult 包含 `getPaywall` 自定义操作的结果。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | value | Data ([AdaptyPaywall](ff-resources#adaptypaywall)) | 包含 [AdaptyPaywall](ff-resources#adaptypaywall) 对象列表 | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | 通过 [AdaptyError](ff-resources#adaptyerror) 包含错误信息 | ### AdaptyGetProductsResult 包含 `getPaywallProducts` 自定义操作的结果。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | value | List < Data ([AdaptyPaywallProduct](product)) > | 包含 [AdaptyPaywallProduct](product) 列表 | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | 通过 [AdaptyError](ff-resources#adaptyerror) 包含错误信息 | ### AdaptyGetProfileResult 包含 `getProfile` 自定义操作的结果。 | 字段名 | 类型 | 描述 | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | 以 [AdaptyProfile](ff-resources#adaptyprofile) 形式包含用户画像 | | error | Data (AdaptyError) | 通过 [AdaptyError](ff-resources#adaptyerror) 包含错误信息 | ### AdaptyMakePurchaseResult 包含 `makePurchase` 自定义操作的结果。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | 以 [AdaptyProfile](ff-resources#adaptyprofile) 的形式包含用户画像 | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | 通过 [AdaptyError](ff-resources#adaptyerror) 包含错误信息 | ### AdaptyNonSubscription 关于非订阅购买的信息。这些可以是一次性(消耗型商品)产品、解锁项目(例如游戏中的新地图解锁)等。 | 字段名称 | 类型 | 描述 | |--------------------------|----------|-------------| | isConsumable | Boolean | 表示该产品是否为消耗型商品 | | isOneTime | Boolean | 表示该产品是否为一次性购买(例如,若为 true,则该购买仅处理一次) | | isRefund | Boolean | 表示该产品是否已退款 | | isSandbox | Boolean | 表示该产品是否在沙盒环境中购买 | | purchasedAt | DateTime | 产品的购买时间 | | purchaseId | String | 该购买在 Adapty 中的 ID,可用于追踪一次性购买产品 | | store | String | 购买该产品的商店(例如 App Store、Google Play) | | vendorProductId | String | 该产品在供应商系统中的 ID | | vendorTransactionId | String | 该产品在供应商系统中的交易 ID | ### AdaptyPaywall 关于[付费墙](paywalls)的信息。 | 字段名称 | 类型 | 描述 | |----------------------|----------|-------------| | abTestName | String | 父级 A/B 测试的名称 | | hasViewConfiguration | Boolean | 指示付费墙是否存在视图配置 | | locale | String | 付费墙的区域设置 ID | | name | String | 付费墙名称 | | placement.id | String | 父级版位的 ID | | remoteConfigString | String | 来自 Adapty 看板中与此付费墙关联的自定义字典 | | placement.revision | Integer | 付费墙的当前修订版本/版本号。每次更改都会生成新的修订版本 | | variationId | String | 用于将购买归因到此付费墙的实验变体 ID | | vendorProductIds | String | 与付费墙相关的产品 ID 数组 | ### AdaptyPaywallProduct 关于[产品](product)的信息。 | 字段名 | 类型 | 描述 | | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | vendorProductId | String | 应用商店中产品的 ID | | localizedDescription | String | 产品在用户语言中的描述 | | localizedTitle | String | 产品在用户语言中的名称 | | regionCode | String | 用于格式化产品价格的地区代码(适用于 iOS) | | isFamilyShareable | Boolean | 表示产品是否在 App Store Connect 中可供家庭共享的布尔值。对于 iOS 14.0 以下版本及 macOS 11.0 以下版本,始终为 FALSE(适用于 iOS) | | paywallVariationId | String | 实验变体的 ID,用于将购买归因到此付费墙 | | paywallABTestName | String | 父级 A/B 测试名称 | | paywallName | String | 父级付费墙名称 | | price | Data ([AdaptyPriceData](#adaptyprice)) | 产品价格 | | subscriptionDetails | Data ([AdaptySubscriptionDetails](#adaptysubscriptiondetails)) | 订阅相关信息 | ### AdaptyPrice 关于产品价格的信息。 | 字段名 | 类型 | 描述 | | --------------- | ------ | ------------------------ | | amount | Double | 价格的数值 | | currencyCode | String | 价格货币的代码 | | currencySymbol | String | 货币使用的符号 | | localizedString | String | 以用户语言显示的价格 | ### AdaptyProductIntroEligibility 定义用户是否符合 iOS 订阅新用户优惠的资格。 | 字段名 | 类型 | 描述 | | --------------- | ----------------------------------------------------------- | ---------------------------------------- | | vendorProductId | String | 应用商店中产品的 ID | | eligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | 定义用户是否符合 iOS 订阅新用户优惠的资格 | ### AdaptyProductNonsubscriptions 与此产品关联的活跃非订阅详情。 | 字段名称 | 类型 | 描述 | | ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | productId | String | 应用商店中该产品的 ID | | nonsubscriptions | [AdaptyNonSubscription](ff-resources#adaptynonsubscription) | 非订阅购买的相关信息。可以是一次性(消耗型商品)产品、解锁内容(例如游戏中的新地图解锁)等。 | ### AdaptyProductSubscriptions 与该产品关联的活跃订阅的详细信息。 | 字段名称 | 类型 | 描述 | | ------------ | ----------------------------------------------------- | --------------------------- | | productId | String | 应用商店中产品的 ID | | subscription | [AdaptySubscription](ff-resources#adaptysubscription) | 关于订阅购买的信息 | ### AdaptyProfile 用户画像的相关信息 | 字段名称 | 类型 | 描述 | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | accessLevels | List < Data ([AdaptyAccessLevelIdentifiers](ff-resources#adaptyaccesslevelidentifiers)) > | 属于该用户的所有访问等级列表 | | profileId | String | 用户画像的 ID | | customerUserId | String | 用户在供应商系统中的 ID | | subscriptions | List < Data ([MapKeySubscriptions](#mapkeysubscriptions)) > | 用户购买的所有订阅列表 | | nonSubscriptions | List < Data ([MapKeyNonSubscriptions](#mapkeynonsubscriptions)) > | 用户购买的所有非订阅产品列表 | ### AdaptyProfileParameters 用户信息。 | 字段名称 | 类型 | 描述 | | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | firstName | String | 用户的名字 | | lastName | String | 用户的姓氏 | | gender | [AdaptyGenderEnum](#adaptygenderenum) | 用户的性别 | | birthday | String | 用户的生日 | | email | String | 用户的电子邮箱 | | phoneNumber | String | 用户的电话号码 | | facebookAnonymousId | String | 用户在 [Facebook Ads 集成](facebook-ads) 中的 ID | | amplitudeUserId | String | 用户在 [Amplitude 集成](amplitude) 中的 ID | | amplitudeDeviceId | String | 用户设备在 [Amplitude 集成](amplitude) 中的 ID | | mixpanelUserId | String | 用户在 [Mixpanel 集成](mixpanel) 中的 ID | | appmetricaProfileId | String | 用户在 [AppMetrica 集成](appmetrica) 中的 ID | | appmetricaDeviceId | String | 用户设备在 [AppMetrica 集成](appmetrica) 中的 ID | | oneSignalPlayerId | String | 用户在 [OneSignal 集成](onesignal) 中的 ID | | pushwooshHWID | String | 用户设备在 [Pushwoosh 集成](pushwoosh) 中的 ID | | firebaseAppInstanceId | String | 用户在 [Firebase 集成](firebase-and-google-analytics) 中的 ID | | airbridgeDeviceId | String | 用户设备在 [Airbridge 集成](airbridge) 中的 ID | | appTrackingTransparencyStatus | AdaptyATTStatus | 访问 IDFA 的状态(适用于 iOS) | | analyticsDisabled | Boolean | 定义是否已为该用户[选择退出外部分析](analytics-integration#disabling-external-analytics-for-a-specific-customer) | | customStringAttributes | List < Data ([AdaptyCustomStringAttribute](ff-resources#adaptycustomstringattribute)) > | 用户的自定义字符串属性列表 | | customDoubleAttributes | List < Data ([AdaptyCustomDoubleAttribute](ff-resources#adaptycustomdoubleattribute)) > | 用户的自定义双精度属性列表 | ### AdaptySubscription 关于现有用户订阅的信息。 | 字段名称 | 类型 | 描述 | | --------------------------- | -------- | ------------------------------------------------------------ | | activatedAt | DateTime | 此订阅激活的时间 | | activeIntroductoryOfferType | String | 当前有效的新用户优惠类型。若已设置,表示在此订阅周期内应用了某项优惠 | | activePromotionalOfferId | String | 当前有效的促销活动 ID(用于 iOS) | | activePromotionalOfferType | String | 当前有效的促销活动类型(用于 iOS)。若已设置,表示在此订阅周期内应用了某项促销活动 | | cancellationReason | String | 订阅被取消的原因 | | expiresAt | DateTime | 订阅到期时间 | | renewedAt | DateTime | 订阅上次续订的时间 | | unsubscribedAt | DateTime | 订阅关闭自动续订的时间。订阅仍可处于有效状态。若未设置,表示用户已重新激活订阅 | | billingIssueDetectedAt | DateTime | 检测到账单问题的时间。订阅仍可处于有效状态。若付款成功处理,则设为 null | | isActive | Boolean | 若此订阅处于有效状态则为 True。通常可通过此属性判断用户是否可访问高级功能 | | isInGracePeriod | Boolean | 若此自动续订订阅处于[宽限期](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions)则为 True | | isLifetime | Boolean | 若此订阅为永久授权(无到期日期)则为 True | | isRefund | Boolean | 若此购买已退款则为 True | | isSandbox | Boolean | 表示产品是否在沙盒环境中购买 | | offerId | String | 当前有效的促销活动 ID(用于 Android) | | startsAt | DateTime | 此访问等级的开始时间(可能是未来时间) | | store | String | 购买产品的商店(例如 App Store、Google Play) | | vendorOriginalTransactionId | String | 供应商系统中初始订阅的 ID | | vendorProductId | String | 供应商系统中产品的 ID | | vendorTransactionId | String | 供应商系统中的交易 ID | | willRenew | Boolean | 若此自动续订订阅已设置为续订则为 True | ### AdaptySubscriptionDetails [AdaptyPaywallProduct](product) 的订阅对象结构。 | 字段名 | 类型 | 描述 | | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | androidBasePlanId | String | Google Play 商店中的[基础方案 ID](https://support.google.com/googleplay/android-developer/answer/12154973) 或 Stripe 中的[价格 ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices)。 | | androidIntroductoryOfferEligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | 用户是否符合 iOS 订阅新用户优惠的资格判定 | | androidOfferId | String | 有效促销活动的 ID(用于 Android) | | androidOfferTags | List < String > | 为基础方案和订阅优惠指定的[自定义标签](https://developers.google.com/android-publisher/api-ref/rest/v3/OfferTag)列表。 | | introductoryOffer | List < Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) > | 新用户优惠的 ID(用于 iOS) | | localizedSubscriptionPeriod | String | 以用户语言显示的订阅周期 | | promotionalOffer | Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) | 促销活动详情(用于 iOS) | | promotionalOfferEligibility | Boolean | 用户是否符合 iOS 订阅促销活动资格的判定 | | promotionalOfferId | String | 促销活动的 ID(用于 iOS) | | renewalType | [AdaptyRenewalTypeEnum](#adaptyrenewaltypeenum) | 通过 [AdaptyRenewalTypeEnum](ff-resources#adaptyrenewaltypeenum) 定义订阅是否为自动续订 | | subscriptionGroupIdentifier | String | 产品所属产品组的 ID(用于 iOS) | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | 订阅的时长 | ### AdaptySubscriptionPeriod 订阅的时长。 | 字段名称 | 类型 | 描述 | | ------------- | --------------------------------------------- | ----------------------------------------------------------- | | numberOfUnits | Integer | 订阅持续的天数/周数/月数/年数。 | | unit | [AdaptyPeriodUnitEnum](#adaptyperiodunitenum) | 周期的计量单位:天、周、月、年。 | ### AdaptySubscriptionPhase 表示订阅阶段,例如免费试用期或新用户优惠期。 | 字段名 | 类型 | 描述 | | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | identifier | String | 阶段的 ID | | localizedNumberOfPeriods | String | 阶段的时长。例如,6 个月的优惠活动将以用户的语言显示为 `6 months`。 | | localizedSubscriptionPeriod | String | 以用户语言表示的订阅时长,例如 `3 months`。 | | numberOfPeriods | Integer | 此阶段包含的订阅周期数。例如,6 个月的优惠活动将包含两个 3 个月的周期。 | | paymentMode | [AdaptyPaymentModeEnum](#adaptypaymentmodeenum) | 此阶段使用的付款模式。 | | price | Data ([AdaptyPrice](#adaptyprice)) | 此阶段的价格。 | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | 此阶段所基于的订阅周期。 | ### AdaptySubscriptionUpdateParameters (*仅限 Android*) 用于将一个订阅替换为另一个订阅的参数。 | 字段名称 | 类型 | 描述 | | ---------- | ------------------------------------------------------------ | ---------- | | oldSubVendorProductId | String | 您想要替换的 Play Store 中当前订阅的 ID。 | | replacementMode | [AdaptySubscriptionUpdateReplacementMode](ff-resources#adaptysubscriptionupdatereplacementmode) | 对应 [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) 值的枚举。 | ### MapKeyNonSubscriptions [AdaptyNonSubscription](ff-resources#adaptynonsubscription) 字典的替代项。 | 字段名 | 类型 | | ---------- | ------------------------------------------------------------ | | key | String | | value | List < Data ([AdaptyNonSubscription](ff-resources#adaptynonsubscription)) > | ### MapKeySubscriptions [AdaptySubscription](ff-resources#adaptysubscription) 字典的替代类型。 | 字段名 | 类型 | | ---------- | ------------------------------------------------------------ | | key | String | | value | List < Data ([AdaptySubscription](ff-resources#adaptysubscription)) > | ## 枚举 \{#enums\} 通过 Adapty 插件传递给 FlutterFlow 的 Adapty 枚举(即预定义常量集合的变量)。 ### AdaptyEligibilityEnum 定义用户是否符合 iOS 订阅新用户优惠的资格。 | 字段名称 | 描述 | |--------------------------|-------------| | eligible | 用户符合新用户优惠资格,可以在您的 UI 中展示此信息 | | ineligible | 用户不符合任何优惠资格,不应在您的 UI 中展示 | | notApplicable | 该产品未配置任何优惠 | ### AdaptyGenderEnum 定义用户性别。 | 字段名称 | 描述 | | ---------- | -------------------------------------------- | | none | 性别未设置 | | female | 用户性别为女性 | | male | 用户性别为男性 | | Other | 用户将其性别定义为"其他" | ### AdaptyPaymentModeEnum \{#adaptypaymentmodeenum\} 定义支付模式。 | 字段名称 | 描述 | | ---------- | ------------------------------------------------------------ | | payAsYouGo | 一种按需计费的定价模式,客户根据其对产品/服务的实际使用量或消耗量付费,而非预先支付固定费用 | | payUpFront | 一种预付款定价模式,客户在收到产品/服务之前即完成付款。 | | freeTrial | 用户正处于免费试用期 | | unknown | 定价模式未定义 | ### AdaptyPeriodUnitEnum 定义周期计量的单位。 | 字段名 | 描述 | | ---------- | ----------- | | day | 以天为单位 | | week | 以周为单位 | | month | 以月为单位 | | year | 以年为单位 | | unknown | 未定义 | ### AdaptyRenewalTypeEnum 定义订阅是否自动续期。 | 字段名称 | 描述 | | ------------- | ---------------------------- | | prepaid | 订阅为预付费模式,不自动续期。 | | autorenewable | 订阅为自动续期模式。 | ### AdaptySubscriptionUpdateReplacementMode 定义 Android 的订阅更新模式。 | 字段名称 | 描述 | | ------------- | --------------------------------------------------- | | withTimeProration | (默认)新方案立即生效,剩余时间将按比例折算并计入用户账户。 | | chargeProratedPrice | 新方案立即生效,计费周期保持不变。剩余期间的费用将被收取。此选项仅适用于订阅升级。 | | withoutProration | 新方案立即生效,新价格将在下一个续费时间收取。计费周期保持不变。 | | deferred | 新购买立即生效,新方案将在旧项目到期后生效。 | | chargeFullPrice | 新方案立即生效,计费周期保持不变。剩余期间的费用将被收取。此选项仅适用于订阅升级。 | ### 应用状态 \{#app-states\} 应用状态变量是保存应用程序当前状态的特定变量。它们可以在整个应用程序的所有页面和组件中被访问和修改。这类变量适用于存储需要在应用不同部分之间共享的数据,例如用户偏好设置和身份验证令牌。 | 字段名称 | 数据类型 | 持久化 | 描述 | | -------------- | -------------------------------------------------- | ------ | ------------------------------------------------------------ | | currentProfile | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | False | 包含当前用户画像信息的变量。请保持其最新状态。 | --- # End of Documentation _Generated on: 2026-07-24T13:01:53.535Z_ _Successfully processed: 277/277 files_ # UNITY - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: zh Generated on: 2026-07-24T13:01:53.540Z Total files: 41 --- # File: sdk-installation-unity --- --- title: "安装与配置 Unity SDK" description: "在 Unity 中为订阅类应用安装 Adapty SDK 的分步指南。" --- Adapty SDK 包含两个关键模块,可无缝集成到您的 Unity 应用中: - **Core Adapty**:这是 Adapty 正常运行所必需的核心 SDK。 - **AdaptyUI**:如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder)(一款无需编写代码即可轻松创建跨平台付费墙的可视化工具),则需要此模块。 :::tip 想了解 Adapty SDK 是如何集成到移动应用中的真实案例吗?欢迎查看我们的[示例应用](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets),其中演示了完整的接入流程,包括展示付费墙、发起购买以及其他基础功能。 ::: ## 环境要求 \{#requirements\} Adapty SDK 支持 iOS 13.0+,但需要 iOS 15.0+ 才能与付费墙编辑工具中创建的付费墙配合使用。 :::info Adapty 兼容 Google Play Billing Library 最高至 8.x 版本。默认情况下,Adapty 使用 Google Play Billing Library v7.0.0。如需使用更新版本,请在 Android 构建中[覆盖 Billing 依赖项](https://developer.android.com/google/play/billing/integrate#dependency)。 ::: :::info 安装 SDK 是 Adapty 配置流程的第 5 步。在应用内购买正常运行之前,您还需要将应用连接到各应用商店,然后在 Adapty 看板中创建产品、付费墙和版位。[快速入门指南](quickstart)涵盖了所有必要步骤。 ::: ## 安装 Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Unity.svg?style=flat&logo=unity)](https://github.com/adaptyteam/AdaptySDK-Unity/releases) 选择你偏好的安装方式: <Tabs groupId="unity-install-method"> <TabItem value="git-url" label="Git URL"> 通过 Unity Package Manager 使用 Git URL 安装 Adapty SDK: 1. 在 Unity 中,打开 **Window → Package Manager**。 2. 点击左上角的 **+**,然后选择 **Add package from git URL...**。 3. 输入以下 URL 并点击 **Add**: ``` https://github.com/adaptyteam/AdaptySDK-Unity.git?path=Packages/com.adapty.unity-sdk#upm ``` 有关详细信息,请参阅 Unity 的[从 Git URL 安装 UPM 包](https://docs.unity3d.com/Manual/upm-ui-giturl.html)指南。 </TabItem> <TabItem value="unity-package" label="Unity package" default> 从 GitHub 下载 [`adapty-unity-plugin-*.unitypackage`](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Releases) 并将其导入到你的项目中。 <img src="/assets/shared/img/456bd98-adapty-unity-plugin.webp" style={{ border: 'none', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> 安装 SDK 后,请完成以下步骤: 1. 安装 [External Dependency Manager (EDM) 插件](https://github.com/googlesamples/unity-jar-resolver#getting-started)。Adapty SDK 使用它来处理 iOS Cocoapods 依赖项和 Android gradle 依赖项。 2. 安装 EDM 后,您可能需要调用依赖管理器: `Assets -> External Dependency Manager -> Android Resolver -> Force Resolve` 以及 `Assets -> External Dependency Manager -> iOS Resolver -> Install Cocoapods` 3. 在为 iOS 构建 Unity 项目时,您会得到 `Unity-iPhone.xcworkspace` 文件,您必须打开该文件而非 `Unity-iPhone.xcodeproj`,否则 Cocoapods 依赖项将不会被使用。 ## 激活 Adapty SDK 的 Adapty 模块 \{#activate-adapty-module-of-adapty-sdk\} 在你的应用代码中激活 Adapty SDK。 :::note Adapty SDK 在你的应用中只需激活一次。 ::: 获取您的 **Public SDK Key**: 1. 打开 Adapty 看板,导航至 [**App settings → General**](https://app.adapty.io/settings/general)。 2. 在 **Api keys** 部分,复制 **Public SDK Key**(不是 Secret Key)。 3. 将代码中的 `"YOUR_PUBLIC_SDK_KEY"` 替换为实际值。 或者,使用 [Adapty CLI](developer-cli) 以编程方式获取: ``` npm install -g adapty adapty auth login adapty apps list ``` 或者,直接运行: ``` npx adapty auth login adapty apps list ``` - 请确保使用 **Public SDK key** 初始化 Adapty,**Secret key** 仅用于[服务端 API](getting-started-with-server-side-api)。 - **SDK keys** 对每个应用都是唯一的,如果您有多个应用,请确保选择正确的那个。 ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` :::important 在调用任何其他 Adapty SDK 方法之前,请等待 `Activate` 完成回调。完整调用顺序请参阅 [Unity SDK 调用顺序](unity-sdk-call-order)。 ::: ## 设置事件监听 \{#set-up-event-listening\} 创建一个脚本来监听 Adapty 事件,在场景中将其命名为 `AdaptyListener`。建议对该对象使用 `DontDestroyOnLoad` 方法,确保它在应用程序的整个生命周期内持续存在。 <img src="/assets/shared/img/2ccd564-create_adapty_listener.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty 使用 `AdaptySDK` 命名空间。在使用 Adapty SDK 的脚本文件顶部,你可以添加: ```csharp showLineNumbers title="C#" using AdaptySDK; ``` 订阅 Adapty 事件: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle updated profile data } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` 我们建议调整脚本执行顺序,将 AdaptyListener 放在 Default Time 之前,以确保 Adapty 尽早完成初始化。 <img src="/assets/shared/img/activate_unity.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 接下来,在应用中配置付费墙: - 如果您使用 [Adapty 付费墙编辑工具](adapty-paywall-builder),请先[激活 AdaptyUI 模块](#activate-adaptyui-module-of-adapty-sdk),然后按照[付费墙编辑工具快速入门](unity-quickstart-paywalls)进行操作。 - 如果您自行构建付费墙 UI,请参阅[自定义付费墙快速入门](unity-quickstart-manual)。 ## 激活 Adapty SDK 的 AdaptyUI 模块 \{#activate-adaptyui-module-of-adapty-sdk\} 如果您计划使用[付费墙编辑工具](adapty-paywall-builder)并已安装 AdaptyUI 模块,则需要激活 AdaptyUI。您可以在配置过程中激活它: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetActivateUI(true); ``` ## 可选设置 \{#optional-setup\} ### 日志记录 \{#logging\} #### 配置日志系统 \{#set-up-the-logging-system\} Adapty 会记录错误和其他重要信息,帮助你了解运行情况。以下是可用的日志级别: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | 仅记录错误日志 | | `warn` | 记录错误以及 SDK 中不会导致严重错误但值得关注的消息 | | `info` | 记录错误、警告及各类信息消息 | | `verbose` | 记录所有可能在调试时有用的附加信息,例如函数调用、API 请求等 | 你可以在配置 Adapty 时设置应用的日志级别: ```csharp showLineNumbers title="C#" // 'verbose' is recommended for development and the first production release var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); builder.LogLevel = AdaptyLogLevel.Verbose; ``` 你也可以在运行时动态修改日志级别: ```csharp showLineNumbers title="C#" Adapty.SetLogLevel(AdaptyLogLevel.Verbose, (error) => { // handle result }); ``` ### 数据政策 \{#data-policies\} Adapty 不会存储用户的个人数据,除非您明确发送,但您可以实施额外的数据安全策略,以符合应用商店或所在国家/地区的法规要求。 #### 禁用 IP 地址采集与共享 \{#disable-ip-address-collection-and-sharing\} 在激活 Adapty 模块时,将 `SetIPAddressCollectionDisabled` 设置为 `true` 即可禁用用户 IP 地址的采集与共享。默认值为 `false`。 使用此参数可增强用户隐私保护、遵守地区性数据保护法规(如 GDPR 或 CCPA),或在应用不需要基于 IP 的功能时减少不必要的数据采集。 ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetIPAddressCollectionDisabled(true); ``` #### 禁止采集和共享广告 ID \{#disable-advertising-id-collection-and-sharing\} 在激活 Adapty 模块时,将 `SetAppleIDFACollectionDisabled` 和/或 `SetGoogleAdvertisingIdCollectionDisabled` 设置为 `true` 可禁用广告标识符的收集。默认值为 `false`。 如需遵守 App Store/Google Play 政策、避免触发 App 跟踪透明度提示,或者你的应用不需要基于广告 ID 的广告归因或分析功能,可使用此参数。 ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleIDFACollectionDisabled(true) .SetGoogleAdvertisingIdCollectionDisabled(true); ``` #### 为 AdaptyUI 配置媒体缓存 \{#set-up-media-cache-configuration-for-adaptyui\} 默认情况下,AdaptyUI 会缓存媒体内容(如图片和视频),以提升性能并减少网络流量。你可以通过提供自定义配置来调整缓存设置。 使用 `SetAdaptyUIMediaCache` 覆盖默认缓存设置: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAdaptyUIMediaCache( 100 * 1024 * 1024, // MemoryStorageTotalCostLimit 100MB null, // MemoryStorageCountLimit 100 * 1024 * 1024 // DiskStorageSizeLimit 100MB ); ``` 参数: | 参数 | 是否必填 | 描述 | |-----------------------------|----------|---------------------------------------------------| | memoryStorageTotalCostLimit | 可选 | 内存缓存大小(字节)。默认值因平台而异。 | | memoryStorageCountLimit | 可选 | 内存存储的条目数量上限。默认值因平台而异。 | | diskStorageSizeLimit | 可选 | 磁盘文件大小上限(字节)。默认值因平台而异。 | ### 启用本地访问等级(Android) \{#enable-local-access-levels-android\} 默认情况下,[本地访问等级](local-access-levels)在 iOS 上已启用,在 Android 上已禁用。若要在 Android 上同样启用,请将 `SetGoogleLocalAccessLevelAllowed` 设置为 `true`: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetGoogleLocalAccessLevelAllowed(true); ``` ### 备份恢复时清除数据 \{#clear-data-on-backup-restore\} 当 `SetAppleClearDataOnBackup` 设置为 `true` 时,SDK 会检测应用从 iCloud 备份恢复的情况,并删除所有本地存储的 SDK 数据,包括已缓存的用户画像信息、产品详情和付费墙。之后 SDK 将以全新状态完成初始化。默认值为 `false`。 :::note 仅删除本地 SDK 缓存。Apple 的交易记录及 Adapty 服务器上的用户数据不受影响。 ::: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleClearDataOnBackup(true); ``` ## 故障排查 \{#troubleshooting\} #### Android 备份规则(Auto Backup 配置) \{#android-backup-rules-auto-backup-configuration\} 部分 SDK(包括 Adapty)会自带 Android 自动备份配置。如果多个 SDK 都定义了备份规则,Android 清单合并工具可能会报错,提示 `android:fullBackupContent`、`android:dataExtractionRules` 或 `android:allowBackup` 相关问题。 常见错误示例:`Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note 以下更改应在你的 Android 平台目录(通常位于项目的 `android/` 文件夹)中进行。 ::: 要解决此问题,你需要: - 告知清单合并工具使用应用自身的备份相关属性值。 - 创建备份规则文件,将 Adapty 的规则与其他 SDK 的规则合并。 #### 1. 在清单中添加 `tools` 命名空间 \{#1-add-the-tools-namespace-to-your-manifest\} 在 `AndroidManifest.xml` 文件中,确保根标签 `<manifest>` 包含 tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. 在 `<application>` 中覆盖备份属性 \{#2-override-backup-attributes-in-application\} 在同一个 `AndroidManifest.xml` 文件中,更新 `<application>` 标签,使应用提供最终属性值,并告知清单合并工具替换库中的值: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` 如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\} 在 Android 项目的 `res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并。Android 根据系统版本使用不同的备份规则格式,同时创建两个文件可确保应用所支持的所有 Android 版本都能正常兼容。 :::note 以下示例以 AppsFlyer 作为第三方 SDK 的示例。请替换或添加你应用中实际使用的其他 SDK 的规则。 ::: **Android 12 及更高版本**(使用新的数据提取规则格式): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Android 11 及更低版本**(使用旧版完整备份内容格式): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important 在 Unity 中,请将上述更改应用到 `Assets/Plugins/Android/AndroidManifest.xml`,并在 `Assets/Plugins/Android/res/xml/` 目录下创建备份规则文件。 ::: #### Android 中从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app-in-android\} 如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会以错误的方式重建或复用该 Activity。这会导致购买结果丢失或被当作已取消处理。 为确保购买流程正常运行,请仅将启动购买流程的 Activity 的启动模式设置为 `standard` 或 `singleTop`,避免使用其他任何模式。 在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Android 上显示付费墙时应用崩溃 \{#app-crashes-when-a-paywall-is-displayed-on-android\} 如果应用在 Android 上显示付费墙时崩溃,可能是因为 Gradle 配置中缺少 Kotlin 插件。添加方法如下: 1. 在 **Player Settings** 中,确保已勾选 **Custom Launcher Gradle Template** 和 **Custom Base Gradle Template** 选项。 <img src="/assets/shared/img/kotlin-plugin1.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 将以下内容添加到 `/Assets/Plugins/Android/launcherTemplate.gradle`: ```groovy showLineNumbers apply plugin: 'com.android.application' // highlight-next-line apply plugin: 'kotlin-android' apply from: 'setupSymbols.gradle' apply from: '../shared/keepUnitySymbols.gradle' ``` 3. 将以下内容添加到 `/Assets/Plugins/Android/baseProjectTemplate.gradle`: ```groovy showLineNumbers plugins { // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version id 'com.android.application' version '8.3.0' apply false id 'com.android.library' version '8.3.0' apply false // highlight-next-line id 'org.jetbrains.kotlin.android' version '1.8.0' apply false **BUILD_SCRIPT_DEPS** } ``` --- # File: unity-quickstart-paywalls --- --- title: "通过 Unity SDK 中的付费墙启用购买功能" description: "了解如何在 Unity 应用中使用 Adapty SDK 展示付费墙。" --- 要启用应用内购买,您需要了解三个关键概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) 是定义要提供哪些产品的配置。在 Adapty 中,付费墙是检索产品的唯一方式,但这种设计让您无需修改应用代码即可更改产品组合、定价和优惠内容。 - [**版位**](placements) – 在应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。您在看板中为版位配置付费墙,然后在代码中通过版位 ID 请求它们。这使得运行 A/B 测试以及向不同用户展示不同付费墙变得更加简单。 Adapty 为您提供三种在应用中启用购买功能的方式。请根据应用需求选择其中一种: | 实现方式 | 复杂度 | 适用场景 | |---|---|---| | Adapty 付费墙编辑工具 | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可立即购买的付费墙](quickstart-paywalls)。Adapty 自动渲染付费墙,并在后台处理所有复杂的购买流程、收据验证和订阅管理。 | | 手动创建的付费墙 | 🟡 中等 | 您在应用代码中实现付费墙 UI,但仍从 Adapty 获取付费墙对象以保持产品组合的灵活性。请参阅[指南](unity-quickstart-manual)。 | | 观察者模式 | 🔴 困难 | 您已有自己的购买处理基础设施并希望继续使用。请注意,观察者模式在 Adapty 中有一定限制。请参阅[文章](observer-vs-full-mode)。 | :::important **以下步骤展示如何实现在 Adapty 付费墙编辑工具中创建的付费墙。** 如果您不想使用付费墙编辑工具,请参阅[处理手动创建付费墙中购买的指南](unity-making-purchases)。 ::: 要展示在 Adapty 付费墙编辑工具中创建的付费墙,在应用代码中您只需: 1. **获取付费墙**:从 Adapty 获取付费墙。 2. **展示付费墙,Adapty 将为您处理购买流程**:在应用中显示您获取到的付费墙容器。 3. **处理按钮操作**:将用户与付费墙的交互与应用的响应关联起来。例如,当用户点击按钮时打开链接或关闭付费墙。 ## 开始之前 \{#before-you-start\} 在开始之前,请完成以下步骤: 1. 在 Adapty 看板中将您的应用连接到 [App Store](initial_ios) 和/或 [Google Play](initial-android)。 2. 在 Adapty 中[创建产品](create-product)。 3. [创建付费墙并向其添加产品](create-paywall)。 4. [创建版位并将付费墙添加到其中](create-placement)。 5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-unity)。 :::tip 完成这些步骤最快的方式是按照[快速入门指南](quickstart)操作,或使用 [Developer CLI](developer-cli-quickstart) 创建付费墙和版位。 ::: ## 1. 获取付费墙 \{#1-get-the-paywall\} 您的付费墙与在看板中配置的版位关联。版位允许您为不同目标受众运行不同的付费墙,或运行 [A/B 测试](ab-tests)。 要获取在 Adapty 付费墙编辑工具中创建的付费墙,您需要: 1. 使用 `GetPaywall` 方法通过[版位](placements) ID 获取 `paywall` 对象,并使用 `HasViewConfiguration` 属性检查它是否是在编辑工具中创建的付费墙。 2. 使用 `CreatePaywallView` 方法创建付费墙视图。该视图包含展示付费墙所需的 UI 元素和样式。 :::important 要获取视图配置,您必须在付费墙编辑工具中开启 **Show on device** 开关。否则,您将获得空的视图配置,付费墙将无法显示。 ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if(error != null) { // handle the error return; } // Create paywall view parameters var parameters = new AdaptyUICreatePaywallViewParameters(); // Create the paywall view AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if(error != null) { // handle the error return; } // view - the paywall view ready to be presented }); }); ``` :::info 本快速入门提供展示付费墙所需的最低配置。有关高级配置详情,请参阅我们的[获取付费墙指南](unity-get-pb-paywalls)。 ::: ## 2. 展示付费墙 \{#2-display-the-paywall\} 现在,当您已获得付费墙配置后,只需添加几行代码即可展示付费墙。 要展示付费墙,请对由 `CreatePaywallView` 方法创建的 `view` 调用 `view.Present()` 方法。每个 `view` 只能使用一次。如果需要再次展示付费墙,请再次调用 `CreatePaywallView` 创建新的 `view` 实例。 ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::info 有关如何展示付费墙的更多详情,请参阅我们的[指南](unity-present-paywalls)。 ::: ## 3. 处理按钮操作 \{#3-handle-button-actions\} 当用户点击付费墙中的按钮时,Unity SDK 会自动处理购买和恢复操作。但是,其他按钮具有自定义或预定义的 ID,需要在您的代码中处理相应操作。 例如,您的付费墙可能有一个关闭按钮和需要打开的 URL(如使用条款和隐私政策)。要处理这些操作,您的类需要实现 `AdaptyPaywallsEventsListener` 接口并注册为监听器。 :::tip 请阅读我们关于如何处理按钮[操作](unity-handle-paywall-actions)和[事件](unity-handling-events)的指南。 ::: ```csharp showLineNumbers title="Unity" public class YourClass : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { // Register this class as the paywall events listener Adapty.SetPaywallsEventsListener(this); } // AdaptyPaywallsEventsListener method - handles button actions public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } } ``` ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试您的购买,以确保可以从付费墙完成测试购买。 接下来,您需要[检查用户的访问等级](unity-check-subscription-status),以确保向正确的用户展示付费墙或授予付费功能的访问权限。 ## 完整示例 \{#full-example\} 以下是如何将所有步骤整合到您的应用中的完整示例。 ```csharp showLineNumbers using System; using UnityEngine; using AdaptySDK; public class PaywallManager : MonoBehaviour, AdaptyPaywallsEventsListener { [SerializeField] private string placementId = "YOUR_PLACEMENT_ID"; private AdaptyUIPaywallView currentPaywallView; void Start() { // Register for paywall events Adapty.SetPaywallsEventsListener(this); GetAndDisplayPaywall(); } private void GetAndDisplayPaywall() { Adapty.GetPaywall(placementId, (paywall, error) => { if (error != null) { Debug.LogError("Error getting paywall: " + error.Message); return; } if (paywall.HasViewConfiguration) { CreateAndPresentPaywallView(paywall); } else { Debug.LogWarning("Paywall was not created using the builder"); } }); } private void CreateAndPresentPaywallView(AdaptyPaywall paywall) { var parameters = new AdaptyUICreatePaywallViewParameters(); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { Debug.LogError("Error creating paywall view: " + error.Message); return; } currentPaywallView = view; view.Present((presentError) => { if (presentError != null) { Debug.LogError("Error presenting paywall: " + presentError.Message); return; } Debug.Log("Paywall presented successfully"); }); }); } // AdaptyPaywallsEventsListener implementation public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: Debug.Log("Close button pressed"); view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } // Required interface methods (implement as needed) public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } public void PaywallViewDidSelectProduct(AdaptyUIPaywallView view, string productId) { } public void PaywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } public void PaywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult) { } public void PaywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } public void PaywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } public void PaywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void ShowPaywall() { GetAndDisplayPaywall(); } void OnDestroy() { if (currentPaywallView != null) { currentPaywallView.Dismiss(null); } } } ``` --- # File: unity-check-subscription-status --- --- title: "在 Unity SDK 中检查订阅状态" description: "了解如何在 Unity 应用中使用 Adapty 检查订阅状态。" --- 要判断用户是否可以访问付费内容或查看付费墙,您需要在用户画像中检查其[访问等级](access-level)。 本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。 ## 获取订阅状态 \{#get-subscription-status\} 当您需要决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种选择: - 如果需要立即获取最新的用户画像数据(例如在应用启动时)或希望强制更新,请调用 `GetProfile`。 - 设置**自动用户画像更新**,以在订阅状态发生变化时自动刷新本地副本。 ### 获取用户画像 \{#get-profile\} 获取订阅状态最简单的方法是使用 `GetProfile` 方法访问用户画像: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` ### 监听订阅更新 \{#listen-to-subscription-updates\} 要在应用中自动接收用户画像更新: 1. 继承 `AdaptyEventListener` 并实现 `OnLoadLatestProfile` 方法——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。 2. 在该方法被调用时存储更新后的用户画像数据,以便在整个应用中使用,无需发起额外的网络请求。 ```csharp public class SubscriptionManager : MonoBehaviour, AdaptyEventListener { private AdaptyProfile currentProfile; void Start() { // Register this object as an Adapty event listener Adapty.SetEventListener(this); } // Store the profile when it updates public void OnLoadLatestProfile(AdaptyProfile profile) { currentProfile = profile; // Update UI, unlock content, etc. } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } // Use stored profile instead of calling getProfile() public bool HasAccess() { if (currentProfile?.AccessLevels != null && currentProfile.AccessLevels.ContainsKey("premium")) { return currentProfile.AccessLevels["premium"].IsActive; } return false; } } ``` :::note 每当应用启动时,Adapty 会自动调用 `OnLoadLatestProfile`,即使设备处于离线状态,也能提供缓存的订阅数据。 ::: ## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\} 当您需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。此方法适用于以下场景:应用启动、进入付费区域,或在展示特定内容之前。 ```csharp private void CheckAccessLevel() { Adapty.GetProfile((profile, error) => { if (error != null) { Debug.LogError("Error checking access level: " + error.Message); // Show paywall if access check fails return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel == null || !accessLevel.IsActive) { // Show paywall if no access } }); } private void InitializePaywall() { LoadPaywall(); CheckAccessLevel(); } ``` ## 后续步骤 \{#next-steps\} 现在您已了解如何追踪订阅状态,接下来请学习如何[使用用户画像](unity-quickstart-identify),以确保用户能够访问其已付费的内容。 --- # File: unity-quickstart-identify --- --- title: "在 Unity SDK 中识别用户" description: "在 Unity 中设置 Adapty 进行应用内订阅管理的快速入门指南。" --- :::important 本指南适用于有自己身份验证系统的开发者。你将了解如何在 Adapty 中管理用户画像,使其与你现有的身份验证系统保持一致。 ::: 用户购买行为的管理方式取决于你的应用身份验证模型: - 如果你的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户部分](#anonymous-users)。 - 如果你的应用已有(或将有)后端身份验证,请参阅[已识别用户部分](#identified-users)。 **核心概念**: - **用户画像**是 SDK 运行所必需的实体,由 Adapty 自动创建。 - 用户画像可以是匿名的**(不含 customer user ID)**,也可以是已识别的**(含 customer user ID)**。 - 您提供 **customer user ID** 是为了将 Adapty 中的用户画像与您内部的身份认证系统进行关联。 以下是匿名用户与已识别用户的区别: | | 匿名用户 | 已识别用户 | |-------------------------|-----------------------------------|-----------------------------------------------| | **购买管理** | 通过应用商店恢复购买 | 通过客户用户 ID 跨设备保留购买历史 | | **用户画像管理** | 每次重新安装都会创建新的用户画像 | 跨会话和设备共享同一用户画像 | | **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用重新安装后仍可保留 | ## 匿名用户 \{#anonymous-users\} 如果你没有后端身份验证,**则无需在应用代码中处理身份验证**: 1. 当 SDK 在应用首次启动时激活,Adapty 会**为该用户创建一个新的用户画像**。 2. 当用户在应用内购买任何商品时,该购买记录会**关联到其 Adapty 用户画像及其应用商店账户**。 3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty 会**在激活时创建一个新的匿名用户画像**。 4. 如果用户之前在您的应用中有过购买记录,默认情况下,SDK 激活时会自动从 App Store 同步其购买历史。 因此,对于匿名用户,每次安装都会创建新的用户画像,但这不是问题,因为在 Adapty 分析中,你可以[配置什么会被视为新安装](general#4-installs-definition-for-analytics)。 对于匿名用户,你需要按**设备 ID** 统计安装量。在这种情况下,设备上的每次应用安装都会被计为一次安装,包括重新安装。 ## 已识别用户 \{#identified-users\} 您有两种方式在应用中识别用户: - [**在登录/注册时:**](#during-loginsignup) 如果用户在应用启动后才登录,请在他们完成身份验证时调用 `identify()`,并传入 customer user ID。 - [**在 SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时已有存储的 customer user ID,请在调用 `activate()` 时直接传入。 :::important 默认情况下,当 Adapty 收到来自某个 Customer User ID 的购买请求,而该 ID 当前已与另一个 Customer User ID 关联时,访问等级将被共享,两个用户画像都拥有付费访问权限。你可以将此设置配置为将付费访问权从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[文章](general#6-sharing-paid-access-between-user-accounts)。 ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 登录/注册期间 \{#during-loginsignup\} 如果你在应用启动后才识别用户身份(例如用户登录或注册之后),请使用 `identify` 方法设置其 customer user ID。 - 如果你**之前从未使用过该 customer user ID**,Adapty 会自动将其关联到当前用户画像。 - 如果你**之前已使用该 customer user ID 识别过该用户**,Adapty 会切换到与该 customer user ID 关联的用户画像。 :::important Customer user ID 对每个用户必须唯一。如果将该参数硬编码为固定值,所有用户将被视为同一人。 ::: 在调用其他 SDK 方法之前,请等待 `Identify` 的完成回调。并发调用会产生 `#3006 profileWasChanged` 错误,或导致操作落到匿名用户画像上。详见 [Unity SDK 调用顺序](unity-sdk-call-order)。 ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` ### 在 SDK 激活期间 \{#during-the-sdk-activation\} 如果在激活 SDK 时已经知道用户 ID,可以直接在 `activate` 方法中传入,无需单独调用 `identify`。 如果知道用户 ID,但在激活后才进行设置,那么在激活时 Adapty 会先创建一个匿名用户画像,等到调用 `identify` 后才会切换到已有的用户画像。 您可以传入已有的客户用户 ID(即之前使用过的 ID),也可以传入新的 ID。若传入新 ID,激活时创建的新用户画像将自动与该客户用户 ID 关联。 :::note 默认情况下,创建匿名用户画像不会影响分析看板,因为安装量是基于设备 ID 来统计的。 设备 ID 代表从应用商店在设备上安装的一次应用实例,仅在重新安装应用后才会重新生成。 它与首次安装还是重复安装无关,也与是否使用了已有的客户用户 ID 无关。 创建用户画像(在 SDK 激活或退出登录时)、登录,或在不重新安装应用的情况下升级应用,均不会产生额外的安装事件。 如果您希望根据唯一用户而非设备来统计安装量,请前往 **App settings**,配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。 ::: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); // 每个用户的 Customer User ID 必须唯一。如果硬编码该参数值,所有用户将被视为同一个人。 Adapty.Activate(builder.Build(), (error) => { if (error != null) { // 处理错误 return; } }); ``` ### 用户退出登录 \{#log-users-out\} 如果您有供用户退出登录的按钮,请使用 `logout` 方法。 :::important 用户退出登录会为用户创建一个新的匿名用户画像。 ::: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` :::info 要让用户重新登录应用,请使用 `identify` 方法。 ::: ### 允许未登录状态下进行购买 \{#allow-purchases-without-login\} 如果用户在登录前后都可以进行购买,你需要确保他们登录后仍能保留访问权限: 1. 当未登录用户发起购买时,Adapty 会将其关联到该用户的匿名用户画像 ID。 2. 当用户登录账号后,Adapty 会切换到使用其已识别的用户画像。 - 如果是新的 customer user ID(例如购买发生在注册之前),Adapty 会将该 customer user ID 分配给当前用户画像,从而保留所有购买历史记录。 - 如果是已存在的 customer user ID(该 customer user ID 已关联到某个用户画像),则需要在用户画像切换后获取实际的访问等级。你可以在识别完成后立即调用 [`getProfile`](unity-check-subscription-status),或[监听用户画像更新](unity-check-subscription-status)以使数据自动同步。 ## 下一步 \{#next-steps\} 恭喜你!你已经在应用中成功实现了应用内付费逻辑!祝你的应用变现一切顺利! 想从 Adapty 获得更多价值,可以进一步探索以下内容: - [**测试**](troubleshooting-test-purchases):确保一切按预期运行 - [**用户引导**](onboardings):通过用户引导吸引用户并提升留存 - [**集成**](configuration):只需一行代码即可与营销归因和数据分析服务完成集成 - [**设置自定义用户画像属性**](unity-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,从而发起 A/B 测试或向不同用户展示不同的付费墙 --- # File: adapty-sdk-integration-skill-unity --- --- title: "通过 SDK 集成技能将 Adapty 接入 Unity 应用" description: "使用 adapty-sdk-integration 技能,借助 AI 编码工具将 Adapty SDK 端到端集成到你的 Unity 应用中。" --- [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 :::important 该功能目前处于测试阶段。如果遇到卡顿或异常情况,请参考[分步集成指南](adapty-cursor-unity)——它会引导你的 AI 工具逐步完成每个阶段的正确文档操作。 ::: [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。 **支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。 安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。 **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` 也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。 安装完成后,在项目中运行该 skill: ``` /adapty-sdk-integration ``` skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。 --- # File: adapty-cursor-unity --- --- title: "借助 AI 将 Adapty 集成到 Unity 应用" description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具,将 Adapty 集成到 Unity 应用的分步指南。" --- 本指南将带你一步一步地将 Adapty 集成到你的 Unity 应用中,借助 AI 编程工具——按正确的顺序向它提供合适的 Adapty 文档即可。 For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## 开始之前:看板配置 \{#before-you-start-dashboard-setup\} Adapty 在您编写任何 SDK 代码之前,需要先完成一些看板配置。您可以通过交互式 LLM 技能,或手动通过看板来完成配置。 ### Skill 方法 \{#skill-approach\} Adapty CLI skill 让你的 LLM 直接设置应用、产品、访问等级、付费墙和版位,无需为每个步骤打开看板。你只需要在看板中[连接你的商店](integrate-payments)。 ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` 添加 skill 后,在你的 agent 中运行 `/adapty-cli`。它将引导你完成每个步骤,包括何时需要打开看板连接你的商店。 ### 看板配置方式 \{#dashboard-approach\} 如果你倾向于手动配置所有内容,以下是编写代码前需要准备的信息。LLM 无法自动查找看板中的配置值,需要你手动提供。 1. **连接应用商店**:在 Adapty 看板中,前往 **App settings → General**,连接 App Store 和 Google Play(如果你的 Unity 应用同时支持两个平台)。这是购买功能正常运行的必要条件。 [连接应用商店](integrate-payments) 2. **复制您的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这是您传递给 Adapty 配置构建器的字符串。 3. **至少创建一个产品**:在 Adapty 看板中,前往 **Products** 页面。您无需在代码中直接引用产品——Adapty 会通过付费墙来分发它们。 [添加产品](quickstart-products) 4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配到一个版位。在代码中,版位 ID 就是传递给 `Adapty.GetPaywall("YOUR_PLACEMENT_ID")` 的字符串。 [创建付费墙](quickstart-paywalls) 5. **设置访问等级**:在 Adapty 看板的 **Products** 页面中按产品进行配置。在代码中,通过 `profile.AccessLevels["premium"]?.IsActive` 检查对应字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户根据所购产品获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编码前[创建额外的访问等级](assigning-access-level-to-a-product)。 :::tip 准备好这五项信息后,就可以开始写代码了。告诉你的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",这样它就能生成正确的初始化和付费墙获取代码。 ::: ### 准备就绪后进行设置 \{#set-up-when-ready\} 这些内容不是开始编码的必要条件,但随着集成的成熟,你会需要它们: - **A/B 测试**:在 **Placements** 页面进行配置,无需更改代码。 [A/B 测试](ab-tests) - **更多付费墙和版位**:添加更多使用不同版位 ID 的 `GetPaywall` 调用。 - **分析集成**:在 **Integrations** 页面进行配置,具体设置因集成而异。请参阅[分析集成](analytics-integration)和[归因集成](attribution-integration)。 ## 将 Adapty 文档输入到您的 LLM \{#feed-adapty-docs-to-your-llm\} ### 使用 Context7(推荐) [Context7](https://context7.com) 是一个 MCP 服务器,让你的 LLM 可以直接访问最新的 Adapty 文档。LLM 会根据你的提问自动获取相关文档,无需手动粘贴 URL。 Context7 支持 **Cursor**、**Claude Code**、**Windsurf** 以及其他兼容 MCP 的工具。运行以下命令即可完成配置: ``` npx ctx7 setup ``` 该命令会自动检测你的编辑器并配置 Context7 服务器。如需手动配置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。 配置完成后,在提示词中引用 Adapty 库: ``` Use the adaptyteam/adapty-docs library to look up how to install the Unity SDK ``` :::warning 虽然 Context7 无需手动粘贴文档链接,但实现顺序很重要。请按照下方的[实现步骤](#implementation-walkthrough)逐步操作,以确保一切正常运行。 ::: ### 使用纯文本文档 \{#use-plain-text-docs\} 您可以以纯文本 Markdown 格式访问任意 Adapty 文档。只需在 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-unity.md](https://adapty.io/docs/zh/adapty-cursor-unity.md)。 下方[实施演练](#implementation-walkthrough)中的每个阶段都包含一个"Send this to your LLM"区块,其中附有可粘贴的 `.md` 链接。 如需一次性获取更多文档,请参阅下方的[索引文件与平台专属子集](#plain-text-doc-index-files)。 ## 实施演练 \{#implementation-walkthrough\} 本指南的其余部分按实施顺序介绍 Adapty 集成流程。每个阶段包含需要发送给 LLM 的文档、完成后应看到的效果以及常见问题。 ### 规划集成方案 \{#plan-your-integration\} 在开始写代码之前,先让 LLM 分析你的项目并制定实现计划。如果你的 AI 工具支持规划模式(例如 Cursor 或 Claude Code 的计划模式),建议先使用该模式,让 LLM 在生成代码前同时读取你的项目结构和 Adapty 文档。 告诉 LLM 你使用的购买方式——这会决定它应该参考哪些指南: - [**Adapty 付费墙编辑工具**](adapty-paywall-builder):在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染。 - [**手动创建付费墙**](unity-making-purchases):自行编写付费墙 UI 代码,但仍使用 Adapty 获取产品并处理购买流程。 - [**观察者模式**](observer-vs-full-mode):保留现有的购买基础设施,仅使用 Adapty 进行数据分析和集成。 不确定该选哪种?请查看[快速入门中的对比表格](unity-quickstart-paywalls)。 ### 安装并配置 SDK \{#install-and-configure-the-sdk\} 通过 Unity Package Manager 添加 Adapty SDK 包,并使用你的公共 SDK 密钥激活它。这是一切的基础——没有它,其他功能都无法正常工作。 **指南:** [安装并配置 Adapty SDK](sdk-installation-unity) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/sdk-installation-unity.md ``` :::tip[Checkpoint] - **预期结果:** 项目成功构建并运行,Unity 控制台显示 Adapty 激活日志。 - **常见问题:** "Public API key is missing" → 检查是否已将占位符替换为 **App settings** 中的真实密钥。 ::: ### 展示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\} 通过版位 ID 获取付费墙、展示付费墙并处理购买事件。具体需要参考哪些指南,取决于你处理购买的方式。 每完成一步后,请在沙盒中测试购买流程,不要等到最后再测试。沙盒配置说明请参见[在沙盒中测试购买](test-purchases-in-sandbox)。 <Tabs groupId="paywall-approach"> <TabItem value="builder" label="付费墙编辑工具" default> **指南:** - [使用付费墙启用购买(快速入门)](unity-quickstart-paywalls) - [获取付费墙编辑工具付费墙及其配置](unity-get-pb-paywalls) - [展示付费墙](unity-present-paywalls) - [处理付费墙事件](unity-handling-events) - [响应按钮操作](unity-handle-paywall-actions) 请将以下内容发送给您的 LLM: ``` 在编写代码之前,请先阅读以下 Adapty 文档: - https://adapty.io/docs/zh/unity-quickstart-paywalls.md - https://adapty.io/docs/zh/unity-get-pb-paywalls.md - https://adapty.io/docs/zh/unity-present-paywalls.md - https://adapty.io/docs/zh/unity-handling-events.md - https://adapty.io/docs/zh/unity-handle-paywall-actions.md ``` :::tip[检查点] - **预期效果:** 付费墙正常显示并包含已配置的产品。点击产品后触发沙盒购买弹窗。 - **注意事项:** 付费墙为空或出现 `GetPaywall` 错误 → 请确认版位 ID 与看板中完全一致,且该版位已分配目标受众。 ::: </TabItem> <TabItem value="manual" label="手动付费墙"> **指南:** - [在自定义付费墙中启用购买功能(快速入门)](unity-quickstart-manual) - [获取付费墙和产品](fetch-paywalls-and-products-unity) - [渲染通过远程配置设计的付费墙](present-remote-config-paywalls-unity) - [进行购买](unity-making-purchases) - [恢复购买](unity-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/zh/unity-quickstart-manual.md - https://adapty.io/docs/zh/fetch-paywalls-and-products-unity.md - https://adapty.io/docs/zh/present-remote-config-paywalls-unity.md - https://adapty.io/docs/zh/unity-making-purchases.md - https://adapty.io/docs/zh/unity-restore-purchase.md :::tip[检查点] - **预期效果:** 自定义付费墙显示从 Adapty 获取的产品。点击产品后触发沙盒购买弹窗。 - **常见问题:** 产品数组为空 → 请确认付费墙已在看板中分配产品,且版位已设置目标受众。 ::: </TabItem> <TabItem value="observer" label="Observer mode"> **相关指南:** - [Observer 模式概览](observer-vs-full-mode) - [实现 Observer 模式](implement-observer-mode-unity) - [在 Observer 模式中上报交易](report-transactions-observer-mode-unity) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/observer-vs-full-mode.md - https://adapty.io/docs/zh/implement-observer-mode-unity.md - https://adapty.io/docs/zh/report-transactions-observer-mode-unity.md ``` :::tip[检查点] - **预期结果:** 使用现有购买流程完成沙盒购买后,该交易会出现在 Adapty 看板的 **Event Feed** 中。 - **注意事项:** 没有事件 → 请确认你已向 Adapty 上报交易,并已为两个应用商店配置服务器通知。 ::: </TabItem> </Tabs> ### 检查订阅状态 \{#check-subscription-status\} 购买完成后,检查用户画像中是否存在有效的访问等级,以控制对高级内容的访问权限。 **指南:** [检查订阅状态](unity-check-subscription-status) 将以下内容发送给您的大语言模型: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/unity-check-subscription-status.md ``` :::tip[检查点] - **预期结果:** 在沙盒购买完成后,`profile.AccessLevels["premium"]?.IsActive` 返回 `true`。 - **注意事项:** 购买后 `AccessLevels` 为空 → 请检查该产品是否已在看板中分配了访问等级。 ::: ### 识别用户 \{#identify-users\} 将应用的用户账号与 Adapty 用户画像关联,确保购买记录在多设备间同步。 :::important 如果你的应用无需登录,可跳过此步骤。 ::: **指南:**[识别用户](unity-quickstart-identify) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/zh/unity-quickstart-identify.md ``` :::tip[Checkpoint] - **预期效果:** 调用 `Adapty.Identify("your-user-id")` 后,看板的 **Profiles** 部分会显示你的自定义用户 ID。 - **注意事项:** 请在激活之后、获取付费墙之前调用 `Identify`,以避免匿名用户画像归因问题。 ::: ### 准备发布 \{#prepare-for-release\} 沙盒中的集成测试通过后,请按照发布检查清单逐项确认,确保一切都已准备好上线。 **指南:** [发布检查清单](release-checklist) 将以下内容发送给你的 LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/zh/release-checklist.md ``` :::tip[Checkpoint] - **预期结果:** 所有检查项均已确认:商店连接、服务器通知、购买流程、访问等级检查以及隐私要求。 - **常见问题:** 缺少服务器通知 → 请在 **App settings → iOS SDK** 中配置 App Store 服务器通知,并在 **App settings → Android SDK** 中配置 Google Play 实时开发者通知。 ::: ## 纯文本文档索引文件 \{#plain-text-doc-index-files\} 如果你需要为 LLM 提供超出单个页面范围的更广泛上下文,我们提供了列出或汇总所有 Adapty 文档的索引文件: - [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面的 `.md` 链接。这是一项[新兴标准](https://llmstxt.org/),旨在让网站对 LLM 更易访问。请注意,对于某些 AI 助手(如 ChatGPT),你需要先下载 `llms.txt`,再将其作为文件上传到对话中。 - [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站合并为单个文件。体积较大——仅在需要完整内容时使用。 - Unity 专用的 [`unity-llms.txt`](https://adapty.io/docs/zh/unity-llms.txt) 和 [`unity-llms-full.txt`](https://adapty.io/docs/zh/unity-llms-full.txt):平台专属子集,相比完整站点可节省 token 消耗。 --- # File: unity-get-pb-paywalls --- --- title: "在 Unity SDK 中获取付费墙编辑工具付费墙及其配置" description: "了解如何在 Adapty 中检索付费墙编辑工具付费墙,以便更好地控制 Unity 应用中的订阅。" --- 在 [Adapty 看板中使用新版付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,您可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体步骤如下。 :::warning 新版付费墙编辑工具需要 Unity SDK 3.3.0 或更高版本。 ::: 请注意,本文介绍的是使用付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中为远程配置付费墙获取付费墙和产品](fetch-paywalls-and-products-unity)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中展示付费墙之前(点击展开)</summary> 1. 在 Adapty 看板中[创建产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。 4. 在你的移动应用中安装 [Adapty SDK](sdk-installation-unity)。 </details> ## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\} 如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动应用代码中手动渲染并展示给用户。这类付费墙已包含展示内容和展示方式的完整配置。不过,你仍需通过版位获取其 ID 和视图配置,然后在移动应用中将其呈现出来。 为确保最佳性能,请尽早获取付费墙及其[视图配置](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前留出足够时间完成图片下载。 使用 `GetPaywall` 方法获取付费墙: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` 参数: | 参数 | 是否必填 | 说明 | |---------|--------|-----------| | **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时所指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为语言代码,由一个或两个子标签组成,中间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关语言代码及推荐使用方式,请参阅[本地化与语言代码](localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存存在时直接返回缓存数据。这样用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清除时才会被清空。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加快付费墙的加载速度,并在 CDN 不可用时启用独立的备用服务器。该机制旨在确保你始终获取最新版本的付费墙,同时在网络连接受限的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值用于限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。</p><p>请注意,在少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为底层操作可能包含多个请求。</p> | 响应参数: | 参数 | 说明 | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 | ## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important 请确保在付费墙编辑工具中启用了 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。 ::: 获取付费墙后,检查其是否包含 `ViewConfiguration`——该字段表明该付费墙是通过付费墙编辑工具创建的,并将指引你如何展示该付费墙。如果存在 `ViewConfiguration`,则将其视为付费墙编辑工具付费墙;如果不存在,则[将其作为远程配置付费墙处理](present-remote-config-paywalls-unity)。 在 Unity SDK 中,直接调用 `CreatePaywallView` 方法,无需手动获取视图配置。 :::warning `CreatePaywallView` 方法的返回结果只能使用一次。如需再次使用,请重新调用 `CreatePaywallView` 方法。若不重新创建而直接调用两次,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```csharp showLineNumbers var parameters = new AdaptyUICreatePaywallViewParameters() .SetPreloadProducts(preloadProducts) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` 参数: | 参数 | 是否必填 | 描述 | | :------------------ | :----------------- | :----------------------------------------------------------- | | **paywall** | 必填 | 一个 `AdaptyPaywall` 对象,用于获取目标付费墙的控制器。 | | **loadTimeout** | 默认值:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能包含多个请求。 | | **PreloadProducts** | 可选 | 提供一个 `AdaptyPaywallProducts` 数组,以优化产品在屏幕上的显示时机。若传入 `nil`,AdaptyUI 将自动获取所需产品。 | | **CustomTags** | 可选 | 定义一个自定义标签及其解析值的字典。自定义标签在付费墙内容中充当占位符,会被动态替换为特定字符串,从而在付费墙中实现个性化内容。详情请参阅付费墙编辑工具中的自定义标签相关说明。 | | **CustomTimers** | 可选 | 定义一个自定义计时器及其结束日期的字典。自定义计时器允许您在付费墙中展示倒计时。 | :::note 如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码([点击此处了解](localizations-and-locale-codes))。 ::: 获取视图后,[展示付费墙](unity-present-paywalls)。 ## 自定义资源 \{#customize-assets\} 要自定义付费墙中的图片和视频,请实现自定义资源。 主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你通过这些 ID 来定位并自定义相应元素的行为。 对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。 例如,你可以: - 为部分用户展示不同的图片或视频。 - 在远程主图加载时显示本地预览图。 - 在播放视频前先显示预览图。 :::important 要使用此功能,请将 Adapty Unity SDK 更新至 3.8.0 或更高版本。 ::: 以下是如何通过简单字典提供自定义资源的示例: ```csharp showLineNumbers var customAssets = new Dictionary<string, AdaptyCustomAsset> { { "custom_image", AdaptyCustomAsset.LocalImageFile("custom_assets/images/custom_image.png") }, { "hero_video", AdaptyCustomAsset.LocalVideoFile("custom_assets/videos/custom_video.mp4") } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomAssets(customAssets) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` :::note 如果找不到对应资源,付费墙将回退到默认外观。 ::: ## 设置开发者自定义计时器 \{#set-up-developer-defined-timers\} 要在 Unity 应用中使用自定义计时器,可以直接向 `SetCustomTimers` 方法传入一个包含计时器 ID 及其结束时间的字典。示例如下: ```csharp showLineNumbers var customTimers = new Dictionary<string, DateTime> { { "CUSTOM_TIMER_6H", DateTime.Now.AddHours(6) }, { "CUSTOM_TIMER_NY", new DateTime(2025, 1, 1) } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomTimers(customTimers) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` 在此示例中,`CUSTOM_TIMER_NY` 和 `CUSTOM_TIMER_6H` 是您在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。计时器解析器确保您的应用为每个计时器动态更新正确的值。例如: - `CUSTOM_TIMER_NY`:距计时器结束时间(如元旦)的剩余时间。 - `CUSTOM_TIMER_6H`:从用户打开付费墙时开始的 6 小时倒计时的剩余时间。 ## 通过默认受众付费墙加速付费墙加载 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙的加载几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络连接较差,付费墙的加载时间可能会超出预期。在这种情况下,你可能希望先展示一个默认付费墙,以保证流畅的用户体验,而不是让用户看到空白页面。 要解决此问题,您可以使用 `GetPaywallForDefaultAudience` 方法,该方法会获取指定版位中**All Users**目标受众的付费墙。但请务必了解,推荐的方式是通过 `getPaywall` 方法获取付费墙,详情请参阅上方的[获取付费墙](#fetch-paywall)部分。 :::warning 建议使用 `GetPaywall` 而非 `GetPaywallForDefaultAudience`,因为后者存在以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要采用向后兼容的设计,否则旧版本可能显示异常。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法根据国家、归因或自定义属性进行定向。 如果更快的获取速度对你的场景而言比这些缺点更重要,请按以下方式使用 `GetPaywallForDefaultAudience`。否则,请使用上文[介绍的](#fetch-paywall) `GetPaywall`。 ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` 参数: | 参数 | 是否必填 | 说明 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 目标[版位](placements)的标识符。即你在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>付费墙本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,如果失败则返回缓存数据。我们推荐使用此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`——在缓存数据存在时优先返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。</p><p></p><p>请注意,缓存在重启应用后仍会保留,只有在重新安装应用或手动清除时才会被清空。</p><p></p><p>Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及备用付费墙。我们还使用 CDN 加速付费墙的获取,并配备了独立的备用服务器以应对 CDN 不可用的情况。整套系统旨在确保你始终能获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。</p> | --- # File: unity-present-paywalls --- --- title: "展示付费墙" description: "了解如何使用 Adapty SDK 在 Unity 应用中展示付费墙。" --- 如果你已经使用付费墙编辑工具自定义了付费墙,则无需在移动端代码中手动处理渲染逻辑来向用户展示它。这类付费墙已包含展示内容和展示方式的完整配置。 :::warning 本指南适用于**新版付费墙编辑工具**,需要 Adapty SDK 3.3.0 或更高版本。 如需展示远程配置付费墙,请参阅[渲染通过远程配置设计的付费墙](present-remote-config-paywalls)。 ::: 要展示付费墙,请对通过 [`CreatePaywallView`](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) 方法创建的 `view` 调用 `view.Present()` 方法。每个 `view` 只能使用一次。如需再次展示同一付费墙,请重新调用 `CreatePaywallView` 以创建新的 `view` 实例。 :::warning 复用同一个 `view` 而不重新创建,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 显示对话框 \{#show-dialog\} 当付费墙视图在 Android 上展示时,请使用此方法代替原生弹窗。在 Android 上,普通弹窗会显示在付费墙视图后方,导致用户看不到。该方法可确保在所有平台上对话框正确显示于付费墙之上。 ```csharp showLineNumbers title="Unity" var dialog = new AdaptyUIDialogConfiguration() .SetTitle("Close paywall?") .SetContent("You will lose access to exclusive offers.") .SetDefaultActionTitle("Stay") .SetSecondaryActionTitle("Close"); AdaptyUI.ShowDialog(view, dialog, (action, error) => { if (error == null) { if (action == AdaptyUIDialogActionType.Secondary) { // User confirmed - close the paywall view.Dismiss(); } // If primary - do nothing, user stays } }); ``` ## 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过向 `Present()` 方法传入 `iosPresentationStyle` 参数来配置付费墙在 iOS 上的展示方式。该参数接受 `AdaptyUIIOSPresentationStyle.FullScreen`(默认值)或 `AdaptyUIIOSPresentationStyle.PageSheet`。 ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` --- # File: unity-handle-paywall-actions --- --- title: "在 Unity SDK 中响应按钮操作" description: "使用 Adapty 在 Unity 中处理付费墙按钮操作,提升应用变现效果。" --- 如果您正在使用 Adapty 付费墙编辑工具构建付费墙,正确设置按钮至关重要: 1. 在付费墙编辑工具中[添加按钮](paywall-buttons),并为其分配预设操作或创建自定义操作 ID。 2. 在您的应用代码中编写处理每个已分配操作的逻辑。 本指南介绍如何在代码中处理自定义操作和预设操作。 :::warning **只有购买和恢复操作会被自动处理。** 其他所有按钮操作(例如关闭付费墙或打开链接)都需要在应用代码中实现相应的响应逻辑。 ::: ## 关闭付费墙 \{#close-paywalls\} 要添加一个可关闭付费墙的按钮: 1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。 2. 在您的应用代码中,为 `close` 操作实现一个处理程序,用于关闭付费墙。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; default: // handle other events break; } } ``` ## 从付费墙打开 URL \{#open-urls-from-paywalls\} :::tip 如果您想添加一组链接(例如使用条款和购买恢复),可以在付费墙编辑工具中添加 **Link** 元素,并以与带有 **Open URL** 操作的按钮相同的方式进行处理。 ::: 要添加一个从付费墙打开链接的按钮(例如**使用条款**或**隐私政策**): 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 操作,并输入您想打开的 URL。 2. 在您的应用代码中,为 `openUrl` 操作实现一个处理程序,用于在浏览器中打开收到的 URL。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if(!string.IsNullOrWhiteSpace(urlString)) { Application.OpenURL(urlString); } break; default: // handle other events break; } } ``` ## 登录应用 \{#log-into-the-app\} 要添加一个让用户登录应用的按钮: 1. 在付费墙编辑工具中,添加一个按钮,并为其分配 ID 为 `login` 的 **Custom** 操作。 2. 在您的应用代码中,为 `login` 自定义操作实现一个处理程序,用于识别您的用户身份。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "login") { // Navigate to login scene SceneManager.LoadScene("LoginScene"); } break; default: // handle other events break; } } ``` ## 处理自定义操作 \{#handle-custom-actions\} 要添加一个处理其他任意操作的按钮: 1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并设置一个 ID。 2. 在您的应用代码中,为您创建的操作 ID 实现相应的处理程序。 例如,如果您有另一套订阅优惠或一次性购买,可以添加一个按钮来显示另一个付费墙: ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "openNewPaywall") { // Display another paywall ShowAlternativePaywall(); } break; default: // handle other events break; } } private void ShowAlternativePaywall() { // Implement your logic to show alternative paywall } ``` --- # File: unity-handling-events --- --- title: "处理付费墙事件" description: "了解如何使用 Adapty SDK 在 Unity 应用中处理付费墙事件。" --- :::important 本指南涵盖购买、恢复、产品选择以及付费墙渲染的事件处理。你还必须实现按钮处理(关闭付费墙、打开链接等)。详情请参阅[处理按钮操作指南](unity-handle-paywall-actions)。 ::: 使用[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复操作。但它们会触发一些事件供应用响应,包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上购买相关操作的通知。以下介绍如何响应这些事件。 :::warning 本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.3.0 或更高版本。 ::: :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 处理事件 \{#handling-events\} 要控制或监控应用付费墙界面上发生的流程,请实现 `AdaptyPaywallsEventsListener` 接口: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; public class PaywallEventsHandler : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { Adapty.SetPaywallsEventsListener(this); } // Implement all required interface methods below } ``` ### 用户触发的事件 \{#user-generated-events\} #### 付费墙已显示 \{#paywall-appeared\} 当付费墙视图呈现到屏幕上时触发。 :::note 在 iOS 上,当用户点击付费墙内的[网页付费墙按钮](web-paywall#step-2a-add-a-web-purchase-button)并在应用内浏览器中打开网页付费墙时,也会触发此事件。 ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### 付费墙已消失 \{#paywall-disappeared\} 当付费墙视图从屏幕上关闭时触发。 :::note 在 iOS 上,当从付费墙打开的[网页付费墙](web-paywall#step-2a-add-a-web-purchase-button)在应用内浏览器中消失时,也会触发此事件。 ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### 产品选择 \{#product-selection\} 当用户或系统选择要购买的产品时触发。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidSelectProduct( AdaptyUIPaywallView view, string productId ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### 开始购买 \{#started-purchase\} 当用户发起购买流程时触发。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### 购买成功、已取消或待处理 \{#successful-canceled-or-pending-purchase\} 如果购买成功、用户取消购买,或购买处于待处理状态,此方法将被调用。用户取消和待处理付款(例如需要家长批准)会触发此方法,而不是 `PaywallViewDidFailPurchase`。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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": "UserCancelled" } } // 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" } } ``` </Details> 在这种情况下,我们建议关闭该界面。 #### 购买失败 \{#failed-purchase\} 如果购买因错误而失败,此方法将被调用。这包括 StoreKit/Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消会触发 `PaywallViewDidFinishPurchase` 并返回已取消结果,待处理付款不会触发此方法。 ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } } } ``` </Details> #### 开始恢复 \{#started-restore\} 当用户发起恢复流程时触发: ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### 恢复成功 \{#successful-restore\} 当购买恢复成功时触发: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishRestore( AdaptyUIPaywallView view, AdaptyProfile profile ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "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" } ] } } ``` </Details> 如果用户已拥有所需的 `accessLevel`,我们建议关闭该界面。请参阅[订阅状态](unity-listen-subscription-changes)了解如何检查。 #### 恢复失败 \{#failed-restore\} 当购买恢复失败时触发: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRestore( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### 网页支付导航完成 \{#finished-web-payment-navigation\} 尝试打开[网页付费墙](web-paywall)进行购买后(无论成功还是失败),此方法将被调用: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishWebPaymentNavigation( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` **参数:** - `product`:已打开(或尝试打开)网页付费墙的产品 - `error`:网页付费墙成功打开时为 `null`,失败时为 `AdaptyError` <Details> <summary>事件示例(点击展开)</summary> ```javascript // 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": "wrong_param", "message": "Current method is not available for this product", "details": { "underlyingError": "Product not configured for web purchases" } } } ``` </Details> ### 数据获取与渲染 \{#data-fetching-and-rendering\} #### 产品加载错误 \{#product-loading-errors\} 当产品加载失败时触发,并提供 `AdaptyError`。如果你在初始化时未传入产品数组,AdaptyUI 会自行从服务器获取所需对象。该操作可能失败,AdaptyUI 将通过调用此方法报告错误: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailLoadingProducts( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### 渲染错误 \{#rendering-errors\} 当界面渲染过程中发生错误时触发,并提供 `AdaptyError`: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRendering( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>事件示例(点击展开)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> 正常情况下不应出现此类错误,如果你遇到了,请告知我们。 --- # File: unity-web-paywalls --- --- title: "在 Unity SDK 中实现网页付费墙" description: "设置网页付费墙,无需支付 App Store 费用和审核即可收款。" --- :::important 开始之前,请确保您已[在看板中配置了网页付费墙](web-paywall),并已安装 Adapty SDK 3.14 或更高版本。 ::: ## 打开网页付费墙 \{#open-web-paywalls\} 如果你使用的是自行开发的付费墙,则需要通过 SDK 方法来处理网页付费墙。`Adapty.OpenWebPaywall` 方法会执行以下操作: 1. 生成一个唯一 URL,使 Adapty 能够将向特定用户展示的付费墙与其跳转到的网页关联起来。 2. 追踪用户返回应用的时机,然后以短间隔轮询 `Adapty.GetProfile`,以判断用户画像的访问权限是否已更新。 这样,一旦支付成功并更新了访问权限,订阅几乎会立即在应用中激活。 ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` :::note `OpenWebPaywall` 方法有两个版本: 1. `OpenWebPaywall(product)` — 根据付费墙生成 URL,并将产品数据附加到 URL 中。 2. `OpenWebPaywall(paywall)` — 根据付费墙生成 URL,但不附加产品数据。当 Adapty 付费墙中的产品与 Web 付费墙中的产品不同时,请使用此版本。 ::: #### 错误处理 \{#handle-errors\} | 错误代码 | 描述 | 建议操作 | |-----------|--------------------------------------------------------|---------------------------------------------------------------------------| | `AdaptyErrorCode.WrongParam` | 付费墙或产品未配置网页购买 URL,或在浏览器中打开 URL 失败 | 查看错误信息了解详情。在 Adapty 看板中检查付费墙/产品配置,或检查设备设置。 | | `AdaptyErrorCode.DecodingFailed` | 无法正确编码 URL 中的参数 | 验证 URL 参数是否有效且格式正确 | :::note 查看错误的 `Message` 属性,以获取具体的错误详情。`WrongParam` 可能对应多种问题(缺少购买 URL、无法打开浏览器等)。 ::: ## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\} :::important 从 Adapty SDK v3.15 起,支持在应用内浏览器中打开网页付费墙。 ::: 默认情况下,网页付费墙会在外部浏览器中打开,这会将用户引导至应用之外。 为了提供流畅的用户体验,你可以改为在应用内浏览器中打开网页付费墙。这样一来,网页购买页面会直接在你的应用内展示,用户无需切换应用即可完成交易。 要启用此功能,请将 `AdaptyWebPresentation.InAppBrowser` 传入 `OpenWebPaywall` 方法: ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, AdaptyWebPresentation.InAppBrowser, // default — ExternalBrowser (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` --- # File: unity-use-fallback-paywalls --- --- title: "Unity - 使用备用付费墙" description: "处理用户离线或 Adapty 服务器不可用的情况" --- :::warning 备用付费墙需要 Unity SDK v2.11 及更高版本支持。 ::: 为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。 * **若应用无法访问 Adapty 服务器:** 应用可以显示备用流程或付费墙,并读取本地的用户引导配置。 * **若应用无法访问互联网:** 应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。 :::important 在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。 ::: ## 配置 \{#configuration\} 1. 将备用配置文件添加到项目中的公共目录 `Assets/StreamingAssets`。 2. 在获取目标付费墙或用户引导**之前**调用 `.setFallback` 方法。 ```csharp using UnityEngine; using AdaptySDK; #if UNITY_IOS string fileName = "ios_fallback.json"; #elif UNITY_ANDROID string fileName = "android_fallback.json"; #else // Optional: handle Editor or other platforms string fileName = "fallback.json"; #endif Adapty.SetFallback(fileName, (error) => { if (error != null) { Debug.LogError($"Failed to set fallback: {error}"); return; } // Fallback set successfully }); ``` 参数: | 参数 | 描述 | |:-------------|:-----------------------------------------------------| | **fileName** | 包含备用配置文件名称的字符串。 | --- # File: unity-localizations-and-locale-codes --- --- title: "在 Unity SDK 中使用本地化和语言代码" description: "了解如何使用 Adapty SDK 对 Unity 应用中的付费墙进行本地化。" --- ## 为什么这很重要 \{#why-this-is-important\} 在某些场景下,语言区域代码会发挥关键作用——例如,当你需要根据应用当前的本地化设置获取对应的付费墙时。 由于语言区域代码较为复杂,且在不同平台之间可能存在差异,我们对所有支持的平台统一使用一套内部标准。正因为这些代码比较复杂,了解你实际发送给服务器的内容、以及后续的处理逻辑就显得尤为重要——这样你才能始终获取到预期的本地化结果。 ## Adapty 的语言代码标准 \{#locale-code-standard-at-adapty\} 在语言代码方面,Adapty 采用了经过少量调整的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag):每个代码由小写子标签组成,以连字符分隔。例如:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。 ## 语言区域代码匹配 \{#locale-code-matching\} 当 Adapty 收到客户端 SDK 传入的语言区域代码并开始查找对应的付费墙本地化版本时,流程如下: 1. 传入的语言区域字符串转换为小写,所有下划线(`_`)替换为连字符(`-`) 2. 查找与完整语言区域代码完全匹配的本地化版本 3. 如果未找到匹配项,则取第一个连字符前的子字符串(例如 `pt-br` 取 `pt`),再次查找匹配的本地化版本 4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本 这样,发送 `'pt_BR'` 的 iOS 设备、发送 `pt-BR` 的 Android 设备以及发送 `pt-br` 的其他设备,都会得到相同的结果。 ## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\} 如果你正在考虑本地化问题,很可能项目中已经有了本地化字符串文件。在这种情况下,我们建议在每个本地化文件中加入一个键值对,其值对应 Adapty 的语言区域代码,然后在调用 SDK 时提取该键的值,示例如下: ```csharp showLineNumbers // 1. Modify your localization files (e.g., using Unity's Localization package) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using AdaptySDK; public class PaywallManager : MonoBehaviour { public async void FetchPaywall() { // Get the current locale from Unity's Localization system var locale = LocalizationSettings.SelectedLocale; var localeCode = GetAdaptyLocaleCode(locale); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetAdaptyLocaleCode(Locale locale) { // Convert Unity locale to Adapty format var localeIdentifier = locale.Identifier.Code; return localeIdentifier.ToLower().Replace('_', '-'); } } ``` 这样,您就能完全掌控应用中每位用户所获取的本地化内容。 ## 另一种本地化实现方式 \{#implementing-localizations-the-other-way\} 你也可以不为每个本地化版本明确定义语言区域代码,从而实现类似(但并不完全相同)的效果。这意味着需要从平台提供的其他对象中提取语言区域代码,例如: ```csharp showLineNumbers using UnityEngine; using System.Globalization; using AdaptySDK; public class PaywallManager : MonoBehaviour { public void FetchPaywall() { var localeCode = GetSystemLocaleCode(); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetSystemLocaleCode() { // Get the system's current culture var culture = CultureInfo.CurrentCulture; var languageCode = culture.TwoLetterISOLanguageName; var regionCode = culture.Name.Contains('-') ? culture.Name.Split('-')[1] : null; if (!string.IsNullOrEmpty(regionCode)) { return $"{languageCode}-{regionCode.ToLower()}"; } return languageCode; } } ``` 请注意,我们不建议使用此方法,原因如下: 1. 在 iOS 上,首选语言与当前语言区域并不相同。如果想让本地化正确生效,要么依赖 Apple 的内置逻辑(使用推荐的本地化字符串文件方案时开箱即用),要么自行重新实现该逻辑。 2. 很难预测 Adapty 服务器实际会收到什么内容。例如,在 iOS 上,设备可能上报类似 `ar_OM@numbers='latn'` 这样的语言区域标识,并将其发送到我们的服务器。而服务器收到后,返回的不会是你期望的 `ar-om` 本地化内容,而是 `ar`,这往往会让人感到意外。 如果你仍决定采用这种方式,请确保已覆盖所有相关的使用场景。 --- # File: unity-troubleshoot-paywall-builder --- --- title: "排查 Unity SDK 中的付费墙编辑工具问题" description: "排查 Unity SDK 中的付费墙编辑工具问题" --- 本指南帮助您解决在 Unity SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。 ## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\} **问题**:`CreateView` 方法无法获取付费墙配置。 **原因**:付费墙未在付费墙编辑工具中启用设备显示。 **解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。 <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 付费墙视图数量过大 \{#the-paywall-view-number-is-too-big\} **问题**:付费墙视图计数显示的数量是预期数量的两倍。 **原因**:您可能在代码中调用了 `LogShowPaywall`,如果您正在使用付费墙编辑工具,这会导致视图计数重复。对于使用付费墙编辑工具设计的付费墙,分析数据会自动追踪,因此您无需使用此方法。 **解决方案**:如果您正在使用付费墙编辑工具,请确保代码中未调用 `LogShowPaywall`。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。 **解决方案**:如有需要,请参照[迁移指南](unity-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新版本的 SDK 中得到解决。 --- # File: unity-quickstart-manual --- --- title: "在 Unity SDK 的自定义付费墙中启用购买功能" description: "将 Adapty SDK 集成到您的自定义 Unity 付费墙中,以启用应用内购买。" --- 本指南介绍如何将 Adapty 集成到您的自定义付费墙中。您可以完全掌控付费墙的实现,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买。 :::important **本指南面向正在实现自定义付费墙的开发者。** 如果您希望以最简便的方式启用购买功能,请使用 [付费墙编辑工具](unity-quickstart-paywalls)。使用付费墙编辑工具,您可以在无代码可视化编辑器中创建付费墙,Adapty 会自动处理所有购买逻辑,您无需重新发布应用即可测试不同的设计方案。 ::: ## 开始之前 \{#before-you-start\} ### 设置产品 \{#set-up-products\} 要启用应用内购买,您需要了解三个核心概念: - [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权) - [**付费墙**](paywalls) – 定义要展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一途径,但这种设计使您无需修改应用代码即可调整产品、价格和优惠。 - [**版位**](placements) – 在应用中展示付费墙的位置和时机(例如 `main`、`onboarding`、`settings`)。您在看板中为版位设置付费墙,然后在代码中通过版位 ID 请求它们。这使得运行 A/B 测试以及向不同用户展示不同付费墙变得轻而易举。 即使您使用自定义付费墙,也请确保理解这些概念。它们本质上只是您管理应用内销售产品的方式。 要实现自定义付费墙,您需要创建一个**付费墙**并将其添加到**版位**中。此设置使您能够获取产品。如需了解在看板中需要执行哪些操作,请参阅[此处](quickstart)的快速入门指南。 ### 管理用户 \{#manage-users\} 您可以选择使用或不使用后端身份验证。 但请注意,Adapty SDK 对匿名用户和已识别用户的处理方式有所不同。请阅读[用户识别快速入门指南](unity-quickstart-identify),了解具体差异并确保您正确地管理用户。 ## 第一步:获取产品 \{#step-1-get-products\} 要获取自定义付费墙的产品,您需要: 1. 通过将[版位](placements) ID 传递给 `getPaywall` 方法来获取 `paywall` 对象。 2. 使用 `getPaywallProducts` 方法获取该付费墙的产品数组。 ```csharp showLineNumbers using AdaptySDK; void LoadPaywall() { Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // Handle the error return; } Adapty.GetPaywallProducts(paywall, (products, productsError) => { if (productsError != null) { // Handle the error return; } // Use products to build your custom paywall UI }); }); } ``` ## 第二步:接受购买 \{#step-2-accept-purchases\} 当用户在自定义付费墙中点击某个产品时,调用 `makePurchase` 方法并传入所选产品。该方法将处理购买流程并返回更新后的用户画像。 ```csharp showLineNumbers using AdaptySDK; void PurchaseProduct(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { if (error != null) { // Handle the error return; } switch (result.Type) { case AdaptyPurchaseResultType.Success: var profile = result.Profile; // Purchase successful, profile updated break; case AdaptyPurchaseResultType.UserCancelled: // User canceled the purchase break; case AdaptyPurchaseResultType.Pending: // Purchase is pending (e.g., user will pay offline with cash) break; } }); } ``` ## 第三步:恢复购买 \{#step-3-restore-purchases\} 应用商店要求所有包含订阅的应用为用户提供恢复购买的途径。 当用户点击恢复按钮时,调用 `restorePurchases` 方法。该方法将把用户的购买历史与 Adapty 同步,并返回更新后的用户画像。 ```csharp showLineNumbers using AdaptySDK; void RestorePurchases() { Adapty.RestorePurchases((profile, error) => { if (error != null) { // Handle the error return; } // Restore successful, profile updated }); } ``` ## 后续步骤 \{#next-steps\} :::tip 有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助! ::: 您的付费墙已准备好在应用中展示。请在 [App Store 沙盒](test-purchases-in-sandbox)或 [Google Play Store](testing-on-android) 中测试您的购买流程,以确保能够从付费墙完成测试购买。 接下来,[检查用户是否已完成购买](unity-check-subscription-status),以决定是否展示付费墙或授予付费功能的访问权限。 --- # File: fetch-paywalls-and-products-unity --- --- title: "在 Unity SDK 中获取远程配置付费墙的付费墙和产品" description: "在 Adapty Unity SDK 中获取付费墙和产品,以提升用户变现效果。" --- 在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题涉及远程配置和自定义付费墙。如需获取付费墙编辑工具自定义付费墙的指导,请参阅[获取付费墙编辑工具付费墙及其配置](unity-get-pb-paywalls)。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: <details> <summary>在移动应用中开始获取付费墙和产品之前(点击展开)</summary> 1. 在 Adapty 看板中[创建您的产品](create-product)。 2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙中](create-paywall)。 3. 在 Adapty 看板中[创建版位并将付费墙添加到版位中](create-placement)。 4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-unity)。 </details> ## 获取付费墙信息 \{#fetch-paywall-information\} 在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合。这些跨平台产品被集成到付费墙中,使您能够在特定的移动应用版位中展示它们。 要展示产品,您需要使用 `getPaywall` 方法从某个[版位](placements)中获取[付费墙](paywalls)。 :::important **不要硬编码产品 ID。** 您唯一应该硬编码的是版位 ID。付费墙是远程配置的,因此产品数量和可用优惠随时可能发生变化。您的应用必须动态处理这些变化——如果今天付费墙返回两个产品,明天返回三个,则应显示所有产品而无需修改代码。 ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` | 参数 | 是否必需 | 描述 | |---------|--------|-----------| | **placementId** | 必需 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>[付费墙本地化](add-remote-config-locale)的标识符。该参数应为由一个或多个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p></p><p>有关语言环境代码及推荐使用方式的更多信息,请参阅[本地化与语言环境代码](unity-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 将尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此方式,因为它可确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时返回缓存数据。在这种情况下,用户可能无法获取绝对最新的数据,但无论网络状况如何,他们都会获得更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然有效,只有在应用卸载重装或手动清理时才会被清除。</p><p></p><p>Adapty SDK 将付费墙存储在两个层级中:上述定期更新的缓存和[备用付费墙](unity-use-fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的付费墙,同时即使在网络连接稀缺的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>该值限制此方法的超时时间。若达到超时时间,将返回缓存数据或本地备用数据。</p><p></p><p>请注意,在极少数情况下,此方法的超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作在底层可能包含多个不同的请求。</p> | 不要硬编码产品 ID!由于付费墙是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)可能随时发生变化。请确保您的代码能够处理这些情况。 例如,如果您最初获取到 2 个产品,您的应用应显示这 2 个产品。但如果您后来获取到 3 个产品,您的应用应显示所有 3 个产品,而无需修改任何代码。唯一需要硬编码的是版位 ID。 响应参数: | 参数 | 描述 | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | 一个 [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他多个属性。 | ## 获取产品 \{#fetch-products\} 获取付费墙后,您可以查询与之对应的产品数组: ```csharp showLineNumbers Adapty.GetPaywallProducts(paywall, (products, error) => { if(error != null) { // handle the error return; } // products - the requested products array }); ``` 响应参数: | 参数 | 描述 | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 | 在实现自定义付费墙设计时,您可能需要访问 [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) 对象中的这些属性。以下列出了最常用的属性,但请参阅链接文档以获取所有可用属性的完整详情。 | 属性 | 描述 | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | 要显示产品标题,请使用 `product.LocalizedTitle`。请注意,本地化基于用户在商店所选的国家/地区,而非设备本身的语言环境。 | | **Price** | 要显示本地化的价格,请使用 `product.Price.LocalizedString`。此本地化基于设备的语言环境信息。您也可以通过 `product.Price.Amount` 以数字形式访问价格,该值将以本地货币提供。要获取对应的货币符号,请使用 `product.Price.CurrencySymbol`。 | | **Subscription Period** | 要显示订阅周期(如周、月、年等),请使用 `product.Subscription?.LocalizedPeriod`。此本地化基于设备语言环境。要以编程方式获取订阅周期,请使用 `product.Subscription?.Period`。从中您可以访问 `Unit` 枚举以获取时长(即 `AdaptySubscriptionPeriodUnit.Day`、`AdaptySubscriptionPeriodUnit.Week`、`AdaptySubscriptionPeriodUnit.Month`、`AdaptySubscriptionPeriodUnit.Year` 或 `AdaptySubscriptionPeriodUnit.Unknown`)。`NumberOfUnits` 值将为您提供周期单位的数量。例如,对于季度订阅,Unit 属性中显示 `AdaptySubscriptionPeriodUnit.Month`,NumberOfUnits 属性中显示 `3`。 | | **Introductory Offer** | 要显示徽章或其他指示符以表明订阅包含新用户优惠,请查看 `product.Subscription?.Offer?.Phases` 属性。这是一个最多包含两个折扣阶段的列表:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下有用属性:<br/>• `PaymentMode`:枚举值,包括 `AdaptyPaymentMode.FreeTrial`、`AdaptyPaymentMode.PayAsYouGo`、`AdaptyPaymentMode.PayUpFront` 和 `AdaptyPaymentMode.Unknown`。免费试用为 `AdaptyPaymentMode.FreeTrial` 类型。<br/>• `Price`:折扣价格(数字形式)。对于免费试用,此处值为 `0`。<br/>• `LocalizedNumberOfPeriods`:使用设备语言环境本地化的字符串,描述优惠的时长。例如,三天试用优惠在此字段显示为 `"3 days"`。<br/>• `SubscriptionPeriod`:或者,您可以通过此属性获取优惠周期的具体详情,其使用方式与前一节描述的相同。<br/>• `LocalizedSubscriptionPeriod`:针对用户语言环境格式化的折扣订阅周期。 | ## 使用默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\} 通常情况下,付费墙几乎可以即时获取,因此您无需担心加速此过程。但是,如果您拥有大量目标受众和付费墙,且用户的网络连接较弱,获取付费墙可能需要比预期更长的时间。在这种情况下,您可能希望显示默认付费墙,以确保流畅的用户体验,而不是完全不显示付费墙。 为解决这一问题,您可以使用 `GetPaywallForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的付费墙。但是,请务必了解,推荐的方式是通过 `getPaywall` 方法获取付费墙,详见上方[获取付费墙](#fetch-paywall)部分。 :::warning 请考虑使用 `GetPaywall` 而非 `GetPaywallForDefaultAudience`,因为后者有以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要向后兼容的设计,或接受旧版本可能显示不正确的情况。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法根据国家/地区、归因或自定义属性进行定向。 如果对于您的使用场景,更快的获取速度超过了这些缺点,请按以下方式使用 `GetPaywallForDefaultAudience`。否则,请按[上述](#fetch-paywall)描述使用 `GetPaywall`。 ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` 参数: | 参数 | 是否必需 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必需 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>付费墙本地化的标识符。该参数应为由一个或两个子标签组成的语言代码,子标签之间用减号(**-**)分隔。第一个子标签表示语言,第二个表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 将尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它可确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时返回缓存数据。在这种情况下,用户可能无法获取绝对最新的数据,但无论网络状况如何,他们都会获得更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后仍然有效,只有在应用卸载重装或手动清理时才会被清除。</p><p></p><p>Adapty SDK 在本地将付费墙存储在两个层级中:上述定期更新的缓存和备用付费墙。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的付费墙,同时即使在网络连接稀缺的情况下也能保证可靠性。</p> | --- # File: present-remote-config-paywalls-unity --- --- title: "在 Unity SDK 中渲染通过远程配置设计的付费墙" description: "了解如何在 Adapty Unity SDK 中展示远程配置付费墙,以个性化用户体验。" --- 如果您使用远程配置自定义了付费墙,则需要在移动应用代码中实现渲染逻辑,以便向用户展示它。由于远程配置提供了高度灵活性,您可以完全掌控付费墙视图中包含的内容及其显示方式。我们提供了获取远程配置的方法,让您能够自主展示通过远程配置配置的自定义付费墙。 ## 获取付费墙远程配置并展示 \{#get-paywall-remote-config-and-present-it\} 要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。 ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } // Access remote config dictionary var dictionary = paywall.RemoteConfig?.Dictionary; var headerText = dictionary?["header_text"] as string; // Or access raw JSON data var jsonData = paywall.RemoteConfig?.Data; }); ``` 此时,一旦您获取到所有必要的值,就可以将它们渲染并组合成一个美观的页面。请确保设计能够适配各种手机屏幕尺寸和方向,为不同设备上的用户提供流畅且友好的体验。 :::warning 请务必按照下文所述[记录付费墙浏览事件](present-remote-config-paywalls-unity#track-paywall-view-events),以便 Adapty 分析系统能够为漏斗和 A/B 测试采集相关数据。 ::: 展示付费墙完成后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.MakePurchase()`。有关 `.MakePurchase()` 方法的详细信息,请参阅[发起购买](unity-making-purchases)。 我们建议[创建一个备用付费墙作为备份](unity-use-fallback-paywalls)。当用户没有网络连接或缓存不可用时,将向其展示此备用付费墙,确保在这些情况下也能提供流畅的体验。 ## 追踪付费墙浏览事件 \{#track-paywall-view-events\} Adapty 可帮助您衡量付费墙的表现。虽然我们会自动收集购买数据,但付费墙浏览记录需要您手动上报,因为只有您才知道用户何时看到了付费墙。 要记录付费墙浏览事件,只需调用 `.LogShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。 :::important 如果您展示的是通过[付费墙编辑工具](adapty-paywall-builder)创建的付费墙,则无需调用 `.LogShowPaywall(paywall)`。 ::: ```csharp showLineNumbers Adapty.LogShowPaywall(paywall, (error) => { // handle the error }); ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:------------------------------------------------------------------| | **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象。 | --- # File: unity-making-purchases --- --- title: "在 Unity SDK 的移动应用中进行购买" description: "使用 Adapty 处理应用内购买和订阅的指南。" --- 在您的移动应用中展示付费墙是向用户提供高级内容或服务访问权限的重要步骤。但是,仅仅展示付费墙只有在您使用[付费墙编辑工具](adapty-paywall-builder)自定义付费墙时,才足以支持购买。 如果您没有使用付费墙编辑工具,则必须使用一个单独的方法 `.makePurchase()` 来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成所需交易的入口。 如果你的付费墙为用户正在购买的产品设置了有效的促销活动,Adapty 会在购买时自动应用该优惠。 :::warning 请注意,只有使用付费墙编辑工具搭建的付费墙,新用户优惠才会自动应用。 在其他情况下,您需要[验证用户在 iOS 上是否符合新用户优惠的条件](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致应用在发布审核时被拒绝,还可能向本应享受新用户优惠的用户收取全价。 ::: 请确保您已[完成初始配置](quickstart),且没有跳过任何步骤。否则,我们将无法验证购买。 ## 进行购买 \{#make-purchase\} :::note **使用[付费墙编辑工具](adapty-paywall-builder)?** 购买会自动处理——可以跳过此步骤。 **需要分步指引?** 请查看[快速入门指南](unity-implement-paywalls-manually),其中包含完整的端到端实现说明。 ::: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successfull purchase break; default: break; } }); } ``` 请求参数: | 参数 | 是否必填 | 描述 | | :---------- | :------- |:------------------------------------------------------------------------------------------------------| | **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) 对象。| 响应参数: | 参数 | 描述 | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>请求成功后,响应中会包含此对象。[AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) 对象提供了用户在应用内的访问等级、订阅及一次性购买的完整信息。</p><p>请检查访问等级状态,以确认用户是否拥有所需的应用访问权限。</p> | :::warning **注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。此方法目前已被 Apple 弃用。 ::: ## 购买时更改订阅 \{#change-subscription-when-making-a-purchase\} 当用户选择新订阅而非续订当前订阅时,具体行为取决于所使用的应用商店: - 对于 App Store,订阅会在同一订阅组内自动更新。如果用户在已有某个订阅组的订阅的情况下,又购买了另一个订阅组的订阅,则两个订阅将同时处于激活状态。 - 对于 Google Play,订阅不会自动更新。你需要按照下方说明,在移动应用代码中手动处理订阅切换逻辑。 在 Android 上将订阅替换为另一个订阅,请在调用 `.makePurchase()` 方法时传入额外参数: ```csharp showLineNumbers // Create subscription update parameters var subscriptionUpdateParams = new AdaptySubscriptionUpdateParameters( "old_product_id", // Product ID of the current subscription AdaptySubscriptionUpdateReplacementMode.WithTimeProration ); Adapty.MakePurchase(product, subscriptionUpdateParams, (profile, error) => { if(error != null) { // Handle the error return; } // successful cross-grade }); ``` 额外请求参数: | 参数 | 是否必填 | 描述 | | :--------------------------- | :------- |:-------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | 必填 | 一个 [`AdaptySubscriptionUpdateParameters`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_subscription_update_parameters.html) 对象。 | 如需了解更多关于订阅和替换模式的内容,请参阅 Google 开发者文档: - [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Google 针对替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。 - 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更仅在当前订阅计费周期结束时生效。 ## 在 iOS 中兑换优惠码 \{#redeem-offer-codes-in-ios\} <Details> <summary>关于优惠码</summary> 优惠码允许您向特定用户提供折扣或免费试用。与自动应用的常规优惠不同,优惠码通过应用外部渠道发放——例如电子邮件营销、社交媒体或印刷材料。用户可以通过在 App Store 中输入代码、访问兑换链接或在应用内对话框中进行兑换。 要设置优惠码,请在 App Store Connect 中打开某个订阅,然后进入其 **Offer Codes** 部分。您可以创建[三种类型](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes)的优惠码: - **Free** — 订阅在设定时长内免费,下次续订恢复原价。 - **Pay as you go** — 用户在设定时长内每个计费周期按折扣价付费,之后订阅恢复原价续订。 - **Pay up front** — 用户一次性按折扣价支付整个优惠期费用,之后订阅恢复原价续订。 您无需将优惠码添加到 Adapty。Apple 会在优惠期内为每笔交易打上优惠码类别标签,包括首次兑换和后续所有折扣续订。Adapty 检测到该标签后,会将每笔交易以 `offer_code` 优惠类别记录。优惠期结束、订阅以原价续订后,该标签将不再存在。您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中按 **Offer Code** 优惠类型筛选分析数据。 #### 营收差异排查 如果您发现某笔优惠码交易在 Adapty 中以产品原价而非折扣价显示,请在 App Store Connect 中核实以下内容: - 优惠码已为用户可兑换的所有地区正确配置了定价。 - 已为用户所在的特定国家或地区设置了优惠价格。Apple 在交易中发送的是地区价格。如果该优惠未配置地区价格,Apple 可能会发送产品原价。 您可以在 [Adapty 看板](controls-filters-grouping-compare-proceeds) 中通过 **Offer Code** 优惠类型和 **Offer Discount Type** 筛选器来筛选和核实优惠码交易。 #### 旧版促销码(已弃用) :::warning Apple 于 2026 年 3 月弃用了应用内购买的促销码。优惠码以更强大的功能取而代之:可配置资格条件、设置到期日期,每季度最多可生成 100 万个代码。如果您之前使用促销码进行应用内购买,请在 App Store Connect 中迁移至优惠码。 ::: 旧版促销码(每个应用每个版本上限 100 个)可免费授予订阅访问权限。与优惠码不同,Apple 不会在促销码交易中包含折扣信息——它在收据中发送的是产品原价。因此,Adapty 以原价记录这些交易,导致 Adapty 分析数据与 App Store Connect 之间出现营收差异。 如果您看到历史交易以原价显示但本应免费,这些很可能来自旧版促销码。由于这些代码现已被弃用,请迁移至优惠码以确保营收数据的准确性。 </Details> 在应用中显示兑换码界面: ```csharp showLineNumbers Adapty.PresentCodeRedemptionSheet((error) => { // handle the error }); ``` :::danger 根据我们的观察,某些应用中的优惠码兑换界面可能无法稳定运行。我们建议直接将用户跳转至 App Store。 为此,您需要打开以下格式的 URL: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## 管理预付费计划(Android) \{#manage-prepaid-plans-android\} 如果你的应用用户可以购买[预付费计划](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,一次性购买数月的非续订订阅),你可以为预付费计划启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)功能。 ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetGoogleEnablePendingPrepaidPlans(true); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: unity-restore-purchase --- --- title: "在 Unity SDK 中恢复移动应用内的购买" description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。" --- 在 iOS 和 Android 中恢复购买是一项功能,允许用户重新获取之前购买的内容(例如订阅或应用内购买),而无需再次付费。此功能对于那些可能已卸载并重新安装应用,或切换到新设备并希望访问之前购买内容而无需再次付款的用户尤为有用。 :::note 在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您属于此情况,可以跳过此步骤。 ::: 如果您未使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买: ```csharp showLineNumbers Adapty.RestorePurchases((profile, error) => { if (error != null) { // handle the error return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel != null && accessLevel.IsActive) { // restore access } }); ``` 响应参数: | 参数 | 描述 | |---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>一个 [`AdaptyProfile`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) 对象。该模型包含有关访问等级、订阅和非订阅购买的信息。</p><p>请检查**访问等级状态**以确定用户是否有权访问该应用。</p> | :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: --- # File: implement-observer-mode-unity --- --- title: "在 Unity SDK 中实现观察者模式" description: "在 Adapty 中实现观察者模式,以在 Unity SDK 中追踪用户订阅事件。" --- 如果您已经拥有自己的购买基础设施,并且尚未准备好完全切换到 Adapty,您可以了解[观察者模式](observer-vs-full-mode)。在其基本形式下,观察者模式提供高级分析功能以及与归因和分析系统的无缝集成。 如果这满足您的需求,您只需: 1. 在配置 Adapty SDK 时将 `observerMode` 参数设置为 `true` 来启用它。请参照 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk) 的设置说明。 2. 将现有购买基础设施中的[交易上报](report-transactions-observer-mode-unity)给 Adapty。 ### 观察者模式设置 \{#observer-mode-setup\} 如果您自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请启用观察者模式。 :::important 在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保您自行处理这一事项。 ::: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetObserverMode(true); // Enable observer mode Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` 参数: | 参数 | 描述 | |--------------|-------------------------------------------------------------------------------------------------------------| | observerMode | 用于控制[观察者模式](observer-vs-full-mode)的布尔值。默认值为 `false`。 | ## 在观察者模式下使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\} 如果您还想使用 Adapty 的付费墙和 A/B 测试功能,也是可以的——但在观察者模式下需要一些额外的设置。除了上述步骤之外,您还需要: 1. 按照[远程配置付费墙](present-remote-config-paywalls-unity)的常规方式展示付费墙。 3. 将付费墙与购买交易进行[关联](report-transactions-observer-mode-unity)。 --- # File: report-transactions-observer-mode-unity --- --- title: "在 Unity SDK 的观察者模式下上报交易" description: "在 Adapty 观察者模式下上报购买交易,用于用户洞察和收入追踪(Unity SDK)。" --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+(当前版本)" default> 在观察者模式下,Adapty SDK 无法自行追踪通过您现有购买系统完成的购买。您需要从应用商店上报交易。务必在发布应用**之前**完成此设置,以避免分析数据出现错误。 使用 `reportTransaction` 显式上报每笔交易,以便 Adapty 识别。 :::warning **不要跳过交易上报!** 如果您不调用 `ReportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成系统。 ::: 如果您使用 Adapty 付费墙,请在上报交易时包含 `variationId`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```csharp showLineNumbers Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | 必填 | <ul><li>iOS:交易的标识符。</li><li>Android:购买的字符串标识符 `purchase.getOrderId`,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。可通过 [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x(旧版)" default> 在观察者模式下,Adapty SDK 无法自行追踪通过您现有购买系统完成的购买。您需要从应用商店上报或恢复交易。务必在发布应用**之前**完成此设置,以避免分析数据出现错误。 在两个平台上使用 `reportTransaction` 显式上报每笔交易,并在 Android 上额外使用 `restorePurchases`,以确保 Adapty 识别该交易。 :::warning **不要跳过交易上报和购买恢复!** 如果您不调用这些方法,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成系统。 ::: 如果您使用 Adapty 付费墙,请在上报交易时包含 `PAYWALL_VARIATION_ID`。这会将购买与触发它的付费墙关联起来,从而确保付费墙分析数据的准确性。 ```csharp showLineNumbers // every time when calling transasction.finish() #if UNITY_ANDROID && !UNITY_EDITOR Adapty.RestorePurchases((profile, error) => { // handle the error }); #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` 参数: | 参数 | 是否必填 | 描述 | | ------------- | -------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | 必填 | <ul><li>iOS,StoreKit 1:一个 [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</li><li>iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</li><li>Android:购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</li></ul> | | variationId | 可选 | 实验变体的字符串标识符。可通过 [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象的 `variationId` 属性获取。 | </TabItem> <TabItem value="old2" label="Adapty SDK 3.2.x 及以下(旧版)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **上报交易** - 3.1.x 及以下版本会自动监听 App Store 中的交易,无需手动上报。 - 3.2 版本不支持观察者模式。 </TabItem> <TabItem value="kotlin" label="Android 及基于 Android 的跨平台" default> **上报交易** 使用 `restorePurchases` 在观察者模式下向 Adapty 上报交易,详情请参阅[在移动端代码中恢复购买](unity-restore-purchase)页面。 :::warning **不要跳过交易上报!** 如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成系统。 ::: </TabItem> </Tabs> **将付费墙与交易关联** Adapty SDK 无法确定购买的来源,因为购买是由您来处理的。因此,如果您打算在观察者模式下使用付费墙和/或 A/B 测试,则需要在移动应用代码中将来自应用商店的交易与相应的付费墙关联起来。在发布应用之前务必正确完成此操作,否则将导致分析数据出现错误。 ```csharp Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { if(error != null) { // handle the error return; } // successful binding }); ``` | 参数 | 是否必填 | 描述 | | ------------------------------------------------------ | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | 必填 | <p>iOS,StoreKit 1:一个 [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。</p><p>iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。</p><p>Android:购买的字符串标识符(purchase.getOrderId),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。</p> | | variationId | 必填 | 实验变体的字符串标识符。可通过 [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) 对象的 `variationId` 属性获取。 | </TabItem> </Tabs> --- # File: unity-troubleshoot-purchases --- --- title: "排查 Unity SDK 中的购买问题" description: "排查 Unity SDK 中的购买问题" --- 本指南帮助您解决在 Unity SDK 中手动实现购买时遇到的常见问题。 ## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **问题**:`makePurchase` 方法成功执行,但用户的用户画像和订阅状态在 Adapty 中未被更新。 **原因**:这通常表示 Google Play Store 设置不完整或存在配置问题。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\} **问题**:`makePurchase` 方法针对同一次购买被多次调用。 **原因**:这通常发生在由于 UI 状态管理问题或用户快速操作导致购买流程被多次触发时。 **解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。 ## 观察者模式下出现 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\} **问题**:在观察者模式下使用 `makePurchase` 时收到 `AdaptyError.cantMakePayments` 错误。 **原因**:在观察者模式下,您应在自己的代码中处理购买,而不是使用 Adapty 的 `makePurchase` 方法。 **解决方案**:如果您使用 `makePurchase` 处理购买,请关闭观察者模式。您需要二选一:要么使用 `makePurchase`,要么在观察者模式下自行处理购买。详情请参阅[实现观察者模式](implement-observer-mode-unity)。 ## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **问题**:您收到来自 Google Play Store 的计费不可用错误。 **原因**:此错误与 Adapty 无关,它是 Google Play 计费库的错误,表示设备上的计费功能不可用。 **解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查看更多信息:[处理 BillingResult 响应码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。 ## 未找到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **问题**:您遇到了找不到 `makePurchasesCompletionHandlers` 的问题。 **原因**:这通常与沙盒测试问题有关。 **解决方案**:创建一个新的沙盒用户并重试。这通常可以解决与沙盒相关的购买完成处理程序问题。 ## 其他问题 \{#other-issues\} **问题**:您遇到了上述未涵盖的其他购买相关问题。 **解决方案**:如有需要,请使用[迁移指南](unity-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新版本的 SDK 中得到修复。 --- # File: unity-identifying-users --- --- title: "在 Unity SDK 中识别用户" description: "了解如何在 Unity 应用中使用 Adapty SDK 识别用户。" --- Adapty 会为每位用户创建一个内部 Profile ID。但如果你有自己的身份验证系统,则应设置自己的 Customer User ID。你可以在[用户画像](profiles-crm)页面通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它,该 ID 会同步发送至所有集成。 ### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\} 如果您在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 配置完成后设置用户 ID \{#setting-customer-user-id-after-configuration\} 如果在 SDK 配置时没有用户 ID,可以随时通过 `.identify()` 方法进行设置。最常见的使用场景是用户完成注册或登录后,从匿名用户切换为已认证用户时。 ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { if(error == null) { // successful identify } }); ``` 请求参数: - **Customer User ID**(必填):字符串类型的用户标识符。 :::warning 重新提交重要用户数据 在某些情况下,例如用户重新登录账号时,Adapty 服务器可能已经存有该用户的信息。此时,Adapty SDK 会自动切换到新用户。如果你之前为匿名用户设置了自定义属性或第三方网络的归因数据,需要为已识别的用户重新提交这些数据。 此外,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。 ::: ### 登出与登录 \{#logging-out-and-logging-in\} 您可以随时调用 `.logout()` 方法使用户登出: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` 之后可以使用 `.identify()` 方法使用户重新登录。 ## 分配 `appAccountToken`(iOS)\{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) 是一个 **UUID**,用于将 App Store 交易与你的内部用户身份关联起来。StoreKit 会将该令牌与每笔交易绑定,以便你的后端将 App Store 数据与用户对应匹配。 建议为每个用户生成一个稳定的 UUID,并在同一账号的不同设备上复用该 UUID。这样可以确保购买记录和 App Store 通知始终与正确的用户关联。 您可以通过两种方式设置令牌——在 SDK 初始化时,或在识别用户时。 :::important 您必须始终将 `appAccountToken` 与 `customerUserId` 一起传递。 如果只传递令牌而不传递 `customerUserId`,令牌将不会包含在交易中。 ::: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; using System; // During configuration: var appAccountToken = new Guid("YOUR_APP_ACCOUNT_TOKEN"); var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", appAccountToken); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); // Or when identifying users Adapty.Identify("YOUR_USER_ID", appAccountToken, (error) => { if (error == null) { // successful identify } }); ``` ## 设置混淆账户 ID(Android)\{#set-obfuscated-account-ids-android\} Google Play 在某些场景下要求使用混淆账户 ID,以保护用户隐私和安全。这些 ID 帮助 Google Play 在识别购买记录的同时保持用户信息匿名,在防欺诈和数据分析方面尤为重要。 如果你的应用处理敏感用户数据,或需要遵守特定的隐私法规,则可能需要设置这些 ID。混淆 ID 让 Google Play 能够追踪购买行为,同时不暴露真实的用户标识。 ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; // 配置期间: var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // 处理错误 return; } }); // 或在识别用户时 Adapty.Identify("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID", (error) => { if (error == null) { // 识别成功 } }); ``` ## 跨设备用户识别 \{#detect-users-across-devices\} 当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。 **不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。 **Adapty 跨设备可检测的内容** | 您的配置 | Adapty 检测到的内容 | 您需要做什么 | | --- | --- | --- | | Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 | | Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 | | Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 | | 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 | **在新设备上恢复购买** 在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。 正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。 --- # File: unity-setting-user-attributes --- --- title: "在 Unity SDK 中设置用户属性" description: "了解如何使用 Adapty SDK 在 Unity 应用中更新用户属性和用户画像数据。" --- 您可以为应用用户设置可选属性,例如电子邮件、电话号码等。然后,您可以使用这些属性创建用户[市场细分](segments),或直接在 CRM 中查看。 ### 设置用户属性 \{#setting-user-attributes\} 要设置用户属性,请调用 `.updateProfile()` 方法: ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetFirstName("John") .SetLastName("Appleseed") .SetBirthday(new DateTime(1970, 1, 3)) .SetGender(ProfileGender.Female) .SetEmail("example@adapty.io"); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != nil) { // handle the error } }); ``` 请注意,之前通过 `updateProfile` 方法设置的属性不会被重置。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ### 允许的键列表 \{#the-allowed-keys-list\} `AdaptyProfileParameters.Builder` 允许的键 `<Key>` 及其对应的值 `<Value>` 如下所示: | 键 | 值 | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | 枚举,允许的值为:`female`、`male`、`other` | | birthday | Date | ### 自定义用户属性 \{#custom-user-attributes\} 您可以设置自定义属性,这些属性通常与您的应用使用情况相关。例如,对于健身应用,可以是每周锻炼次数;对于语言学习应用,可以是用户的知识水平等。您可以在市场细分中使用这些属性来创建有针对性的付费墙和优惠,也可以在分析中使用它们来确定哪些产品指标对收入影响最大。 ```csharp showLineNumbers try { builder = builder.SetCustomStringAttribute("string_key", "string_value"); builder = builder.SetCustomDoubleAttribute("double_key", 123.0f); } catch (Exception e) { // handle the exception } ``` 要删除现有键,请使用 `.withRemoved(customAttributeForKey:)` 方法: ```csharp showLineNumbers try { builder = builder.RemoveCustomAttribute("key_to_remove"); } catch (Exception e) { // handle the exception } ``` 有时您需要查看已设置的自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。 :::warning 请注意,`customAttributes` 的值可能并非最新,因为用户属性可以随时从不同设备发送,因此服务器上的属性可能在上次同步后已发生变化。 ::: ### 限制 \{#limits\} - 每位用户最多 30 个自定义属性 - 键名最多 30 个字符,可包含字母数字字符及以下任意字符:`_` `-` `.` - 值可以是字符串或浮点数,最多 50 个字符。 --- # File: unity-listen-subscription-changes --- --- title: "在 Unity SDK 中检查订阅状态" description: "在 Adapty 中追踪和管理用户订阅状态,提升 Unity 应用的用户留存率。" --- 借助 Adapty,追踪订阅状态变得轻而易举。您无需在代码中手动插入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可确认其订阅状态。 <details> <summary>开始检查订阅状态之前(点击展开)</summary> - 对于 iOS,请配置 [App Store 服务器通知](enable-app-store-server-notifications) - 对于 Android,请配置[实时开发者通知 (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\} 访问等级是 [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) 对象的属性。我们建议在应用启动时(例如[识别用户](unity-identifying-users#setting-customer-user-id-on-configuration)时)获取用户画像,并在发生变更时及时更新。这样,您就可以直接使用已获取的用户画像对象,而无需反复请求。 如需接收用户画像更新通知,请按照下方[监听订阅状态更新](#listening-for-subscription-status-updates)章节的说明监听用户画像变更事件。 :::tip 想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。 ::: ## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\} 使用 `.GetProfile()` 方法从服务器获取访问等级: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` 响应参数: | 参数 | 描述 | | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>[AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) 对象。通常,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级权限。</p><p></p><p>`.getProfile` 方法始终会尝试请求 API,因此可提供最新的结果。如果由于某些原因(如无网络连接)Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。值得注意的是,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的最新状态。</p> | `.getProfile()` 方法会返回用户画像,您可以从中获取访问等级状态。每个应用可以设置多个访问等级。例如,如果您有一个新闻应用,并针对不同主题独立销售订阅,可以创建"sports"和"science"等访问等级。但大多数情况下,您只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。 以下是检查默认"premium"访问等级的示例: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // "premium" is an identifier of default access level var accessLevel = profile.AccessLevels["premium"]; if (accessLevel != null && accessLevel.IsActive) { // grant access to premium features } }); ``` ### 监听订阅状态更新 \{#listening-for-subscription-status-updates\} 每当用户的订阅发生变化时,Adapty 都会触发相应事件。 要接收来自 Adapty 的消息,您需要进行一些额外配置: ```csharp showLineNumbers // Extend `AdaptyEventListener ` with `OnLoadLatestProfile ` method: public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle any changes to subscription state } } ``` Adapty 也会在应用启动时触发事件,此时将传递缓存的订阅状态。 ### 订阅状态缓存 \{#subscription-status-cache\} Adapty SDK 中实现的缓存会存储用户画像的订阅状态。这意味着即使服务器不可用,也可以访问缓存数据以获取用户画像的订阅状态信息。 但需要注意的是,无法直接从缓存中请求数据。SDK 会每分钟定期向服务器查询,以检查用户画像是否有任何更新或变更。如果存在任何修改(如新的交易记录或其他更新),这些变更将同步至缓存数据,以确保其与服务器保持一致。 --- # File: unity-deal-with-att --- --- title: "在 Unity SDK 中处理 ATT" description: "在 Unity 上开始使用 Adapty,简化订阅设置与管理。" --- 如果您的应用使用了 AppTrackingTransparency 框架,并向用户展示应用跟踪授权请求,则应将[授权状态](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/)发送给 Adapty。 ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetAppTrackingTransparencyStatus(IOSAppTrackingTransparencyStatus.Authorized); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != null) { // handle the error } }); ``` :::warning 我们强烈建议您在该值发生变化时尽早发送,只有这样,数据才能及时传送到您已配置的集成渠道。 ::: --- # File: kids-mode-unity --- --- title: "Unity SDK 中的儿童模式" description: "轻松启用儿童模式以符合 Apple 和 Google 的政策。Unity SDK 中不收集 IDFA、GAID 或广告数据。" --- 如果您的 Unity 应用面向儿童,则必须遵守 [Apple](https://developer.apple.com/kids/) 和 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的政策。如果您使用的是 Adapty SDK,只需几个简单的步骤即可将其配置为符合这些政策,并通过应用商店审核。 ## 需要做什么? \{#whats-required\} 您需要配置 Adapty SDK 以禁止收集以下信息: - [IDFA(广告标识符)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers)(iOS) - [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)(Android) - [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) 此外,我们建议谨慎使用客户用户 ID。`<FirstName.LastName>` 格式的用户 ID 肯定会被视为收集个人数据,使用电子邮件亦然。对于儿童模式,最佳实践是使用随机化或匿名化的标识符(例如哈希 ID 或设备生成的 UUID),以确保合规。 ## 启用儿童模式 \{#enabling-kids-mode\} ### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\} 在 Adapty 看板中,您需要禁用 IP 地址收集。为此,请前往 [App settings](https://app.adapty.io/settings/general),然后在 **Collect users' IP address** 下点击 **Disable IP address collection**。 ### 在您的移动应用代码中进行更新 \{#updates-in-your-mobile-app-code\} Unity 中对儿童模式的支持即将推出! 目前,您可以参阅原生平台指南: - [iOS SDK 中的儿童模式](kids-mode),用于 iOS 配置 - [Android SDK 中的儿童模式](kids-mode-android),用于 Android 配置 --- # File: unity-get-onboardings --- --- title: "在 Unity SDK 中获取用户引导" description: "了解如何在 Adapty for Unity 中获取用户引导。" --- 在 Adapty 看板中[使用编辑工具设计完用户引导的视觉部分](design-onboarding)后,您可以在 Unity 应用中展示它。此过程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。 开始之前,请确保: 1. 您已安装 [Adapty Unity SDK](sdk-installation-unity) 3.14.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)中。 ## 获取用户引导并创建视图 \{#fetch-onboarding-and-create-view\} 当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器的形式存储,包含应用需要获取并展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(例如测验答案或表单输入)。容器还会自动跟踪分析事件,因此您无需单独实现视图跟踪。 为了获得最佳性能,请尽早获取用户引导配置,以便在向用户展示之前有足够时间下载图片。 要获取用户引导,请使用 `GetOnboarding` 方法: ```csharp showLineNumbers Adapty.GetOnboarding("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由减号(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p><p>有关区域设置代码及推荐使用方式的更多信息,请参阅[本地化与区域设置代码](flutter-localizations-and-locale-codes)。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取绝对最新的数据,但无论网络连接多么不稳定,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后保持不变,仅在重新安装应用或手动清理时才会清除。</p><p></p><p>Adapty SDK 在本地以两个层次存储用户引导:上述定期更新的缓存以及备用用户引导。我们还使用 CDN 更快地获取用户引导,并在 CDN 无法访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。</p> | | **loadTimeout** | 默认值:5 秒 | <p>此值限制该方法的超时时间。如果达到超时时间,将返回缓存数据或本地备用内容。</p><p>请注意,在极少数情况下,此方法可能比 `loadTimeout` 中指定的时间稍晚超时,因为该操作在内部可能由多个不同请求组成。</p> | 响应参数: | 参数 | 描述 | |:----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | 一个 [`AdaptyOnboarding`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_onboarding.html) 对象,包含:用户引导标识符和配置、远程配置以及其他几个属性。 | 获取用户引导后,调用 `CreateOnboardingView` 方法。 :::warning `CreateOnboardingView` 方法的结果只能使用一次。如果需要再次使用,请重新调用 `CreateOnboardingView` 方法。在不重新创建的情况下调用两次可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```csharp showLineNumbers AdaptyUI.CreateOnboardingView(onboarding, (view, error) => { // handle the result }); ``` 参数: | 参数 | 是否必填 | 描述 | |:---------------| :------------- |:-----------------------------------------------------------------------------| | **onboarding** | 必填 | 用于获取所需用户引导视图的 `AdaptyOnboarding` 对象。 | | **externalUrlsPresentation** | <p>可选</p><p>默认值:`InAppBrowser`</p> | <p>控制用户引导中链接的打开方式。可用选项:</p><p>- `AdaptyWebPresentation.InAppBrowser` - 在应用内浏览器中打开链接(默认)</p><p>- `AdaptyWebPresentation.ExternalBrowser` - 在设备外部浏览器中打开链接</p><p>使用示例请参阅[自定义用户引导中链接的打开方式](unity-present-onboardings#customize-how-links-open-in-onboardings)。</p> | 成功加载用户引导及其视图配置后,您可以[在移动应用中展示它](unity-present-onboardings)。 ## 使用默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} 通常情况下,用户引导几乎可以即时获取,因此您无需担心加速此过程。但是,如果您拥有大量目标受众和用户引导,且用户的网络连接较弱,获取用户引导可能需要比预期更长的时间。在这种情况下,您可能希望显示默认用户引导,以确保流畅的用户体验,而不是完全不显示用户引导。 为此,您可以使用 `GetOnboardingForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的用户引导。但是,务必理解,推荐的方式是通过 `getOnboarding` 方法获取用户引导,详见上方[获取用户引导](#fetch-onboarding)部分。 :::warning 建议使用 `GetOnboarding` 而非 `GetOnboardingForDefaultAudience`,因为后者有以下重要限制: - **兼容性问题**:在支持多个应用版本时可能产生问题,需要向后兼容的设计,否则较旧版本可能显示不正确。 - **无个性化**:仅显示"所有用户"目标受众的内容,无法根据国家、归因或自定义属性进行定向。 如果对您的使用场景而言,更快的获取速度超过了这些缺点,请按如下所示使用 `GetOnboardingForDefaultAudience`。否则,请按[上方](#fetch-onboarding)所述使用 `GetOnboarding`。 ::: ```csharp showLineNumbers Adapty.GetOnboardingForDefaultAudience("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` 参数: | 参数 | 是否必填 | 描述 | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 | | **locale** | <p>可选</p><p>默认值:`en`</p> | <p>用户引导本地化的标识符。该参数应为由减号(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。</p><p></p><p>示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。</p> | | **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | <p>默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获取最新数据。</p><p></p><p>但是,如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时返回缓存数据。在这种情况下,用户可能无法获取绝对最新的数据,但无论网络连接多么不稳定,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。</p><p></p><p>请注意,缓存在应用重启后保持不变,仅在重新安装应用或手动清理时才会清除。</p><p></p><p>Adapty SDK 在本地以两个层次存储用户引导:上述定期更新的缓存以及备用用户引导。我们还使用 CDN 更快地获取用户引导,并在 CDN 无法访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。</p> | --- # File: unity-present-onboardings --- --- title: "在 Unity SDK 中展示用户引导" description: "了解如何有效地展示用户引导以提升转化率。" --- 如果你已经在编辑工具中自定义了用户引导,就不需要在 Unity 应用代码中另行处理渲染逻辑——该用户引导已经包含了展示内容和展示方式的完整配置。 开始之前,请确保: 1. 已安装 [Adapty Unity SDK](sdk-installation-unity) 3.14.0 或更高版本。 2. 已[创建用户引导](create-onboarding)。 3. 已将用户引导添加到[版位](placements)。 要展示用户引导,请对 `CreateOnboardingView` 方法创建的 `view` 调用 `view.Present()` 方法。每个 `view` 只能使用一次。如需再次展示付费墙,请重新调用 `CreateOnboardingView` 创建新的 `view` 实例。 :::warning 在未重新创建 `view` 的情况下复用同一个 `view`,可能会导致 `AdaptyUIError.viewAlreadyPresented` 错误。 ::: ```csharp showLineNumbers title="Unity" view.Present((presentError) => { if (presentError != null) { // handle the error } }; ``` ## 配置 iOS 展示样式 \{#configure-ios-presentation-style\} 通过将 `iosPresentationStyle` 参数传递给 `Present()` 方法,可配置用户引导在 iOS 上的展示方式。该参数接受 `AdaptyUIIOSPresentationStyle.FullScreen`(默认值)或 `AdaptyUIIOSPresentationStyle.PageSheet` 值。 ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` ## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\} :::important 自定义用户引导中链接打开方式的功能从 Adapty SDK v3.15 开始支持。 ::: 默认情况下,用户引导中的链接会在应用内浏览器中打开,让用户无需切换应用即可直接查看网页内容,体验更加流畅。 如需改为在外部浏览器中打开链接,请将 `AdaptyWebPresentation.ExternalBrowser` 传入 `CreateOnboardingView` 方法: ```csharp showLineNumbers title="Unity" AdaptyUI.CreateOnboardingView( onboarding, AdaptyWebPresentation.ExternalBrowser, // default — InAppBrowser (view, error) => { if (error != null) { // handle the error return; } // present the onboarding view view.Present((presentError) => { if (presentError != null) { // handle the error } }); } ); ``` 可用选项: - `AdaptyWebPresentation.InAppBrowser` - 在应用内浏览器中打开链接(默认) - `AdaptyWebPresentation.ExternalBrowser` - 在设备的外部浏览器中打开链接 --- # File: unity-handling-onboarding-events --- --- title: "在 Unity SDK 中处理用户引导事件" description: "使用 Adapty 在 Unity 中处理用户引导相关事件。" --- 在开始之前,请确保: 1. 您已安装 [Adapty Unity SDK](sdk-installation-unity) 3.14.0 或更高版本。 2. 您已[创建用户引导](create-onboarding)。 3. 您已将用户引导添加到[版位](placements)。 使用编辑工具配置的用户引导会生成应用可以响应的事件。请参阅以下内容了解如何响应这些事件。 要在 Unity 应用中控制或监控用户引导界面上发生的流程,请实现 `AdaptyOnboardingsEventsListener` 接口。 ## 自定义动作 \{#custom-actions\} 在编辑工具中,您可以为按钮添加**自定义**动作并为其分配一个 ID。 <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 然后,您可以在代码中使用此 ID 并将其作为自定义动作处理。例如,当用户点击自定义按钮(如**登录**或**允许通知**)时,将触发 `OnboardingViewOnCustomAction` 方法,其中 `actionId` 参数为编辑工具中的 **Action ID**。您可以创建自己的 ID,例如 "allowNotifications"。 要处理用户引导事件,请实现 `AdaptyOnboardingsEventsListener` 接口: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { void Start() { Adapty.SetOnboardingsEventsListener(this); } public void OnboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { if (actionId == "allowNotifications") { // request notification permissions } } public void OnboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error ) { // handle errors } // Implement other required interface methods (see examples below) } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## 关闭用户引导 \{#closing-onboarding\} 当用户点击分配了**关闭**动作的按钮时,用户引导即被视为已关闭。 <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important 请注意,您需要自行管理用户关闭用户引导后发生的事情。例如,您需要停止显示用户引导本身。 ::: 在您的类中实现 `OnboardingViewOnCloseAction` 方法: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { view.Dismiss((error) => { if (error != null) { // handle the error } }); } // ... other interface methods } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## 打开付费墙 \{#opening-a-paywall\} :::tip 如果你想在用户引导内部打开付费墙,请处理此事件。如果你想在付费墙关闭后再打开另一个付费墙,有一种更直接的方式——处理 [`OnboardingViewOnCloseAction`](#closing-onboarding) 并直接打开付费墙,无需依赖事件数据。 ::: 在用户引导中使用付费墙最流畅的方式,是将 action ID 设置为与付费墙版位 ID 相同。这样,在收到 `OnboardingViewOnPaywallAction` 事件后,你可以直接用版位 ID 获取并打开对应的付费墙。 请注意,在 iOS 上,屏幕上一次只能显示一个视图(付费墙或用户引导)。如果您在用户引导上叠加显示付费墙,则无法以编程方式控制后台的用户引导。尝试关闭用户引导将会关闭付费墙,导致用户引导仍然可见。为避免这种情况,请始终在显示付费墙之前先关闭用户引导视图。 ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { // Dismiss onboarding before presenting paywall view.Dismiss((dismissError) => { if (dismissError != null) { // handle the error return; } Adapty.GetPaywall(actionId, (paywall, error) => { if (error != null) { // handle the error return; } AdaptyUI.CreatePaywallView(paywall, (paywallView, createError) => { if (createError != null) { // handle the error return; } paywallView.Present((presentError) => { if (presentError != null) { // handle the error } }); }); }); }); } // ... other interface methods } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## 完成用户引导加载 \{#finishing-loading-onboarding\} 当用户引导加载完成时,实现 `OnboardingViewDidFinishLoading` 方法: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta ) { // handle loading completion } // ... other interface methods } ``` <Details> <summary>事件示例(点击展开)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## 追踪导航 \{#tracking-navigation\} `OnboardingViewOnAnalyticsEvent` 方法在用户引导流程中发生各种分析事件时被调用。 `analyticsEvent` 对象可以是以下类型之一: | 类型 | 描述 | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | 用户引导已加载时 | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | 任何屏幕显示时 | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | 屏幕完成时。包含可选的 `ElementId`(已完成元素的标识符)和可选的 `Reply`(用户的响应)。当用户执行任何操作退出屏幕时触发。| | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | 第二个屏幕显示时 | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | 通过输入字段收集用户邮箱时触发 | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | 当用户到达具有 `final` ID 的屏幕时触发。如果您需要此事件,请[将 `final` ID 分配给最后一个屏幕](design-onboarding)。| | `AdaptyOnboardingsAnalyticsEventUnknown` | 用于任何无法识别的事件类型。包含 `Name`(未知事件的名称)和 `meta`(附加元数据)| 每个事件都包含 `meta` 信息: | 字段 | 描述 | |------------|-------------| | `OnboardingId` | 用户引导流程的唯一标识符 | | `ScreenClientId` | 当前屏幕的标识符 | | `ScreenIndex` | 当前屏幕在流程中的位置 | | `ScreensTotal` | 流程中的屏幕总数 | 以下是如何将分析事件用于追踪的示例: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent analyticsEvent ) { switch (analyticsEvent) { case AdaptyOnboardingsAnalyticsEventOnboardingStarted: // track onboarding start TrackEvent("onboarding_started", meta); break; case AdaptyOnboardingsAnalyticsEventScreenPresented: // track screen presentation TrackEvent("screen_presented", meta); break; case AdaptyOnboardingsAnalyticsEventScreenCompleted screenCompleted: // track screen completion with user response TrackEvent("screen_completed", meta, screenCompleted.ElementId, screenCompleted.Reply); break; case AdaptyOnboardingsAnalyticsEventOnboardingCompleted: // track successful onboarding completion TrackEvent("onboarding_completed", meta); break; case AdaptyOnboardingsAnalyticsEventUnknown unknownEvent: // handle unknown events TrackEvent(unknownEvent.Name, meta); break; // handle other cases as needed } } // ... other interface methods } ``` :::note `TrackEvent` 方法是一个占位符,您需要自行实现它以将分析数据发送到您首选的分析服务。 ::: <Details> <summary>事件示例(点击展开)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: unity-onboarding-input --- --- title: "在 Unity SDK 中处理用户引导数据" description: "使用 Adapty SDK 在 Unity 应用中保存和使用用户引导数据。" --- 当用户回答测验问题或在输入框中输入数据时,`OnboardingViewOnStateUpdatedAction` 方法将被调用。您可以在代码中保存或处理字段类型。 在您的类中实现 `OnboardingViewOnStateUpdatedAction` 方法: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { switch (@params) { case AdaptyOnboardingsSelectParams selectParams: // handle single selection break; case AdaptyOnboardingsMultiSelectParams multiSelectParams: // handle multiple selections break; case AdaptyOnboardingsInputParams inputParams: // handle text input break; case AdaptyOnboardingsDatePickerParams datePickerParams: // handle date selection break; } } // ... other interface methods } ``` 参数说明: | 参数 | 描述 | |----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | 输入元素的唯一标识符。您可以用它在保存时将问题与答案关联起来。 | | `@params` | 用户输入数据对象。可以是以下类型之一。 | | `AdaptyOnboardingsSelectParams` | 从选项中单选。包含 `Id`、`Value`、`Label` | | `AdaptyOnboardingsMultiSelectParams` | 从选项中多选。包含 `Params` 列表(每个包含 `Id`、`Value`、`Label`)<br/>• `input`:包含 `type`、`value` 的对象<br/>• `datePicker`:包含 `day`、`month`、`year` 的对象 | | `AdaptyOnboardingsInputParams` | 文本输入框。包含 `Input`,可以是 `AdaptyOnboardingsTextInput`、`AdaptyOnboardingsEmailInput` 或 `AdaptyOnboardingsNumberInput` | | `AdaptyOnboardingsDatePickerParams` | 日期选择。包含可为空的 `Day`、`Month`、`Year` | <Details> <summary>已保存数据示例(您的实现可能有所不同)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## 使用场景 \{#use-cases\} ### 用数据丰富用户画像 \{#enrich-user-profiles-with-data\} 如果您希望立即将输入数据与用户画像关联,避免重复询问相同信息,则需要在处理操作时使用输入数据[更新用户画像](unity-setting-user-attributes)。 例如,您要求用户在 ID 为 `name` 的文本框中输入姓名,并希望将该字段的值设为用户的名字;同时要求用户在 `email` 字段中输入电子邮件。在您的应用代码中,可以如下实现: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsInputParams inputParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "name": if (inputParams.Input is AdaptyOnboardingsTextInput textInput) { builder.SetFirstName(textInput.Value); } break; case "email": if (inputParams.Input is AdaptyOnboardingsEmailInput emailInput) { builder.SetEmail(emailInput.Value); } break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` ### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\} 通过在用户引导中使用测验,您还可以根据用户完成用户引导后的情况自定义向其展示的付费墙。 例如,您可以询问用户的运动经验,并向不同用户群体展示不同的 CTA 和产品。 1. 在用户引导编辑工具中[添加测验](onboarding-quizzes),并为其选项分配有意义的 ID。 2. 根据 ID 处理测验响应,并为用户[设置自定义属性](unity-setting-user-attributes)。 ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsSelectParams selectParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "experience": // set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.SetCustomStringAttribute("experience", selectParams.Value); break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` 3. 为每个自定义属性值[创建市场细分](segments)。 4. 创建一个[版位](placements),并为您创建的每个市场细分添加[目标受众](audience)。 5. 在您的应用代码中为该版位[展示付费墙](unity-paywalls)。如果您的用户引导中有一个按钮用于打开付费墙,请将付费墙代码作为[该按钮操作的响应](unity-handling-onboarding-events#opening-a-paywall)来实现。 --- # File: unity-sdk-call-order --- --- title: "Unity SDK 中的调用顺序" description: "通过按照正确顺序调用 Adapty SDK 方法,避免付费权限丢失、归因缺失以及偶发的 #2002 错误。" --- `Adapty.Activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其完成回调触发之前,SDK 没有任何状态。在 `Activate()` 之前或与其并行发起的任何调用都会失败,并返回 [`#2002 notActivated`](unity-handle-errors#custom-network-codes) 错误。 如果你的应用需要对用户进行身份验证,并且在启动后才能获取到 customer user ID,请在获取到该 ID 时调用 `Adapty.Identify()`。在 `Identify` 回调触发之前,不要调用任何用户操作相关的方法。与该调用产生竞争的请求,要么会以 [`#3006 profileWasChanged`](unity-handle-errors#custom-network-codes) 错误失败,要么会落到激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属并不总能转移到已识别的用户画像上。如果你的应用不需要用户身份验证,则跳过 `Identify`,继续使用匿名用户画像即可。 MMP 和分析类 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待其 UID 回调后再调用 `Adapty.Activate`。否则 MMP ID 会绑定到一个短暂的匿名用户画像上,不一定能迁移到已识别的用户画像。关于 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。 ## 正确的操作顺序 \{#the-correct-order\} 你的操作路径取决于两件事:何时获取到 customer user ID,以及是否使用了 MMP 或数据分析 SDK。 - **步骤 2 和 5**:所有应用必须完成。初始化 SDK,然后调用 SDK 方法。 - **步骤 1 和 3**:仅在集成 MMP 或数据分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。 - **步骤 4**:仅在应用需要用户登录、且在启动后才能获取 customer user ID 时需要。 如果在应用启动时已知客户用户 ID,可直接在 `Activate()` 中传入(步骤 2a)。这条路径不会创建匿名用户画像,因此步骤 4 无需执行。 | 步骤 | 调用 | 时机 | 说明 | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | 初始化 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动,最先执行 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerId`。 | | 2a | `Adapty.Activate(builder.Build(), ...)` 并在 builder 上设置 `SetCustomerUserId` | 应用启动,步骤 1 之后,如果已有 customer user ID | 推荐方式,不会创建匿名用户画像。 | | 2b | `Adapty.Activate(builder.Build(), ...)` 不设置 `SetCustomerUserId` | 应用启动,步骤 1 之后,如果没有 customer user ID(或从不收集) | Adapty 会创建一个匿名用户画像。 | | 3 | 为每个 MMP 调用 `Adapty.SetIntegrationIdentifier(key, value, callback)` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,确保 MMP ID 关联到正确的用户画像。 | | 4 | `Adapty.Identify("YOUR_USER_ID", callback)` | 步骤 3 之后(若无 MMP 则在步骤 2 之后),步骤 5 之前——仅适用于路径 2b 且需要身份验证时 | 等待完成回调。在 `Identify` 执行期间并发调用会产生 `#3006 profileWasChanged` 错误。 | | 5 | `GetPaywall`、`GetPaywallProducts`、`RestorePurchases`、`MakePurchase`、`UpdateAttribution`、`UpdateProfile` | 如果调用了 `Identify`,则在步骤 4 之后;否则在步骤 3 之后(若无 MMP 则在步骤 2 之后) | 这些调用需要一个稳定的用户画像。 | :::important 跳过这些步骤会导致回访用户失去高级访问权限、用户画像缺少 `appsflyer_id`,以及付费墙按错误的目标受众返回。 ::: ## Web2app 与网页漏斗安装 \{#web2app-and-web-funnel-installs\} 如果用户在网页端(Stripe、Paddle)完成购买后再安装原生应用,设备首次调用 `Activate()` 时会创建一个新的匿名用户画像,该画像不会与网页端的用户画像关联。如果能在应用启动前(通过登录流程或安装来源追踪)拿到客户用户 ID,请直接传入 `Activate()`;否则,网页端的购买记录在设备上将不可见,直到你调用 `Identify("YOUR_USER_ID")` 再调用 `RestorePurchases` 才能同步。 关于每次网页端结账时需要传入的元数据,请参阅: - [Stripe](stripe) - [Paddle](paddle) --- # File: unity-optimize-paywall-fetching --- --- title: "在 Unity SDK 中优化付费墙获取" description: "可靠地获取 Adapty 付费墙:适用于 Unity 的时机、缓存与备用方案。" --- 在 Unity 中可靠地获取付费墙需要做到三点:快速渲染、返回面向目标受众的付费墙,以及在网络较慢时优雅降级。以下规则涵盖了实现这一目标所需的时机、缓存与备用方案。 :::tip 以下规则假设 `Adapty.Activate()` 和 `Adapty.Identify()` 已经执行完成。详见 [Unity SDK 调用顺序](unity-sdk-call-order)。 ::: ## 规则与注意事项 \{#rules-and-pitfalls\} | 应该这样做 | 不应该这样做 | 原因 | |---|---|---| | 仅在即将展示付费墙时才获取对应版位。 | 在启动时并发预取所有版位。 | 批量预取会阻塞主线程,导致启动期间出现黑屏。 | | 在归因数据有机会解析后再调用 `GetPaywall`——例如在 `Activate` 之后等待 1–2 秒,或等 `OnLoadLatestProfile` 触发后再调用。 | 在 `Awake()` 中调用 `GetPaywall`。 | 此时归因数据尚未到达。付费墙会按默认目标受众解析,悄悄绕过市场细分和 ASA 个性化逻辑。 | | 设置 `loadTimeout`,并为每个版位配置[备用付费墙](fallback-paywalls)。 | 无限等待 `GetPaywall` 返回。 | 没有超时限制时,网络较差的用户会看到空白屏幕,直到网络恢复——或者直接关掉应用。 | 有关 `fetchPolicy` 和 `loadTimeout` 参数的说明,请参阅[获取付费墙和产品](fetch-paywalls-and-products-unity);有关如何选择合适版位的信息,请参阅[版位](placements)。 ## 针对弱网环境进行优化 \{#tune-for-poor-connectivity\} 对于网络连接持续较差的市场(农村地区、交通途中、受路由影响的地区): - 除首次请求外,每次获取时将 `fetchPolicy` 设置为 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`。 - 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。 - 将 `loadTimeout` 设置为 3–5 秒,超时后接受备用付费墙。 - 不要将付费墙的展示逻辑阻塞在 `GetProfile` 上。独立调用 `GetPaywall`,避免因用户画像加载缓慢而影响界面显示。 --- # File: unity-test --- --- title: "在 Unity SDK 中测试与发布" description: "了解如何使用 Adapty SDK 测试和发布您的 Unity 应用。" --- 如果您已经在 Unity 应用中集成了 Adapty SDK,您需要测试所有内容是否正确配置,以及在 iOS 和 Android 平台上购买流程是否按预期运行。这包括使用 Apple 沙盒环境和 Google Play 测试环境测试 SDK 集成和实际购买流程。 ## 测试您的应用 \{#test-your-app\} 如需全面测试应用内购买,请参阅我们针对各平台的测试指南:[iOS 测试指南](test-purchases-in-sandbox) 和 [Android 测试指南](testing-on-android)。 ## 准备发布 \{#prepare-for-release\} 在将应用提交到商店之前,请遵循[发布检查清单](release-checklist)以确认: - 商店连接和服务器通知已配置 - 购买完成并已上报至 Adapty - 访问等级解锁和恢复功能正常 - 隐私和审核要求已满足 --- # File: InvalidProductIdentifiers-unity --- --- title: "修复 Unity SDK 中的 Code-1000 noProductIDsFound 错误" description: "解决在 Adapty 中管理订阅时出现的无效产品标识符错误。" --- 1000 代码错误 `noProductIDsFound` 表示你在付费墙中请求的产品虽然已在 App Store 中列出,但目前无法购买。该错误有时会附带 `InvalidProductIdentifiers` 警告。如果只出现警告而没有错误,可以忽略。 如果你遇到了 `noProductIDsFound` 错误,请按以下步骤排查: ## 步骤 1. 检查 Bundle ID \{#step-2-check-bundle-id\} 1. 打开 [App Store Connect](https://appstoreconnect.apple.com/apps)。选择你的应用,进入 **General** → **App Information** 页面。 2. 在 **General Information** 子区块中复制 **Bundle ID**。 <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. 在 Adapty 顶部菜单中打开 [**App settings** -> **iOS SDK** 标签页](https://app.adapty.io/settings/ios-sdk),将复制的值粘贴到 **Bundle ID** 字段中。 <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. 返回 App Store Connect 中的 **App information** 页面,复制其中的 **Apple ID**。 5. 在 Adapty 看板的 [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) 页面中,将该 ID 粘贴到 **Apple app ID** 字段。 ## 步骤 2:检查产品 \{#step-3-check-products\} 1. 前往 **App Store Connect**,在左侧菜单中导航至 [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions)。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,你将在 **Subscriptions** 区域看到所有产品。 3. 确认你要测试的产品已标记为 **Ready to Submit**。 <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 将表格中的产品 ID 与 Adapty 看板中 [**Products**](https://app.adapty.io/products) 标签页里的产品 ID 进行比对。如果 ID 不匹配,请从表格中复制该产品 ID,然后在 Adapty 看板中[创建产品](create-product)。 <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 3 步:检查产品可用性 \{#step-4-check-product-availability\} 1. 返回 **App Store Connect**,打开同一个 **Subscriptions** 页面。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称,查看你的产品。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 滚动至 **Availability** 部分,确认所有所需国家和地区均已列出。 <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 4 步:检查产品价格 \{#step-5-check-product-prices\} 1. 再次前往 **App Store Connect** 中的 **Monetization** → **Subscriptions** 部分。 <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 点击订阅组名称。 3. 选择您要测试的产品。 <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. 向下滚动至 **Subscription Pricing**,展开 **Current Pricing for New Subscribers** 部分。 <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. 确保所有必填价格均已填写。 <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 第 5 步:确认应用付费状态、银行账户及税务表格均已生效 \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. 在 [**App Store Connect**](https://appstoreconnect.apple.com/) 主页,点击 **Business**。 <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. 选择你的公司名称。 <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. 向下滚动,确认你的 **Paid Apps Agreement**、**Bank Account** 和 **Tax forms** 均显示为 **Active**。 按照以上步骤,您应该能够解决 `InvalidProductIdentifiers` 警告,并让您的产品在商店中上线。 ## 第 6 步:若产品卡住,请重新创建 \{#step-6-recreate-the-product-if-its-stuck\} 即使第 1–5 步全部通过——状态为 `Approved`、Bundle ID 匹配、API 密钥有效——SDK 仍然可能返回 `1000 noProductIDsFound`。这种情况下,产品可能卡在了 Apple 的注册表中。Apple 的产品注册表偶尔会进入一种状态:产品在 App Store Connect 界面中存在,但无法通过 StoreKit 的查找路径被识别。 在 App Store Connect 中删除该产品,然后用相同的产品 ID 重新创建。重新创建后,最长需要等待 24 小时才能完成同步。 --- # File: cantMakePayments-unity --- --- title: "修复 Unity SDK 中 Code-1003 cantMakePayment 错误" description: "解决在 Adapty 中管理订阅时出现的支付错误。" --- 1003 错误 `cantMakePayments` 表示该设备无法进行应用内购买。 如果你遇到了 `cantMakePayments` 错误,通常是由以下原因之一导致的: - 设备限制:该错误与 Adapty 无关。请参阅下方的修复方法。 - 观察者模式配置:`makePurchase` 方法与观察者模式不能同时使用。请参阅下方相关章节。 ## 问题:设备限制 \{#issue-device-restrictions\} | 问题 | 解决方案 | |---------------------------|---------------------------------------------------------| | 屏幕使用时间限制 | 在 [Screen Time](https://support.apple.com/en-us/102470) 中关闭应用内购买限制 | | 账户被暂停 | 联系 Apple 支持以解决账户问题 | | 地区限制 | 使用受支持地区的 App Store 账户 | ## 问题:同时使用观察者模式和 makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} 如果你使用 `makePurchases` 来处理购买,则无需启用观察者模式。[观察者模式](observer-vs-full-mode) 仅在你自行实现购买逻辑时才需要使用。 因此,如果你正在使用 `makePurchase`,可以安全地从 SDK 激活代码中移除对观察者模式的启用。 --- # File: migration-to-unity-sdk-314 --- --- title: "迁移 Adapty Unity SDK 至 v3.14" description: "迁移至 Adapty Unity SDK v3.14,获得更好的性能和新的变现功能。" --- Adapty SDK 3.14.0 是一个主要版本,带来了一些改进,但可能需要你执行一些迁移步骤: 1. 为付费墙事件添加独立的事件监听器。 2. 将 `AdaptyUI.CreateView` 重命名为 `AdaptyUI.CreatePaywallView` 及相关方法。 3. 更新 `MakePurchase` 方法,改用 `AdaptyPurchaseParameters` 替代单独参数。 4. 将 `SetFallbackPaywalls` 替换为 `SetFallback` 方法。 5. 更新付费墙属性访问方式,改用 `AdaptyPlacement`。 6. 更新远程配置访问方式,改用 `AdaptyRemoteConfig` 对象。 7. 将 `AdaptyPaywall` 模型中的 `VendorProductIds` 替换为 `ProductIdentifiers`。 8. 更新 `GetPaywall` 的获取策略,改用 `AdaptyFetchPolicy`。 ## 付费墙事件的独立事件监听器 \{#separate-event-listener-for-paywall-events\} 如果你展示的付费墙是通过[付费墙编辑工具](adapty-paywall-builder)设计的,付费墙视图事件现在使用专用的 `AdaptyPaywallsEventsListener` 接口和 `SetPaywallsEventsListener` 方法。核心 `AdaptyEventListener` 接口仍用于用户画像更新和安装详情。 ```diff showLineNumbers using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, - AdaptyEventListener { + AdaptyEventListener, + AdaptyPaywallsEventsListener { void Start() { Adapty.SetEventListener(this); + Adapty.SetPaywallsEventsListener(this); } // AdaptyEventListener methods public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } + // AdaptyPaywallsEventsListener methods + // Implement paywall event handlers here } ``` [了解有关处理付费墙事件的更多信息](unity-handling-events)。 ## 重命名视图创建与展示方法 \{#rename-view-creation-and-presentation-methods\} 视图创建和展示方法已重命名: ```diff showLineNumbers using AdaptySDK; - AdaptyUI.CreateView(paywall, parameters, (view, error) => { + AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { // handle the error return; } - AdaptyUI.PresentView(view, (error) => { + AdaptyUI.PresentPaywallView(view, (error) => { // handle the error }); }); } ``` 同样,关闭方法也已重命名: ```diff showLineNumbers - AdaptyUI.DismissView(view, (error) => { + AdaptyUI.DismissPaywallView(view, (error) => { // handle the error }); ``` ## 更新 MakePurchase 方法 \{#update-makepurchase-method\} `MakePurchase` 方法现在使用 `AdaptyPurchaseParameters`,替代了原来独立的 `subscriptionUpdateParams` 和 `isOfferPersonalized` 参数。这样做可以提升类型安全性,并为未来扩展购买参数预留空间。 ```diff showLineNumbers using AdaptySDK; void MakePurchase( AdaptyPaywallProduct product, AdaptySubscriptionUpdateParameters subscriptionUpdate, bool? isOfferPersonalized ) { - Adapty.MakePurchase(product, subscriptionUpdate, isOfferPersonalized, (result, error) => { + var parameters = new AdaptyPurchaseParametersBuilder() + .SetSubscriptionUpdateParams(subscriptionUpdate) + .SetIsOfferPersonalized(isOfferPersonalized) + .Build(); + + Adapty.MakePurchase(product, parameters, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successful purchase break; default: break; } }); } ``` 如果不需要额外参数,可以直接使用: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { // handle purchase result }); } ``` ## 更新备用付费墙方法 \{#update-fallback-method\} :::important 升级到 Unity SDK 3.14 时,你需要从 Adapty 看板下载新的备用文件,并替换项目中的现有文件。 ::: 设置备用付费墙的方法已更新。`SetFallbackPaywalls` 方法已重命名为 `SetFallback`: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { #if UNITY_IOS var assetId = "adapty_fallback_ios.json"; #elif UNITY_ANDROID var assetId = "adapty_fallback_android.json"; #else var assetId = ""; #endif - Adapty.SetFallbackPaywalls(assetId, (error) => { + Adapty.SetFallback(assetId, (error) => { // handle the error }); } ``` 请查看 [在 Unity 中使用备用付费墙](unity-use-fallback-paywalls) 页面中的完整代码示例。 ## 更新付费墙属性访问方式 \{#update-paywall-property-access\} 以下属性已从 `AdaptyPaywall` 移至 `AdaptyPlacement`: ```diff showLineNumbers using AdaptySDK; void ProcessPaywall(AdaptyPaywall paywall) { - var abTestName = paywall.ABTestName; - var audienceName = paywall.AudienceName; - var revision = paywall.Revision; - var placementId = paywall.PlacementId; + var abTestName = paywall.Placement.ABTestName; + var audienceName = paywall.Placement.AudienceName; + var revision = paywall.Placement.Revision; + var placementId = paywall.Placement.Id; } ``` ## 更新远程配置访问方式 \{#update-remote-config-access\} 远程配置属性已被重构为 `AdaptyRemoteConfig` 对象,以提供更好的组织结构: ```diff showLineNumbers using AdaptySDK; void ProcessRemoteConfig(AdaptyPaywall paywall) { - var remoteConfigString = paywall.RemoteConfigString; - var locale = paywall.Locale; - var remoteConfigDict = paywall.RemoteConfig; + var remoteConfigString = paywall.RemoteConfig.Data; + var locale = paywall.RemoteConfig.Locale; + var remoteConfigDict = paywall.RemoteConfig.Dictionary; } ``` ## 更新 AdaptyPaywall 模型用法 \{#update-adaptypaywall-model-usage\} `VendorProductIds` 属性已被弃用,请改用 `ProductIdentifiers`。新属性返回 `AdaptyProductIdentifier` 对象,而非简单的字符串,能提供更结构化的产品信息。 ```diff showLineNumbers using AdaptySDK; void ProcessPaywallProducts(AdaptyPaywall paywall) { - var productIds = paywall.VendorProductIds; - foreach (var vendorId in productIds) { - // use vendorId - } + var productIdentifiers = paywall.ProductIdentifiers; + foreach (var productId in productIdentifiers) { + var vendorId = productId.VendorProductId; + // use vendorId + } } ``` `AdaptyProductIdentifier` 对象通过 `VendorProductId` 属性提供对厂商产品 ID 的访问,在保持原有功能的同时,为未来的功能扩展提供了更清晰的结构。 ## 更新 GetPaywall 获取策略 \{#update-getpaywall-fetch-policy\} `GetPaywall` 方法中的 `fetchPolicy` 参数类型已从 `AdaptyPaywallFetchPolicy` 更改为 `AdaptyPlacementFetchPolicy`。此更改统一了 SDK 中获取策略的使用方式。 ```diff showLineNumbers using AdaptySDK; void GetPaywall(string placementId) { - Adapty.GetPaywall(placementId, AdaptyPaywallFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { + Adapty.GetPaywall(placementId, AdaptyPlacementFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { // handle the result }); } ``` --- # File: migration-to-unity-sdk-34 --- --- title: "迁移 Adapty Unity SDK 至 v3.4" description: "迁移至 Adapty Unity SDK v3.4,获得更好的性能与全新的变现功能。" --- Adapty SDK 3.4.0 是一个重要版本,引入了需要你进行迁移操作的改进内容。 ## 更新备用付费墙文件 \{#update-fallback-paywall-files\} 更新您的备用付费墙文件,以确保与新 SDK 版本的兼容性: 1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。 2. 将移动应用中的现有备用付费墙[替换为新文件](unity-use-fallback-paywalls)。 ## 更新 Observer Mode 的实现方式 \{#update-implementation-of-observer-mode\} 如果你正在使用 Observer Mode,请确保更新其实现方式。 之前,向 Adapty 上报交易时使用的是不同的方法。在新版本中,Android 和 iOS 应均统一使用 `reportTransaction` 方法。该方法会明确地将每笔交易上报给 Adapty,确保其被正确识别。如果使用了付费墙,请传入 variation ID 以将交易与付费墙关联。 :::warning **不要跳过交易上报!** 如果不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会发送到集成渠道。 ::: ```diff showLineNumbers - #if UNITY_ANDROID && !UNITY_EDITOR - Adapty.RestorePurchases((profile, error) => { - // handle the error - }); - #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` --- # File: migration-to-unity330 --- --- title: "迁移 Adapty Unity SDK 至 v3.3" description: "迁移至 Adapty Unity SDK v3.3,获得更好的性能和全新的变现功能。" --- Adapty SDK 3.3.0 是一个重要版本,带来了一些改进,但可能需要你执行一些迁移步骤。 1. 升级至 Adapty SDK v3.3.x。 2. 重命名了 Adapty SDK 中 Adapty 和 AdaptyUI 模块的多个类、属性和方法。 3. 从现在起,`SetLogLevel` 方法接受一个回调作为参数。 4. 从现在起,`PresentCodeRedemptionSheet` 方法接受一个回调作为参数。 5. 更改付费墙视图的创建方式。 6. 移除 `GetProductsIntroductoryOfferEligibility` 方法。 7. 将备用付费墙保存为独立文件(每个平台一个),放在 `Assets/StreamingAssets/` 目录下,并将文件名传递给 `SetFallbackPaywalls` 方法。 8. 更新购买流程。 9. 更新付费墙编辑工具事件的处理方式。 10. 更新付费墙编辑工具付费墙错误的处理方式。 11. 更新 Adjust、Amplitude、AppMetrica、Appsflyer、Branch、Firebase 和 Google Analytics、Mixpanel、OneSignal、Pushwoosh 的集成配置。 13. 更新观察者模式的实现方式。 14. 使用显式 `Activate` 调用更新 Unity 插件初始化。 ## 将 Adapty Unity SDK 升级到 3.3.x \{#upgrade-adapty-unity-sdk-to-33x\} 在此版本之前,Adapty SDK 是确保 Adapty 在应用中正常运行所必需的核心 SDK,而 AdaptyUI SDK 则是可选 SDK,仅在使用 Adapty 付费墙编辑工具时才需要安装。 从 3.3.0 版本开始,AdaptyUI SDK 已被弃用,AdaptyUI 已作为模块合并到 Adapty SDK 中。由于此变更,您需要移除 AdaptyUI SDK 并重新安装 Adapty SDK。 1. 从项目中移除 **AdaptySDK** 和 **AdaptyUISDK** 的包依赖项。 2. 删除 **AdaptySDK** 和 **AdaptyUISDK** 文件夹。 3. 按照 [Unity 的 Adapty SDK 安装与配置](sdk-installation-unity) 页面的说明,重新导入 AdaptySDK 包。 ## 重命名 \{#renamings\} 1. 在 Adapty 模块中重命名: | 旧版本 | 新版本 | | ------------------------- | ------------------------ | | Adapty.sdkVersion | Adapty.SDKVersion | | Adapty.LogLevel | AdaptyLogLevel | | Adapty.Paywall | AdaptyPaywall | | Adapty.PaywallFetchPolicy | AdaptyPaywallFetchPolicy | | PaywallProduct | AdaptyPaywallProduct | | Adapty.Profile | AdaptyProfile | | Adapty.ProfileParameters | AdaptyProfileParameters | | ProfileGender | AdaptyProfileGender | | Error | AdaptyError | 2. 在 AdaptyUI 模块中重命名: | 旧版本 | 新版本 | | ------------------ | ------------------ | | CreatePaywallView | CreateView | | PresentPaywallView | PresentView | | DismissPaywallView | DismissView | | AdaptyUI.View | AdaptyUIView | | AdaptyUI.Action | AdaptyUIUserAction | ## 更改 SetLogLevel 方法 \{#change-the-setloglevel-method\} 从现在起,`SetLogLevel` 方法接受回调作为参数。 ```diff showLineNumbers - Adapty.SetLogLevel(Adapty.LogLevel.Verbose); + Adapty.SetLogLevel(Adapty.LogLevel.Verbose, null); // or you can pass the callback to handle the possible error ``` ## 更改 PresentCodeRedemptionSheet 方法 \{#change-the-presentcoderedemptionsheet-method\} 从现在起,`PresentCodeRedemptionSheet` 方法接受回调作为参数。 ```diff showLineNumbers - Adapty.PresentCodeRedemptionSheet(); + Adapty.PresentCodeRedemptionSheet(null); // or you can pass the callback to handle the possible error ``` ## 更改付费墙视图的创建方式 \{#change-how-the-paywall-view-is-created\} 完整代码示例请参阅[获取使用付费墙编辑工具设计的付费墙视图配置](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 ```diff showLineNumbers + var parameters = new AdaptyUICreateViewParameters() + .SetPreloadProducts(true); - AdaptyUI.CreatePaywallView( + AdaptyUI.CreateView( paywall, - preloadProducts: true, + parameters, (view, error) => { // use the view }); ``` ## 移除 GetProductsIntroductoryOfferEligibility 方法 \{#remove-the-getproductsintroductoryoffereligibility-method\} 在 Adapty iOS SDK 3.3.0 之前,无论用户是否符合资格,产品对象始终包含优惠信息。您必须在使用优惠之前手动检查资格。 现在,产品对象仅在用户符合资格时才包含优惠信息。这意味着您不再需要检查资格——如果存在优惠,则用户符合资格。 ## 备用付费墙的传入方式更新 \{#update-method-for-providing-fallback-paywalls\} 在此版本之前,备用付费墙以序列化 JSON 的形式传入。从 v 3.3.0 开始,机制发生了变化: 1. 将备用付费墙保存到 `/Assets/StreamingAssets/` 目录下的文件中,Android 和 iOS 各一个文件。 2. 将文件名传入 `SetFallbackPaywalls` 方法。 你的代码需要做如下修改: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { + #if UNITY_IOS + var assetId = "adapty_fallback_ios.json"; + #elif UNITY_ANDROID + var assetId = "adapty_fallback_android.json"; + #else + var assetId = ""; + #endif - Adapty.SetFallbackPaywalls("FALLBACK_PAYWALLS_JSON_STRING", (error) => { + Adapty.SetFallbackPaywalls(assetId, (error) => { // handle the error }); } ``` 完整代码示例请参阅 [在 Unity 中使用备用付费墙](unity-use-fallback-paywalls) 页面。 ## 更新购买功能 \{#update-making-purchase\} 之前,取消的购买和待处理的购买被视为错误,分别返回 `PaymentCancelled` 和 `PendingPurchase` 错误码。 现在引入了新的 `AdaptyPurchaseResultType` 类,用于处理已取消、成功和待处理的购买。请按以下方式更新购买相关代码: ```diff showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { - Adapty.MakePurchase(product, (profile, error) => { - // handle successfull purchase + Adapty.MakePurchase(product, (result, error) => { + switch (result.Type) { + case AdaptyPurchaseResultType.Pending: + // handle pending purchase + break; + case AdaptyPurchaseResultType.UserCancelled: + // handle purchase cancellation + break; + case AdaptyPurchaseResultType.Success: + var profile = result.Profile; + // handle successful purchase + break; + default: + break; } }); } ``` 查看[在移动应用中进行购买](unity-making-purchases)页面中的最终代码示例。 ## 更新付费墙编辑工具事件处理方式 \{#update-handling-of-paywall-builder-events\} 取消和待处理的购买不再被视为错误,所有这些情况现在通过 `PaywallViewDidFinishPurchase` 方法处理。 1. 删除对取消购买事件的处理。 2. 按以下方式更新成功购买事件的处理: ```diff showLineNumbers - public void OnFinishPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Profile profile - ) { } + public void PaywallViewDidFinishPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyPurchaseResult purchasedResult + ) { } ``` 3. 更新操作处理方式: ```diff showLineNumbers - public void OnPerformAction( - AdaptyUI.View view, - AdaptyUI.Action action - ) { + public void PaywallViewDidPerformAction( + AdaptyUIView view, + AdaptyUIUserAction action + ) { switch (action.Type) { - case AdaptyUI.ActionType.Close: + case AdaptyUIUserActionType.Close: view.Dismiss(null); break; - case AdaptyUI.ActionType.OpenUrl: + case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if (urlString != null { Application.OpenURL(urlString); } default: // handle other events break; } } ``` 4. 更新已开始购买的处理方式: ```diff showLineNumbers - public void OnSelectProduct( - AdaptyUI.View view, - Adapty.PaywallProduct product - ) { } + public void PaywallViewDidSelectProduct( + AdaptyUIView view, + string productId + ) { } ``` 5. 更新购买失败的处理方式: ```diff showLineNumbers - public void OnFailPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Error error - ) { } + public void PaywallViewDidFailPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyError error + ) { } ``` 6. 更新成功恢复购买事件的处理方式: 查看 [处理付费墙事件](unity-handling-events) 页面中的完整代码示例。 ## 更新付费墙编辑工具付费墙错误的处理方式 \{#update-handling-of-paywall-builder-paywall-errors\} 错误处理方式也有所变更,请根据以下指引更新你的代码。 1. 更新产品加载错误的处理方式: ```diff showLineNumbers - public void OnFailLoadingProducts( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailLoadingProducts( + AdaptyUIView view, + AdaptyError error + ) { } ``` 2. 更新渲染错误的处理方式: ```diff showLineNumbers - public void OnFailRendering( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailRendering( + AdaptyUIView view, + AdaptyError error + ) { } ``` ## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\} 从 Adapty Unity SDK 3.3.0 开始,我们更新了 `updateAttribution` 方法的公共 API。之前,它接受 `[AnyHashable: Any]` 字典,允许您直接从各种服务传递归因对象。现在,它需要 `[String: any Sendable]`,因此您需要在传递之前转换归因对象。 为确保集成在 Adapty Unity SDK 3.3.0 及更高版本中正常运行,请按以下各节所述更新以下集成的 SDK 配置。 ### Adjust 按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。 ```diff showLineNumbers - using static AdaptySDK.Adapty; using AdaptySDK; Adjust.GetAdid((adid) => { - Adjust.GetAttribution((attribution) => { - Dictionary<String, object> data = new Dictionary<String, object>(); - - data["network"] = attribution.Network; - data["campaign"] = attribution.Campaign; - data["adgroup"] = attribution.Adgroup; - data["creative"] = attribution.Creative; - - String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { - // handle the error - }); + if (adid != null) { + Adapty.SetIntegrationIdentifier( + "adjust_device_id", + adid, + (error) => { + // handle the error + }); } }); Adjust.GetAttribution((attribution) => { Dictionary<String, object> data = new Dictionary<String, object>(); data["network"] = attribution.Network; data["campaign"] = attribution.Campaign; data["adgroup"] = attribution.Adgroup; data["creative"] = attribution.Creative; String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { + Adapty.UpdateAttribution(attributionString, "adjust", (error) => { // handle the error }); }); ``` ### Amplitude 按如下方式更新你的移动应用代码。完整代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。 ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAmplitudeUserId("YOUR_AMPLITUDE_USER_ID"); - builder.SetAmplitudeDeviceId(amplitude.getDeviceId()); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "amplitude_user_id", + "YOUR_AMPLITUDE_USER_ID", + (error) => { + // handle the error + }); + Adapty.SetIntegrationIdentifier( + "amplitude_device_id", + amplitude.getDeviceId(), + (error) => { + // handle the error + }); ``` ### AppMetrica 按照下方示例更新您的移动应用代码。完整代码示例请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。 ```diff showLineNumbers using AdaptySDK; - var deviceId = AppMetrica.GetDeviceId(); - if (deviceId != null { - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - builder.SetAppmetricaDeviceId(deviceId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); - } + var deviceId = AppMetrica.GetDeviceId(); + if (deviceId != null { + Adapty.SetIntegrationIdentifier( + "appmetrica_device_id", + deviceId, + (error) => { + // handle the error + }); + + Adapty.SetIntegrationIdentifier( + "appmetrica_profile_id", + "YOUR_ADAPTY_CUSTOMER_USER_ID", + (error) => { + // handle the error + }); + } ``` ### AppsFlyer 按照以下方式更新你的移动应用代码。完整代码示例请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。 ```diff showLineNumbers using AppsFlyerSDK; using AdaptySDK; // before SDK initialization AppsFlyer.getConversionData(this.name); // in your IAppsFlyerConversionData void onConversionDataSuccess(string conversionData) { // It's important to include the network user ID - string appsFlyerId = AppsFlyer.getAppsFlyerId(); - Adapty.UpdateAttribution(conversionData, AttributionSource.Appsflyer, appsFlyerId, (error) => { + string appsFlyerId = AppsFlyer.getAppsFlyerId(); + + Adapty.SetIntegrationIdentifier( + "appsflyer_id", + appsFlyerId, + (error) => { // handle the error }); + + Adapty.UpdateAttribution( + conversionData, + "appsflyer", + (error) => { + // handle the error + }); } ``` ### Branch 按如下方式更新您的移动应用代码。完整代码示例请参阅 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。 ```diff showLineNumbers using AdaptySDK; - class YourBranchImplementation { - func initializeBranch() { - Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data { - Adapty.updateAttribution(data, source: .branch) - } - } - } - } + Branch.initSession(delegate(Dictionary<string, object> parameters, string error) { + string attributionString = JsonUtility.ToJson(parameters); + + Adapty.UpdateAttribution( + attributionString, + "branch", + (error) => { + // handle the error + }); + }); ``` ### Firebase 和 Google Analytics \{#firebase-and-google-analytics\} 按照以下方式更新移动应用代码。完整代码示例请参阅 [Firebase 和 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。 ```diff showLineNumbers // We suppose FirebaseAnalytics Unity Plugin is already installed using AdaptySDK; Firebase.Analytics .FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle error return; } var firebaseId = task.Result var builder = new Adapty.ProfileParameters.Builder(); - builder.SetFirebaseAppInstanceId(firebaseId); - - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error + Adapty.SetIntegrationIdentifier( + "firebase_app_instance_id", + firebaseId, + (error) => { + // handle the error }); }); ``` ### Mixpanel 按照以下步骤更新您的移动应用代码。完整代码示例请参阅 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。 ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetMixpanelUserId(Mixpanel.DistinctId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### OneSignal 按以下方式更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。 ```diff showLineNumbers using AdaptySDK; - using OneSignalSDK; - var pushUserId = OneSignal.Default.PushSubscriptionState.userId; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetOneSignalPlayerId(pushUserId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### Pushwoosh 按如下所示更新您的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。 ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetPushwooshHWID(Pushwoosh.Instance.HWID); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "pushwoosh_hwid", + Pushwoosh.Instance.HWID, + (error) => { + // handle the error + }); ``` ## 更新 Observer 模式实现 \{#update-observer-mode-implementation\} 更新付费墙与交易的关联方式。之前,您需要使用 `setVariationId` 方法来分配 `variationId`。现在,您可以在使用新的 `reportTransaction` 方法记录交易时直接传入 `variationId`。请参阅[在 Observer 模式下将付费墙与购买交易关联](report-transactions-observer-mode-unity)中的完整代码示例。 ```diff showLineNumbers // every time when calling transaction.finish() - Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { - if(error != null) { - // handle the error - return; - } - - // successful binding - }); + Adapty.ReportTransaction( + "YOUR_TRANSACTION_ID", + "PAYWALL_VARIATION_ID", // optional + (error) => { + // handle the error + }); ``` ## 更新 Unity 插件初始化 \{#update-the-unity-plugin-initialization\} 从 Adapty Unity SDK 3.3.0 开始,在插件初始化期间需要显式调用 `Activate` 方法: ```csharp showLineNumbers Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: migration-to-unity-sdk-v3 --- --- title: "将 Adapty Unity SDK 迁移至 v3.0" description: "迁移至 Adapty Unity SDK v3.0,获得更好的性能与新的变现功能。" --- Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder)支持——这是一款全新的无代码、易上手的付费墙创建工具。凭借极高的灵活性和丰富的设计能力,你的付费墙将变得更加高效、更具盈利潜力。 ## 升级流程 \{#upgrade-process\} Unity 的升级流程与其他平台相同: 1. 升级至 Adapty SDK v3.x 2. 将现有付费墙迁移至新版付费墙编辑工具 有关 Unity 专属的详细迁移说明,请参阅 [Unity SDK 安装指南](sdk-installation-unity),并遵循主迁移指南中概述的通用迁移步骤。 --- # File: unity-migration-guide --- --- title: "SDK 迁移指南" description: "Unity Adapty SDK 的迁移指南。" --- ## 迁移指南 \{#migration-guides\} ### [迁移至 Unity Adapty SDK 3.x 的指南](unity-sdk-migration-guides) 了解如何从旧版本迁移到 Unity Adapty SDK 3.x。 ## 新特性 \{#whats-new\} ### 版本 3.x \{#version-3x\} - 增强的付费墙展示 - 改进的错误处理 - 更好的 C# 支持 - 性能优化 ### 版本 2.x \{#version-2x\} - 新增用户引导功能 - 增强的分析能力 - 改进的购买流程 - 缺陷修复与稳定性提升 ## 重大变更 \{#breaking-changes\} ### 版本 3.x \{#version-3x-breaking\} - 更新了观察者 API - 更改了付费墙展示方法 - 修改了错误处理结构 ### 版本 2.x \{#version-2x-breaking\} - 更新了用户引导 API - 更改了用户画像结构 - 修改了购买流程 ## 迁移检查清单 \{#migration-checklist\} 迁移到新版本时: - [ ] 审查重大变更 - [ ] 更新 API 调用 - [ ] 测试所有功能 - [ ] 更新错误处理 - [ ] 验证分析跟踪 - [ ] 在所有平台上进行测试 --- # End of Documentation _Generated on: 2026-07-24T13:01:53.556Z_ _Successfully processed: 41/41 files_