# UNITY - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: vi Generated on: 2026-07-01T16:30:13.917Z Total files: 41 --- # File: sdk-installation-unity --- --- title: "Cài đặt & cấu hình Unity SDK" description: "Hướng dẫn từng bước cài đặt Adapty SDK trên Unity cho ứng dụng dựa trên gói đăng ký." --- Adapty SDK bao gồm hai module chính để tích hợp liền mạch vào ứng dụng Unity của bạn: - **Core Adapty**: SDK cốt lõi, bắt buộc phải có để Adapty hoạt động đúng trong ứng dụng. - **AdaptyUI**: Module này cần thiết nếu bạn sử dụng [Adapty Paywall Builder](adapty-paywall-builder) — công cụ no-code thân thiện giúp tạo paywall đa nền tảng dễ dàng. :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) của chúng tôi, minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ## Yêu cầu \{#requirements\} Adapty SDK hỗ trợ iOS 13.0 trở lên, nhưng yêu cầu iOS 15.0 trở lên để hoạt động với các paywall được tạo trong Paywall Builder. :::info Adapty tương thích với Google Play Billing Library lên đến phiên bản 8.x. Mặc định, Adapty sử dụng Google Play Billing Library v7.0.0. Để dùng phiên bản mới hơn, hãy [ghi đè dependency Billing](https://developer.android.com/google/play/billing/integrate#dependency) trong bản build Android của bạn. ::: --- no_index: true --- import Callout from '../../../components/Callout.astro'; Cài đặt SDK là bước 5 trong quá trình thiết lập Adapty. Trước khi các giao dịch mua hàng hoạt động trong ứng dụng, bạn cần kết nối ứng dụng với các cửa hàng, sau đó tạo sản phẩm, paywall và placement trong Adapty Dashboard. [Hướng dẫn quickstart](quickstart) sẽ hướng dẫn bạn qua tất cả các bước cần thiết. ## Cài đặt 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) 1. Tải file [`adapty-unity-plugin-*.unitypackage`](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Releases) từ GitHub và import vào project của bạn. 2. Tải và import [plugin External Dependency Manager](https://github.com/googlesamples/unity-jar-resolver). 3. SDK sử dụng plugin "External Dependency Manager" để quản lý các dependency iOS Cocoapods và Android gradle. Sau khi cài đặt, bạn có thể cần gọi dependency manager: `Assets -> External Dependency Manager -> Android Resolver -> Force Resolve` và `Assets -> External Dependency Manager -> iOS Resolver -> Install Cocoapods` 4. Khi build project Unity cho iOS, bạn sẽ nhận được file `Unity-iPhone.xcworkspace`. Bạn phải mở file này thay vì `Unity-iPhone.xcodeproj`, nếu không các dependency của Cocoapods sẽ không được sử dụng. ## Kích hoạt module Adapty của Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} Kích hoạt Adapty SDK trong code ứng dụng của bạn. :::note Adapty SDK chỉ cần được kích hoạt một lần trong ứng dụng. ::: Để lấy **Public SDK Key**: 1. Truy cập Adapty Dashboard và điều hướng đến [**App settings → General**](https://app.adapty.io/settings/general). 2. Trong phần **Api keys**, sao chép **Public SDK Key** (KHÔNG phải Secret Key). 3. Thay thế `"YOUR_PUBLIC_SDK_KEY"` trong code. Hoặc lấy theo cách lập trình, sử dụng [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Hoặc, trực tiếp: ``` npx adapty auth login adapty apps list ``` - Đảm bảo bạn sử dụng **Public SDK key** để khởi tạo Adapty, **Secret key** chỉ nên dùng cho [server-side API](getting-started-with-server-side-api). - **SDK keys** là duy nhất cho mỗi ứng dụng, vì vậy nếu bạn có nhiều ứng dụng, hãy đảm bảo chọn đúng key. ```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 Hãy chờ callback hoàn thành của `Activate` trước khi gọi bất kỳ phương thức nào khác của Adapty SDK. Xem [Thứ tự gọi trong Unity SDK](unity-sdk-call-order) để biết trình tự đầy đủ. ::: ## Thiết lập lắng nghe sự kiện \{#set-up-event-listening\} Tạo một script để lắng nghe các sự kiện Adapty. Đặt tên là `AdaptyListener` trong scene của bạn. Chúng tôi khuyên dùng phương thức `DontDestroyOnLoad` cho object này để đảm bảo nó tồn tại trong suốt vòng đời của ứng dụng. Adapty sử dụng namespace `AdaptySDK`. Ở đầu các file script sử dụng Adapty SDK, bạn có thể thêm: ```csharp showLineNumbers title="C#" using AdaptySDK; ``` Đăng ký nhận sự kiện 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) { } } ``` Chúng tôi khuyên bạn nên điều chỉnh Script Execution Order để đặt AdaptyListener trước Default Time. Điều này đảm bảo Adapty được khởi tạo sớm nhất có thể. ## Thêm Kotlin Plugin vào project của bạn \{#add-kotlin-plugin-to-your-project\} :::warning Bước này là bắt buộc. Nếu bỏ qua, ứng dụng di động của bạn có thể bị crash khi hiển thị paywall. ::: 1. Trong **Player Settings**, đảm bảo rằng các tùy chọn **Custom Launcher Gradle Template** và **Custom Base Gradle Template** đã được chọn. 2. Thêm dòng sau vào `/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. Thêm dòng sau vào `/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** } ``` Bây giờ hãy thiết lập paywall trong ứng dụng của bạn: - Nếu bạn dùng [Adapty Paywall Builder](adapty-paywall-builder), trước tiên hãy [kích hoạt module AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) bên dưới, sau đó làm theo [hướng dẫn nhanh Paywall Builder](unity-quickstart-paywalls). - Nếu bạn tự xây dựng giao diện paywall, xem [hướng dẫn nhanh cho paywall tùy chỉnh](unity-quickstart-manual). ## Kích hoạt module AdaptyUI của Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Nếu bạn có kế hoạch sử dụng [Paywall Builder](adapty-paywall-builder) và đã cài đặt module AdaptyUI, bạn cần kích hoạt AdaptyUI. Bạn có thể kích hoạt nó trong quá trình cấu hình: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetActivateUI(true); ``` ## Cài đặt tùy chọn \{#optional-setup\} ### Ghi log \{#logging\} #### Thiết lập hệ thống ghi log \{#set-up-the-logging-system\} Adapty ghi log các lỗi và thông tin quan trọng khác để giúp bạn hiểu những gì đang xảy ra. Có các mức độ sau: | Mức độ | Mô tả | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `error` | Chỉ ghi log các lỗi | | `warn` | Ghi log các lỗi và các thông điệp từ SDK không gây ra lỗi nghiêm trọng nhưng đáng chú ý | | `info` | Ghi log các lỗi, cảnh báo và các thông điệp thông tin | | `verbose` | Ghi log mọi thông tin bổ sung có thể hữu ích trong quá trình debug, chẳng hạn như các lời gọi hàm, truy vấn API, v.v. | Bạn có thể đặt mức log trong ứng dụng khi cấu hình 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; ``` Bạn cũng có thể thay đổi mức log tại runtime: ```csharp showLineNumbers title="C#" Adapty.SetLogLevel(AdaptyLogLevel.Verbose, (error) => { // handle result }); ``` ### Chính sách dữ liệu \{#data-policies\} Adapty không lưu trữ dữ liệu cá nhân của người dùng trừ khi bạn gửi rõ ràng, nhưng bạn có thể triển khai các chính sách bảo mật dữ liệu bổ sung để tuân thủ quy định của cửa hàng hoặc quốc gia. #### Tắt thu thập và chia sẻ địa chỉ IP \{#disable-ip-address-collection-and-sharing\} Khi kích hoạt module Adapty, đặt `SetIPAddressCollectionDisabled` thành `true` để tắt việc thu thập và chia sẻ địa chỉ IP của người dùng. Giá trị mặc định là `false`. Dùng tham số này để tăng cường quyền riêng tư của người dùng, tuân thủ các quy định bảo vệ dữ liệu theo khu vực (như GDPR hoặc CCPA), hoặc giảm việc thu thập dữ liệu không cần thiết khi các tính năng dựa trên IP không cần thiết cho ứng dụng của bạn. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetIPAddressCollectionDisabled(true); ``` #### Tắt thu thập và chia sẻ advertising ID \{#disable-advertising-id-collection-and-sharing\} Khi kích hoạt module Adapty, đặt `SetAppleIDFACollectionDisabled` và/hoặc `SetGoogleAdvertisingIdCollectionDisabled` thành `true` để tắt thu thập các định danh quảng cáo. Giá trị mặc định là `false`. Dùng tham số này để tuân thủ chính sách App Store/Google Play, tránh kích hoạt lời nhắc App Tracking Transparency, hoặc nếu ứng dụng của bạn không yêu cầu attribution quảng cáo hoặc analytics dựa trên advertising ID. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleIDFACollectionDisabled(true) .SetGoogleAdvertisingIdCollectionDisabled(true); ``` #### Thiết lập cấu hình cache media cho AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Mặc định, AdaptyUI lưu cache media (chẳng hạn hình ảnh và video) để cải thiện hiệu suất và giảm sử dụng mạng. Bạn có thể tùy chỉnh cài đặt cache bằng cách cung cấp cấu hình tùy chỉnh. Dùng `SetAdaptyUIMediaCache` để ghi đè cài đặt cache mặc định: ```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 ); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |-----------------------------|----------|-----------------------------------------------------------------------------------------------| | memoryStorageTotalCostLimit | tùy chọn | Tổng kích thước cache trong bộ nhớ tính bằng byte. Mặc định theo giá trị của từng nền tảng. | | memoryStorageCountLimit | tùy chọn | Giới hạn số lượng item trong bộ nhớ cache. Mặc định theo giá trị của từng nền tảng. | | diskStorageSizeLimit | tùy chọn | Giới hạn kích thước file trên đĩa tính bằng byte. Mặc định theo giá trị của từng nền tảng. | ### Bật mức độ truy cập cục bộ (Android) \{#enable-local-access-levels-android\} Mặc định, [mức độ truy cập cục bộ](local-access-levels) được bật trên iOS và tắt trên Android. Để bật trên Android, đặt `SetGoogleLocalAccessLevelAllowed` thành `true`: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetGoogleLocalAccessLevelAllowed(true); ``` ### Xóa dữ liệu khi khôi phục từ backup \{#clear-data-on-backup-restore\} Khi `SetAppleClearDataOnBackup` được đặt thành `true`, SDK sẽ phát hiện khi ứng dụng được khôi phục từ backup iCloud và xóa tất cả dữ liệu SDK được lưu cục bộ, bao gồm thông tin hồ sơ người dùng đã cache, chi tiết sản phẩm và paywall. Sau đó SDK sẽ khởi tạo lại với trạng thái sạch. Giá trị mặc định là `false`. :::note Chỉ cache SDK cục bộ bị xóa. Lịch sử giao dịch với Apple và dữ liệu người dùng trên server Adapty vẫn không thay đổi. ::: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleClearDataOnBackup(true); ``` ## Khắc phục sự cố \{#troubleshooting\} #### Quy tắc backup Android (cấu hình Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Một số SDK (bao gồm Adapty) đi kèm với cấu hình Android Auto Backup riêng. Nếu bạn sử dụng nhiều SDK có định nghĩa backup rules, quá trình merge Android manifest có thể thất bại với lỗi liên quan đến `android:fullBackupContent`, `android:dataExtractionRules`, hoặc `android:allowBackup`. Triệu chứng lỗi thường gặp: `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 Những thay đổi này cần được thực hiện trong thư mục platform Android của bạn (thường nằm trong thư mục `android/` của dự án). ::: Để khắc phục, bạn cần: - Yêu cầu manifest merger sử dụng các giá trị của ứng dụng cho các thuộc tính liên quan đến backup. - Tạo các file backup rule kết hợp rules của Adapty với rules từ các SDK khác. #### 1. Thêm namespace `tools` vào manifest \{#1-add-the-tools-namespace-to-your-manifest\} Trong file `AndroidManifest.xml`, hãy đảm bảo thẻ gốc `` có chứa tools: ```xml ... ``` #### 2. Ghi đè các thuộc tính backup trong `` \{#2-override-backup-attributes-in-application\} Trong cùng file `AndroidManifest.xml`, cập nhật thẻ `` để ứng dụng của bạn cung cấp các giá trị cuối cùng và yêu cầu manifest merger thay thế các giá trị từ thư viện: ```xml ... ``` Nếu có SDK nào cũng đặt `android:allowBackup`, hãy thêm nó vào `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Tạo các file backup rules đã merge \{#3-create-merged-backup-rules-files\} Tạo các file XML trong thư mục `res/xml/` của dự án Android, kết hợp rules của Adapty với rules từ các SDK khác. Android sử dụng các định dạng backup rule khác nhau tùy theo phiên bản OS, vì vậy việc tạo cả hai file đảm bảo tương thích với tất cả các phiên bản Android mà ứng dụng hỗ trợ. :::note Các ví dụ dưới đây sử dụng AppsFlyer làm SDK bên thứ ba mẫu. Hãy thay thế hoặc bổ sung rules cho các SDK khác mà bạn đang dùng trong ứng dụng. ::: **Dành cho Android 12 trở lên** (sử dụng định dạng data extraction rules mới): ```xml title="sample_data_extraction_rules.xml" ``` **Dành cho Android 11 trở xuống** (sử dụng định dạng full backup content cũ): ```xml title="sample_backup_rules.xml" :::important Trong Unity, hãy áp dụng các thay đổi này vào `Assets/Plugins/Android/AndroidManifest.xml` và tạo các file quy tắc backup trong `Assets/Plugins/Android/res/xml/`. ::: #### Mua hàng thất bại sau khi quay lại từ ứng dụng khác trên Android \{#purchases-fail-after-returning-from-another-app-in-android\} Nếu Activity khởi động flow mua hàng sử dụng `launchMode` không phải mặc định, Android có thể tái tạo hoặc tái sử dụng nó không chính xác khi người dùng quay lại từ Google Play, ứng dụng ngân hàng hoặc trình duyệt. Điều này có thể khiến kết quả mua hàng bị mất hoặc bị xử lý là đã hủy. Để đảm bảo mua hàng hoạt động đúng, chỉ sử dụng chế độ `standard` hoặc `singleTop` cho Activity khởi động flow mua hàng, và tránh các chế độ khác. Trong `AndroidManifest.xml` của bạn, hãy đảm bảo Activity khởi động flow mua hàng được đặt thành `standard` hoặc `singleTop`: ```xml ``` --- # File: unity-quickstart-paywalls --- --- title: "Bật tính năng mua hàng bằng cách sử dụng paywall trong Unity SDK" description: "Tìm hiểu cách hiển thị paywall trong ứng dụng Unity của bạn với Adapty SDK." --- Để bật tính năng in-app purchase, bạn cần hiểu ba khái niệm chính: - [**Sản phẩm**](product) – bất kỳ thứ gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời) - [**Paywall**](paywalls) là các cấu hình xác định sản phẩm nào sẽ được cung cấp. Trong Adapty, paywall là cách duy nhất để lấy sản phẩm, nhưng thiết kế này cho phép bạn thay đổi ưu đãi, giá cả và tổ hợp sản phẩm mà không cần chỉnh sửa code ứng dụng. - [**Placement**](placements) – vị trí và thời điểm hiển thị paywall trong ứng dụng (như `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho các placement trong dashboard, sau đó yêu cầu chúng bằng placement ID trong code. Điều này giúp dễ dàng chạy A/B test và hiển thị các paywall khác nhau cho từng nhóm người dùng. Adapty cung cấp cho bạn ba cách để bật tính năng mua hàng trong ứng dụng. Hãy chọn một trong số đó tùy theo yêu cầu của ứng dụng: | Cách triển khai | Độ phức tạp | Khi nào nên dùng | |---------------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Paywall Builder | ✅ Dễ | Bạn [tạo một paywall hoàn chỉnh, sẵn sàng để mua trong no-code builder](quickstart-paywalls). Adapty tự động render và xử lý toàn bộ flow mua hàng phức tạp, xác thực receipt và quản lý gói đăng ký ở phía sau. | | Paywall tạo thủ công | 🟡 Trung bình | Bạn tự triển khai giao diện paywall trong code ứng dụng, nhưng vẫn lấy đối tượng paywall từ Adapty để duy trì tính linh hoạt trong ưu đãi sản phẩm. Xem [hướng dẫn](unity-quickstart-manual). | | Observer mode | 🔴 Khó | Bạn đã có cơ sở hạ tầng xử lý mua hàng riêng và muốn tiếp tục sử dụng. Lưu ý rằng observer mode có những giới hạn nhất định trong Adapty. Xem [bài viết](observer-vs-full-mode). | :::important **Các bước dưới đây hướng dẫn cách triển khai paywall được tạo trong Adapty paywall builder.** Nếu bạn không muốn dùng paywall builder, hãy xem [hướng dẫn xử lý mua hàng trong paywall tạo thủ công](unity-making-purchases). ::: Để hiển thị paywall được tạo trong Adapty paywall builder, trong code ứng dụng, bạn chỉ cần: 1. **Lấy paywall**: Lấy paywall từ Adapty. 2. **Hiển thị paywall và Adapty sẽ xử lý mua hàng cho bạn**: Hiển thị paywall container bạn đã lấy được trong ứng dụng. 3. **Xử lý các hành động nút bấm**: Liên kết tương tác của người dùng với paywall với phản hồi tương ứng trong ứng dụng. Ví dụ: mở liên kết hoặc đóng paywall khi người dùng nhấn nút. ## Trước khi bắt đầu \{#before-you-start\} Trước khi bắt đầu, hãy hoàn thành các bước sau: 1. Kết nối ứng dụng của bạn với [App Store](initial_ios) và/hoặc [Google Play](initial-android) trong Adapty Dashboard. 2. [Tạo sản phẩm](create-product) trong Adapty. 3. [Tạo paywall và thêm sản phẩm vào đó](create-paywall). 4. [Tạo placement và thêm paywall vào đó](create-placement). 5. [Cài đặt và kích hoạt Adapty SDK](sdk-installation-unity) trong code ứng dụng. :::tip Cách nhanh nhất để hoàn thành các bước này là làm theo [hướng dẫn quickstart](quickstart) hoặc tạo paywall và placement bằng [Developer CLI](developer-cli-quickstart). ::: ## 1. Lấy paywall \{#1-get-the-paywall\} Các paywall của bạn được liên kết với các placement đã cấu hình trong dashboard. Placement cho phép bạn chạy các paywall khác nhau cho các đối tượng khác nhau hoặc chạy [A/B test](ab-tests). Để lấy paywall được tạo trong Adapty paywall builder, bạn cần: 1. Lấy đối tượng `paywall` theo [placement](placements) ID bằng phương thức `GetPaywall` và kiểm tra xem đó có phải là paywall được tạo trong builder hay không thông qua thuộc tính `HasViewConfiguration`. 2. Tạo paywall view bằng phương thức `CreatePaywallView`. View chứa các phần tử giao diện và styling cần thiết để hiển thị paywall. :::important Để lấy cấu hình view, bạn phải bật toggle **Show on device** trong Paywall Builder. Nếu không, bạn sẽ nhận được cấu hình view rỗng và paywall sẽ không được hiển thị. ::: ```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 Hướng dẫn quickstart này cung cấp cấu hình tối thiểu cần thiết để hiển thị paywall. Để biết chi tiết cấu hình nâng cao, xem [hướng dẫn lấy paywall](unity-get-pb-paywalls). ::: ## 2. Hiển thị paywall \{#2-display-the-paywall\} Khi đã có cấu hình paywall, bạn chỉ cần thêm vài dòng code để hiển thị paywall của mình. Để hiển thị paywall, sử dụng phương thức `view.Present()` trên `view` được tạo bởi phương thức `CreatePaywallView`. Mỗi `view` chỉ có thể dùng một lần. Nếu cần hiển thị lại paywall, hãy gọi `CreatePaywallView` thêm một lần nữa để tạo instance `view` mới. ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::info Để biết thêm chi tiết về cách hiển thị paywall, xem [hướng dẫn](unity-present-paywalls). ::: ## 3. Xử lý các hành động nút bấm \{#3-handle-button-actions\} Khi người dùng nhấn các nút trong paywall, Unity SDK sẽ tự động xử lý việc mua hàng và khôi phục. Tuy nhiên, các nút khác có ID tùy chỉnh hoặc được định nghĩa sẵn và yêu cầu bạn xử lý hành động trong code. Ví dụ: paywall của bạn thường có nút đóng và các URL để mở (như điều khoản sử dụng và chính sách bảo mật). Để xử lý các hành động này, class của bạn cần implement interface `AdaptyPaywallsEventsListener` và đăng ký làm listener. :::tip Đọc hướng dẫn về cách xử lý [hành động](unity-handle-paywall-actions) và [sự kiện](unity-handling-events) của nút bấm. ::: ```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; } } } ``` ## Các bước tiếp theo \{#next-steps\} --- no_index: true --- import Callout from '../../../components/Callout.astro'; Bạn có câu hỏi hoặc gặp sự cố? Hãy xem [diễn đàn hỗ trợ](https://adapty.featurebase.app/) của chúng tôi — nơi bạn có thể tìm câu trả lời cho các câu hỏi thường gặp hoặc đặt câu hỏi của riêng mình. Đội ngũ và cộng đồng của chúng tôi luôn sẵn sàng giúp đỡ! Paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. Hãy kiểm tra giao dịch mua hàng của bạn trong [App Store sandbox](test-purchases-in-sandbox) hoặc trong [Google Play Store](testing-on-android) để đảm bảo bạn có thể hoàn thành một giao dịch mua thử từ paywall. Tiếp theo, bạn cần [kiểm tra mức độ truy cập của người dùng](unity-check-subscription-status) để đảm bảo hiển thị paywall hoặc cấp quyền truy cập tính năng trả phí cho đúng người dùng. ## Ví dụ đầy đủ \{#full-example\} Dưới đây là cách tích hợp tất cả các bước đó vào ứng dụng của bạn. ```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: "Kiểm tra trạng thái đăng ký trong Unity SDK" description: "Tìm hiểu cách kiểm tra trạng thái đăng ký trong ứng dụng Unity của bạn với Adapty." --- Để quyết định xem người dùng có thể truy cập nội dung trả phí hay cần xem paywall, bạn cần kiểm tra [mức độ truy cập](access-level) của họ trong hồ sơ người dùng. Bài viết này hướng dẫn bạn cách truy cập trạng thái hồ sơ người dùng để quyết định nội dung hiển thị — cho xem paywall hay mở quyền truy cập các tính năng trả phí. ## Lấy trạng thái đăng ký \{#get-subscription-status\} Khi quyết định có nên hiển thị paywall hay nội dung trả phí cho người dùng, bạn kiểm tra [mức độ truy cập](access-level) trong hồ sơ người dùng của họ. Bạn có hai lựa chọn: - Gọi `GetProfile` nếu bạn cần dữ liệu hồ sơ mới nhất ngay lập tức (ví dụ: khi khởi động ứng dụng) hoặc muốn buộc cập nhật. - Thiết lập **cập nhật hồ sơ tự động** để giữ một bản sao cục bộ được tự động làm mới mỗi khi trạng thái đăng ký thay đổi. ### Lấy hồ sơ người dùng \{#get-profile\} Cách đơn giản nhất để lấy trạng thái đăng ký là dùng phương thức `GetProfile` để truy cập hồ sơ: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` ### Lắng nghe cập nhật đăng ký \{#listen-to-subscription-updates\} Để tự động nhận cập nhật hồ sơ trong ứng dụng: 1. Kế thừa `AdaptyEventListener` và implement phương thức `OnLoadLatestProfile` — Adapty sẽ tự động gọi phương thức này mỗi khi trạng thái đăng ký của người dùng thay đổi. 2. Lưu dữ liệu hồ sơ được cập nhật khi phương thức này được gọi, để bạn có thể dùng nó trong toàn bộ ứng dụng mà không cần thực hiện thêm các yêu cầu mạng. ```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 tự động gọi `OnLoadLatestProfile` khi ứng dụng khởi động, cung cấp dữ liệu đăng ký đã được cache ngay cả khi thiết bị ngoại tuyến. ::: ## Kết nối hồ sơ với logic paywall \{#connect-profile-with-paywall-logic\} Khi bạn cần đưa ra quyết định ngay lập tức về việc hiển thị paywall hay cấp quyền truy cập tính năng trả phí, bạn có thể kiểm tra trực tiếp hồ sơ người dùng. Cách tiếp cận này hữu ích cho các tình huống như khởi động ứng dụng, khi vào các mục premium, hoặc trước khi hiển thị nội dung cụ thể. ```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(); } ``` ## Bước tiếp theo \{#next-steps\} Sau khi đã biết cách theo dõi trạng thái đăng ký, hãy tìm hiểu cách [làm việc với hồ sơ người dùng](unity-quickstart-identify) để đảm bảo người dùng có thể truy cập những gì họ đã trả phí. --- # File: unity-quickstart-identify --- --- title: "Xác định người dùng trong Unity SDK" description: "Hướng dẫn nhanh để thiết lập Adapty cho việc quản lý gói đăng ký in-app trong Unity." --- :::important Hướng dẫn này dành cho bạn nếu bạn có hệ thống xác thực riêng. Tại đây, bạn sẽ học cách làm việc với hồ sơ người dùng trong Adapty để đảm bảo nó phù hợp với hệ thống xác thực hiện có của bạn. ::: Cách bạn quản lý giao dịch mua của người dùng phụ thuộc vào mô hình xác thực của ứng dụng: - Nếu ứng dụng của bạn không sử dụng xác thực backend và không lưu trữ dữ liệu người dùng, hãy xem [phần về người dùng ẩn danh](#anonymous-users). - Nếu ứng dụng của bạn có (hoặc sẽ có) xác thực backend, hãy xem [phần về người dùng đã xác định](#identified-users). **Các khái niệm chính**: - **Hồ sơ người dùng** là các thực thể cần thiết để SDK hoạt động. Adapty tự động tạo chúng. - Chúng có thể là ẩn danh **(không có customer user ID)** hoặc đã xác định **(có customer user ID)**. - Bạn cung cấp **customer user ID** để đối chiếu hồ sơ người dùng trong Adapty với hệ thống xác thực nội bộ của bạn. Đây là những điểm khác biệt giữa người dùng ẩn danh và người dùng đã xác định: | | Người dùng ẩn danh | Người dùng đã xác định | |-------------------------|------------------------------------------------------|----------------------------------------------------------------------------------------| | **Quản lý giao dịch mua** | Khôi phục giao dịch mua ở cấp cửa hàng | Duy trì lịch sử giao dịch mua trên nhiều thiết bị thông qua customer user ID của họ | | **Quản lý hồ sơ người dùng** | Hồ sơ người dùng mới mỗi lần cài đặt lại | Cùng một hồ sơ người dùng xuyên suốt các phiên và thiết bị | | **Lưu trữ dữ liệu** | Dữ liệu người dùng ẩn danh gắn với lần cài đặt ứng dụng | Dữ liệu người dùng đã xác định được lưu trữ qua các lần cài đặt ứng dụng | ## Người dùng ẩn danh \{#anonymous-users\} Nếu bạn không có xác thực backend, **bạn không cần xử lý xác thực trong code ứng dụng**: 1. Khi SDK được kích hoạt lần đầu tiên khi ứng dụng khởi chạy, Adapty **tạo một hồ sơ người dùng mới cho người dùng**. 2. Khi người dùng mua bất cứ thứ gì trong ứng dụng, giao dịch mua này được **liên kết với hồ sơ người dùng Adapty của họ và tài khoản cửa hàng của họ**. 3. Khi người dùng **cài đặt lại** ứng dụng hoặc cài đặt trên **thiết bị mới**, Adapty **tạo một hồ sơ người dùng ẩn danh mới khi kích hoạt**. 4. Nếu người dùng đã thực hiện giao dịch mua trước đó trong ứng dụng của bạn, theo mặc định, các giao dịch mua của họ sẽ được tự động đồng bộ từ App Store khi SDK được kích hoạt. Vậy, với người dùng ẩn danh, hồ sơ người dùng mới sẽ được tạo mỗi lần cài đặt, nhưng điều đó không phải là vấn đề vì trong phân tích Adapty, bạn có thể [cấu hình những gì sẽ được coi là lần cài đặt mới](general#4-installs-definition-for-analytics). Đối với người dùng ẩn danh, bạn cần đếm lượt cài đặt theo **ID thiết bị**. Trong trường hợp này, mỗi lần cài đặt ứng dụng trên một thiết bị được tính là một lượt cài đặt, bao gồm cả cài đặt lại. ## Người dùng đã xác định \{#identified-users\} Bạn có hai lựa chọn để xác định người dùng trong ứng dụng: - [**Trong quá trình đăng nhập/đăng ký:**](#during-loginsignup) Nếu người dùng đăng nhập sau khi ứng dụng khởi động, hãy gọi `identify()` với customer user ID khi họ xác thực. - [**Trong quá trình kích hoạt SDK:**](#during-the-sdk-activation) Nếu bạn đã có customer user ID được lưu trữ khi ứng dụng khởi chạy, hãy gửi nó khi gọi `activate()`. :::important Theo mặc định, khi Adapty nhận được giao dịch mua từ một Customer User ID hiện đang liên kết với Customer User ID khác, mức độ truy cập được chia sẻ, vì vậy cả hai hồ sơ người dùng đều có quyền truy cập trả phí. Bạn có thể cấu hình cài đặt này để chuyển quyền truy cập trả phí từ hồ sơ người dùng này sang hồ sơ người dùng khác hoặc tắt hoàn toàn việc chia sẻ. Xem [bài viết](general#6-sharing-paid-access-between-user-accounts) để biết thêm chi tiết. ::: ### Trong quá trình đăng nhập/đăng ký \{#during-loginsignup\} Nếu bạn xác định người dùng sau khi ứng dụng khởi chạy (ví dụ: sau khi họ đăng nhập vào ứng dụng hoặc đăng ký), hãy sử dụng phương thức `identify` để đặt customer user ID của họ. - Nếu bạn **chưa từng sử dụng customer user ID này trước đây**, Adapty sẽ tự động liên kết nó với hồ sơ người dùng hiện tại. - Nếu bạn **đã sử dụng customer user ID này để xác định người dùng trước đây**, Adapty sẽ chuyển sang làm việc với hồ sơ người dùng được liên kết với customer user ID này. :::important Customer user ID phải là duy nhất cho mỗi người dùng. Nếu bạn hardcode giá trị tham số, tất cả người dùng sẽ được coi là một. ::: Hãy chờ callback hoàn thành của `Identify` trước khi gọi các phương thức SDK khác. Các lệnh gọi đồng thời sẽ tạo ra lỗi `#3006 profileWasChanged` hoặc rơi vào hồ sơ người dùng ẩn danh. Xem [Thứ tự gọi trong Unity SDK](unity-sdk-call-order). ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` ### Trong quá trình kích hoạt SDK \{#during-the-sdk-activation\} Nếu bạn đã biết customer user ID khi kích hoạt SDK, bạn có thể gửi nó trong phương thức `activate` thay vì gọi `identify` riêng biệt. Nếu bạn biết customer user ID nhưng chỉ đặt nó sau khi kích hoạt, điều đó có nghĩa là khi kích hoạt, Adapty sẽ tạo một hồ sơ người dùng ẩn danh mới và chỉ chuyển sang hồ sơ người dùng hiện có sau khi bạn gọi `identify`. Bạn có thể truyền vào customer user ID hiện có (đã từng sử dụng trước đây) hoặc một ID mới. Nếu bạn truyền một ID mới, hồ sơ người dùng mới được tạo khi kích hoạt sẽ được tự động liên kết với customer user ID đó. :::note Theo mặc định, việc tạo hồ sơ người dùng ẩn danh không ảnh hưởng đến các dashboard phân tích, vì lượt cài đặt được tính dựa trên ID thiết bị. ID thiết bị đại diện cho một lần cài đặt ứng dụng từ cửa hàng trên một thiết bị và chỉ được tạo lại sau khi ứng dụng được cài đặt lại. Nó không phụ thuộc vào việc đây là lần cài đặt đầu tiên hay lần cài đặt lại, hoặc liệu có sử dụng customer user ID hiện có hay không. Việc tạo hồ sơ người dùng (khi kích hoạt SDK hoặc đăng xuất), đăng nhập, hoặc nâng cấp ứng dụng mà không cài đặt lại không tạo thêm sự kiện cài đặt. Nếu bạn muốn đếm lượt cài đặt dựa trên người dùng duy nhất thay vì thiết bị, hãy vào **App settings** và cấu hình [**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 IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` ### Đăng xuất người dùng \{#log-users-out\} Nếu bạn có nút cho phép người dùng đăng xuất, hãy sử dụng phương thức `logout`. :::important Việc đăng xuất người dùng sẽ tạo một hồ sơ người dùng ẩn danh mới cho người dùng đó. ::: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` :::info Để đăng nhập lại người dùng vào ứng dụng, hãy sử dụng phương thức `identify`. ::: ### Cho phép mua hàng mà không cần đăng nhập \{#allow-purchases-without-login\} Nếu người dùng của bạn có thể thực hiện giao dịch mua cả trước và sau khi đăng nhập vào ứng dụng, bạn cần đảm bảo rằng họ sẽ giữ được quyền truy cập sau khi đăng nhập: 1. Khi người dùng chưa đăng nhập thực hiện giao dịch mua, Adapty gắn nó với ID hồ sơ người dùng ẩn danh của họ. 2. Khi người dùng đăng nhập vào tài khoản, Adapty chuyển sang làm việc với hồ sơ người dùng đã xác định của họ. - Nếu đây là customer user ID mới (ví dụ: giao dịch mua được thực hiện trước khi đăng ký), Adapty gán customer user ID cho hồ sơ người dùng hiện tại, do đó toàn bộ lịch sử giao dịch mua được duy trì. - Nếu đây là customer user ID hiện có (customer user ID đã được liên kết với một hồ sơ người dùng), bạn cần lấy mức độ truy cập thực tế sau khi chuyển đổi hồ sơ người dùng. Bạn có thể gọi [`getProfile`](unity-check-subscription-status) ngay sau khi xác định, hoặc [lắng nghe các cập nhật hồ sơ người dùng](unity-check-subscription-status) để dữ liệu tự động đồng bộ. ## Các bước tiếp theo \{#next-steps\} Xin chúc mừng! Bạn đã triển khai logic thanh toán in-app trong ứng dụng của mình! Chúc bạn thành công với việc kiếm tiền từ ứng dụng! Để khai thác Adapty tối đa hơn, bạn có thể khám phá các chủ đề sau: - [**Kiểm thử**](troubleshooting-test-purchases): Đảm bảo mọi thứ hoạt động như mong đợi - [**Onboardings**](onboardings): Thu hút người dùng với onboarding và thúc đẩy sự gắn kết - [**Tích hợp**](configuration): Tích hợp với các dịch vụ attribution marketing và phân tích chỉ trong một dòng code - [**Đặt thuộc tính hồ sơ người dùng tùy chỉnh**](unity-setting-user-attributes): Thêm thuộc tính tùy chỉnh vào hồ sơ người dùng và tạo phân khúc, để bạn có thể chạy A/B test hoặc hiển thị các paywall khác nhau cho các người dùng khác nhau --- # File: adapty-sdk-integration-skill-unity --- --- title: "Tích hợp Adapty vào ứng dụng Unity của bạn với kỹ năng tích hợp SDK" description: "Sử dụng skill adapty-sdk-integration để tích hợp Adapty SDK vào ứng dụng Unity của bạn từ đầu đến cuối với công cụ lập trình AI của bạn." --- :::important Skill này đang trong giai đoạn beta. Nếu nó bị treo hoặc hoạt động không như mong đợi, hãy làm theo [hướng dẫn tích hợp từng bước](adapty-cursor-unity) — hướng dẫn này sẽ dẫn dắt công cụ AI của bạn qua từng giai đoạn với tài liệu phù hợp. ::: --- no_index: true --- [Skill adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) tự động hóa toàn bộ quá trình tích hợp Adapty: thiết lập dashboard, cài đặt SDK, paywall và xác minh từng giai đoạn. Skill tự động nhận diện nền tảng của bạn và tải tài liệu Adapty phù hợp ở mỗi giai đoạn. **Công cụ được hỗ trợ**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Để cài đặt, chọn lệnh phù hợp với công cụ của bạn. Danh sách đầy đủ có trong [README của skill](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 hoặc bất kỳ công cụ nào khác**: Clone repo và sao chép thư mục `plugins/adapty-sdk-integration/skills/adapty-sdk-integration/` vào thư mục skills của công cụ bạn đang dùng. Sau khi cài đặt, chạy skill trong dự án của bạn: ``` /adapty-sdk-integration ``` Skill sẽ hỏi một vài câu hỏi thiết lập, sau đó hướng dẫn bạn qua các bước: thiết lập dashboard, cài đặt SDK, paywall và xác minh. --- # File: adapty-cursor-unity --- --- title: "Tích hợp Adapty vào ứng dụng Unity của bạn với sự hỗ trợ của AI" description: "Hướng dẫn từng bước tích hợp Adapty vào ứng dụng Unity của bạn sử dụng Cursor, Context7, ChatGPT, Claude, hoặc các công cụ AI khác." --- Hướng dẫn này sẽ đưa bạn qua từng bước tích hợp Adapty vào ứng dụng Unity với một công cụ AI — bạn chỉ cần cung cấp đúng tài liệu Adapty theo đúng thứ tự. 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. ## Trước khi bắt đầu: thiết lập dashboard \{#before-you-start-dashboard-setup\} Adapty yêu cầu một số cấu hình trên dashboard trước khi bạn viết bất kỳ đoạn code SDK nào. Bạn có thể thực hiện điều này với một LLM skill tương tác, hoặc thủ công thông qua Dashboard. ### Cách dùng Skill (khuyến nghị) \{#skill-approach-recommended\} Adapty CLI skill cho phép LLM của bạn thiết lập ứng dụng, sản phẩm, mức độ truy cập, paywall và placement trực tiếp — mà không cần mở Dashboard cho từng bước. Bạn chỉ cần [kết nối cửa hàng của mình](integrate-payments) trong Dashboard. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` Sau khi thêm skill, chạy `/adapty-cli` trong agent của bạn. Nó sẽ hướng dẫn bạn qua từng bước — bao gồm cả khi nào cần mở Dashboard để kết nối cửa hàng. ### Cách thủ công qua Dashboard \{#dashboard-approach\} Nếu bạn muốn tự cấu hình mọi thứ, đây là những gì bạn cần trước khi viết code. LLM của bạn không thể tra cứu các giá trị dashboard — bạn cần cung cấp chúng. 1. **Kết nối cửa hàng ứng dụng**: Trong Adapty Dashboard, vào **App settings → General**. Kết nối cả App Store và Google Play nếu ứng dụng Unity của bạn nhắm đến cả hai nền tảng. Đây là yêu cầu bắt buộc để mua hàng hoạt động. [Kết nối cửa hàng ứng dụng](integrate-payments) 2. **Sao chép Public SDK key**: Trong Adapty Dashboard, vào **App settings → General**, sau đó tìm phần **API keys**. Trong code, đây là chuỗi bạn truyền vào Adapty configuration builder. 3. **Tạo ít nhất một sản phẩm**: Trong Adapty Dashboard, vào trang **Products**. Bạn không tham chiếu sản phẩm trực tiếp trong code — Adapty cung cấp chúng thông qua paywall. [Thêm sản phẩm](quickstart-products) 4. **Tạo paywall và placement**: Trong Adapty Dashboard, tạo paywall trên trang **Paywalls**, sau đó gán nó cho một placement trên trang **Placements**. Trong code, placement ID là chuỗi bạn truyền vào `Adapty.GetPaywall("YOUR_PLACEMENT_ID")`. [Tạo paywall](quickstart-paywalls) 5. **Thiết lập mức độ truy cập**: Trong Adapty Dashboard, cấu hình cho từng sản phẩm trên trang **Products**. Trong code, chuỗi được kiểm tra trong `profile.AccessLevels["premium"]?.IsActive`. Mức độ truy cập `premium` mặc định phù hợp với hầu hết các ứng dụng. Nếu người dùng trả phí được truy cập các tính năng khác nhau tùy theo sản phẩm (ví dụ: gói `basic` so với gói `pro`), hãy [tạo thêm mức độ truy cập](assigning-access-level-to-a-product) trước khi bắt đầu viết code. :::tip Khi bạn có đủ năm mục trên, bạn đã sẵn sàng viết code. Hãy nói với LLM của bạn: "Public SDK key của tôi là X, placement ID của tôi là Y" để nó có thể tạo code khởi tạo và lấy paywall chính xác. ::: ### Thiết lập khi sẵn sàng \{#set-up-when-ready\} Những mục này không bắt buộc để bắt đầu viết code, nhưng bạn sẽ cần chúng khi tích hợp trưởng thành hơn: - **A/B test**: Cấu hình trên trang **Placements**. Không cần thay đổi code. [A/B test](ab-tests) - **Thêm paywall và placement**: Thêm nhiều lệnh gọi `GetPaywall` với các placement ID khác nhau. - **Tích hợp analytics**: Cấu hình trên trang **Integrations**. Cách thiết lập khác nhau tùy theo tích hợp. Xem [tích hợp analytics](analytics-integration) và [tích hợp attribution](attribution-integration). ## Cung cấp tài liệu Adapty cho LLM của bạn \{#feed-adapty-docs-to-your-llm\} ### Dùng Context7 (khuyến nghị) \{#use-context7-recommended\} [Context7](https://context7.com) là một MCP server cung cấp cho LLM của bạn quyền truy cập trực tiếp vào tài liệu Adapty cập nhật nhất. LLM của bạn tự động lấy đúng tài liệu dựa trên những gì bạn hỏi — không cần dán URL thủ công. Context7 hoạt động với **Cursor**, **Claude Code**, **Windsurf**, và các công cụ tương thích MCP khác. Để thiết lập, chạy: ``` npx ctx7 setup ``` Lệnh này sẽ phát hiện trình soạn thảo của bạn và cấu hình Context7 server. Để thiết lập thủ công, xem [Context7 GitHub repository](https://github.com/upstash/context7). Sau khi cấu hình, tham chiếu thư viện Adapty trong các prompt của bạn: ``` Use the adaptyteam/adapty-docs library to look up how to install the Unity SDK ``` :::warning Dù Context7 giúp bạn không cần dán link tài liệu thủ công, thứ tự triển khai vẫn quan trọng. Hãy làm theo [hướng dẫn triển khai](#implementation-walkthrough) bên dưới từng bước để đảm bảo mọi thứ hoạt động. ::: ### Dùng tài liệu dạng plain text \{#use-plain-text-docs\} Bạn có thể truy cập bất kỳ tài liệu Adapty nào dưới dạng Markdown thuần. Thêm `.md` vào cuối URL, hoặc nhấn **Copy for LLM** dưới tiêu đề bài viết. Ví dụ: [adapty-cursor-unity.md](https://adapty.io/docs/vi/adapty-cursor-unity.md). Mỗi giai đoạn trong [hướng dẫn triển khai](#implementation-walkthrough) bên dưới đều có block "Gửi cho LLM của bạn" với các link `.md` để dán vào. Để có nhiều tài liệu hơn cùng một lúc, xem [file index và các tập con theo nền tảng](#plain-text-doc-index-files) bên dưới. ## Hướng dẫn triển khai \{#implementation-walkthrough\} Phần còn lại của hướng dẫn này đi qua việc tích hợp Adapty theo thứ tự triển khai. Mỗi giai đoạn bao gồm tài liệu cần gửi cho LLM, những gì bạn sẽ thấy khi hoàn thành, và các vấn đề thường gặp. ### Lên kế hoạch tích hợp \{#plan-your-integration\} Trước khi bắt đầu viết code, hãy yêu cầu LLM phân tích dự án của bạn và tạo kế hoạch triển khai. Nếu công cụ AI của bạn hỗ trợ chế độ lập kế hoạch (như chế độ plan của Cursor hoặc Claude Code), hãy sử dụng nó để LLM có thể đọc cả cấu trúc dự án lẫn tài liệu Adapty trước khi viết code. Hãy cho LLM biết bạn dùng cách tiếp cận nào để xử lý mua hàng — điều này ảnh hưởng đến hướng dẫn mà nó nên theo: - [**Adapty Paywall Builder**](adapty-paywall-builder): Bạn tạo paywall trong trình tạo no-code của Adapty, và SDK hiển thị chúng tự động. - [**Paywall tự tạo**](unity-making-purchases): Bạn tự xây dựng giao diện paywall trong code nhưng vẫn dùng Adapty để lấy sản phẩm và xử lý mua hàng. - [**Observer mode**](observer-vs-full-mode): Bạn giữ nguyên hạ tầng mua hàng hiện có và chỉ dùng Adapty cho analytics và tích hợp. Chưa biết chọn cái nào? Đọc [bảng so sánh trong quickstart](unity-quickstart-paywalls). ### Cài đặt và cấu hình SDK \{#install-and-configure-the-sdk\} Thêm package Adapty SDK qua Unity Package Manager và kích hoạt nó với Public SDK key của bạn. Đây là nền tảng — mọi thứ khác đều không hoạt động nếu thiếu bước này. **Hướng dẫn:** [Cài đặt & cấu hình Adapty SDK](sdk-installation-unity) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/sdk-installation-unity.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Dự án build và chạy được. Unity Console hiển thị log kích hoạt Adapty. - **Lưu ý:** "Public API key is missing" → kiểm tra xem bạn đã thay thế placeholder bằng key thật từ App settings chưa. ::: ### Hiển thị paywall và xử lý mua hàng \{#show-paywalls-and-handle-purchases\} Lấy paywall theo placement ID, hiển thị nó và xử lý các sự kiện mua hàng. Hướng dẫn bạn cần phụ thuộc vào cách bạn xử lý mua hàng. Kiểm tra từng lần mua trong sandbox khi bạn thực hiện — đừng chờ đến cuối. Xem [Kiểm tra mua hàng trong sandbox](test-purchases-in-sandbox) để biết hướng dẫn thiết lập. **Hướng dẫn:** - [Bật mua hàng sử dụng paywall (quickstart)](unity-quickstart-paywalls) - [Lấy paywall từ Paywall Builder và cấu hình của chúng](unity-get-pb-paywalls) - [Hiển thị paywall](unity-present-paywalls) - [Xử lý sự kiện paywall](unity-handling-events) - [Phản hồi các hành động button](unity-handle-paywall-actions) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/unity-quickstart-paywalls.md - https://adapty.io/docs/vi/unity-get-pb-paywalls.md - https://adapty.io/docs/vi/unity-present-paywalls.md - https://adapty.io/docs/vi/unity-handling-events.md - https://adapty.io/docs/vi/unity-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Paywall xuất hiện với các sản phẩm bạn đã cấu hình. Nhấn vào sản phẩm kích hoạt hộp thoại mua hàng sandbox. - **Lưu ý:** Paywall trống hoặc lỗi `GetPaywall` → xác minh placement ID khớp chính xác với dashboard và placement có đối tượng được gán. ::: **Hướng dẫn:** - [Bật mua hàng trong paywall tùy chỉnh của bạn (quickstart)](unity-quickstart-manual) - [Lấy paywall và sản phẩm](fetch-paywalls-and-products-unity) - [Hiển thị paywall được thiết kế bởi remote config](present-remote-config-paywalls-unity) - [Thực hiện mua hàng](unity-making-purchases) - [Khôi phục mua hàng](unity-restore-purchase) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/unity-quickstart-manual.md - https://adapty.io/docs/vi/fetch-paywalls-and-products-unity.md - https://adapty.io/docs/vi/present-remote-config-paywalls-unity.md - https://adapty.io/docs/vi/unity-making-purchases.md - https://adapty.io/docs/vi/unity-restore-purchase.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Paywall tùy chỉnh của bạn hiển thị các sản phẩm lấy từ Adapty. Nhấn vào sản phẩm kích hoạt hộp thoại mua hàng sandbox. - **Lưu ý:** Mảng sản phẩm trống → xác minh paywall có sản phẩm được gán trong dashboard và placement có đối tượng. ::: **Hướng dẫn:** - [Tổng quan Observer mode](observer-vs-full-mode) - [Triển khai Observer mode](implement-observer-mode-unity) - [Báo cáo giao dịch trong Observer mode](report-transactions-observer-mode-unity) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/observer-vs-full-mode.md - https://adapty.io/docs/vi/implement-observer-mode-unity.md - https://adapty.io/docs/vi/report-transactions-observer-mode-unity.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Sau khi mua hàng sandbox bằng flow mua hàng hiện tại của bạn, giao dịch xuất hiện trong **Event Feed** trên Adapty dashboard. - **Lưu ý:** Không có sự kiện → xác minh bạn đang báo cáo giao dịch cho Adapty và server notifications được cấu hình cho cả hai cửa hàng. ::: ### Kiểm tra trạng thái gói đăng ký \{#check-subscription-status\} Sau khi mua hàng, kiểm tra hồ sơ người dùng để xem mức độ truy cập có đang hoạt động không nhằm giới hạn nội dung premium. **Hướng dẫn:** [Kiểm tra trạng thái gói đăng ký](unity-check-subscription-status) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/unity-check-subscription-status.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Sau khi mua hàng sandbox, `profile.AccessLevels["premium"]?.IsActive` trả về `true`. - **Lưu ý:** `AccessLevels` trống sau khi mua hàng → kiểm tra sản phẩm có mức độ truy cập được gán trong dashboard chưa. ::: ### Xác định người dùng \{#identify-users\} Liên kết tài khoản người dùng trong ứng dụng của bạn với hồ sơ Adapty để mua hàng được duy trì trên nhiều thiết bị. :::important Bỏ qua bước này nếu ứng dụng của bạn không có xác thực. ::: **Hướng dẫn:** [Xác định người dùng](unity-quickstart-identify) Gửi cho LLM của bạn: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/vi/unity-quickstart-identify.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Sau khi gọi `Adapty.Identify("your-user-id")`, phần **Profiles** trên dashboard hiển thị ID người dùng tùy chỉnh của bạn. - **Lưu ý:** Gọi `Identify` sau khi kích hoạt nhưng trước khi lấy paywall để tránh attribution hồ sơ ẩn danh. ::: ### Chuẩn bị phát hành \{#prepare-for-release\} Khi tích hợp của bạn hoạt động trong sandbox, hãy đi qua checklist phát hành để đảm bảo mọi thứ sẵn sàng cho môi trường production. **Hướng dẫn:** [Checklist phát hành](release-checklist) Gửi cho LLM của bạn: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/vi/release-checklist.md ``` :::tip[Checkpoint] - **Kết quả mong đợi:** Tất cả các mục trong checklist được xác nhận: kết nối cửa hàng, server notifications, flow mua hàng, kiểm tra mức độ truy cập và các yêu cầu về quyền riêng tư. - **Lưu ý:** Thiếu server notifications → cấu hình App Store Server Notifications trong **App settings → iOS SDK** và Google Play Real-Time Developer Notifications trong **App settings → Android SDK**. ::: ## File index tài liệu dạng plain text \{#plain-text-doc-index-files\} Nếu bạn cần cung cấp cho LLM ngữ cảnh rộng hơn ngoài các trang riêng lẻ, chúng tôi lưu trữ các file index liệt kê hoặc kết hợp toàn bộ tài liệu Adapty: - [`llms.txt`](https://adapty.io/docs/vi/llms.txt): Liệt kê tất cả các trang với các link `.md`. Đây là [tiêu chuẩn đang nổi lên](https://llmstxt.org/) để làm cho website có thể truy cập bởi LLM. Lưu ý rằng với một số AI agent (ví dụ: ChatGPT), bạn sẽ cần tải xuống `llms.txt` và tải lên chat dưới dạng file. - [`llms-full.txt`](https://adapty.io/docs/vi/llms-full.txt): Toàn bộ tài liệu Adapty được kết hợp thành một file duy nhất. Rất lớn — chỉ dùng khi bạn cần toàn bộ nội dung. - [`unity-llms.txt`](https://adapty.io/docs/vi/unity-llms.txt) và [`unity-llms-full.txt`](https://adapty.io/docs/vi/unity-llms-full.txt) dành riêng cho Unity: Các tập con theo nền tảng giúp tiết kiệm token so với toàn bộ site. --- # File: unity-get-pb-paywalls --- --- title: "Lấy paywall từ Paywall Builder và cấu hình của chúng trong Unity SDK" description: "Tìm hiểu cách lấy paywall PB trong Adapty để kiểm soát gói đăng ký tốt hơn trong ứng dụng Unity của bạn." --- Sau khi [bạn thiết kế phần giao diện cho paywall của mình](adapty-paywall-builder) bằng Paywall Builder mới trong Adapty Dashboard, bạn có thể hiển thị nó trong ứng dụng di động của mình. Bước đầu tiên trong quy trình này là lấy paywall được liên kết với placement và cấu hình view của nó như mô tả bên dưới. :::warning Paywall Builder mới hoạt động với Unity SDK phiên bản 3.3.0 trở lên. ::: Lưu ý rằng chủ đề này đề cập đến các paywall được tùy chỉnh bằng Paywall Builder. Nếu bạn đang triển khai paywall theo cách thủ công, hãy tham khảo chủ đề [Lấy paywall và sản phẩm cho paywall remote config trong ứng dụng di động của bạn](fetch-paywalls-and-products-unity). :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. :::
Trước khi bắt đầu hiển thị paywall trong ứng dụng di động của bạn (nhấn để mở rộng) 1. [Tạo sản phẩm của bạn](create-product) trong Adapty Dashboard. 2. [Tạo paywall và thêm sản phẩm vào đó](create-paywall) trong Adapty Dashboard. 3. [Tạo placement và thêm paywall vào đó](create-placement) trong Adapty Dashboard. 4. Cài đặt [Adapty SDK](sdk-installation-unity) trong ứng dụng di động của bạn.
## Lấy paywall được thiết kế bằng Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Nếu bạn đã [thiết kế paywall bằng Paywall Builder](adapty-paywall-builder), bạn không cần lo lắng về việc render nó trong mã ứng dụng di động để hiển thị cho người dùng. Paywall như vậy chứa cả nội dung cần hiển thị và cách hiển thị nó. Tuy nhiên, bạn vẫn cần lấy ID của nó thông qua placement, cấu hình view của nó, rồi sau đó hiển thị nó trong ứng dụng di động của bạn. Để đảm bảo hiệu suất tối ưu, điều quan trọng là phải lấy paywall và [cấu hình view](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) của nó càng sớm càng tốt, cho phép đủ thời gian để tải hình ảnh trước khi hiển thị cho người dùng. Để lấy paywall, sử dụng phương thức `GetPaywall`: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |---------|--------|-----------| | **placementId** | bắt buộc | Mã định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Mã định danh của [bản địa hóa paywall](add-paywall-locale-in-adapty-paywall-builder). Tham số này được kỳ vọng là mã ngôn ngữ bao gồm một hoặc hai subtag được phân tách bằng ký tự dấu trừ (**-**). Subtag đầu tiên là cho ngôn ngữ, subtag thứ hai là cho khu vực.

Ví dụ: `en` có nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha Brazil.

Xem [Bản địa hóa và mã locale](localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng chúng.

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố gắng tải dữ liệu từ máy chủ và sẽ trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình gặp vấn đề với kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ trải nghiệm thời gian tải nhanh hơn, bất kể kết nối internet của họ kém đến đâu. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên là an toàn để tránh các yêu cầu mạng.

Lưu ý rằng cache vẫn còn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ paywall cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](fallback-paywalls). Chúng tôi cũng sử dụng CDN để lấy paywall nhanh hơn và một máy chủ dự phòng độc lập trong trường hợp CDN không thể truy cập được. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywall trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.

| | **loadTimeout** | mặc định: 5 giây |

Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về.

Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, vì thao tác có thể bao gồm các yêu cầu khác nhau bên dưới.

| Tham số phản hồi: | Tham số | Mô tả | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) với danh sách ID sản phẩm, mã định danh paywall, Remote Config và một số thuộc tính khác. | ## Lấy cấu hình view của paywall được thiết kế bằng Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Hãy đảm bảo bật toggle **Show on device** trong paywall builder. Nếu tùy chọn này không được bật, cấu hình view sẽ không thể lấy được. ::: Sau khi lấy paywall, hãy kiểm tra xem nó có `ViewConfiguration` không, điều này cho biết nó được tạo bằng Paywall Builder. Điều này sẽ hướng dẫn bạn cách hiển thị paywall. Nếu `ViewConfiguration` có mặt, hãy xử lý nó như paywall Paywall Builder; nếu không, [xử lý nó như paywall remote config](present-remote-config-paywalls-unity). Trong Unity SDK, hãy gọi trực tiếp phương thức `CreatePaywallView` mà không cần lấy cấu hình view thủ công trước. :::warning Kết quả của phương thức `CreatePaywallView` chỉ có thể được sử dụng một lần. Nếu bạn cần sử dụng lại, hãy gọi phương thức `CreatePaywallView` từ đầu. Gọi nó hai lần mà không tạo lại có thể dẫn đến lỗi `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 }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | | :------------------ | :------------- | :----------------------------------------------------------- | | **paywall** | bắt buộc | Một đối tượng `AdaptyPaywall` để lấy controller cho paywall mong muốn. | | **loadTimeout** | mặc định: 5 giây | Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về. Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, vì thao tác có thể bao gồm các yêu cầu khác nhau bên dưới. | | **PreloadProducts** | tùy chọn | Cung cấp một mảng `AdaptyPaywallProducts` để tối ưu hóa thời gian hiển thị sản phẩm trên màn hình. Nếu `nil` được truyền vào, AdaptyUI sẽ tự động lấy các sản phẩm cần thiết. | | **CustomTags** | tùy chọn | Định nghĩa một dictionary các custom tag và giá trị đã được xử lý của chúng. Custom tag đóng vai trò là placeholder trong nội dung paywall, được thay thế động bằng các chuỗi cụ thể cho nội dung được cá nhân hóa trong paywall. Tham khảo chủ đề Custom tags in paywall builder để biết thêm chi tiết. | | **CustomTimers** | tùy chọn | Định nghĩa một dictionary các custom timer và ngày kết thúc của chúng. Custom timer cho phép bạn hiển thị đồng hồ đếm ngược trong paywall của mình. | :::note Nếu bạn đang sử dụng nhiều ngôn ngữ, hãy tìm hiểu cách thêm [bản địa hóa Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) và cách sử dụng mã locale đúng cách [tại đây](localizations-and-locale-codes). ::: Sau khi có view, [hiển thị paywall](unity-present-paywalls). ## Tùy chỉnh assets \{#customize-assets\} Để tùy chỉnh hình ảnh và video trong paywall của bạn, hãy triển khai custom assets. Hình ảnh hero và video có ID được định nghĩa sẵn: `hero_image` và `hero_video`. Trong một custom asset bundle, bạn nhắm đến các phần tử này bằng ID của chúng và tùy chỉnh hành vi của chúng. Đối với các hình ảnh và video khác, bạn cần [đặt custom ID](custom-media) trong Adapty dashboard. Ví dụ, bạn có thể: - Hiển thị hình ảnh hoặc video khác cho một số người dùng. - Hiển thị ảnh xem trước cục bộ trong khi hình ảnh chính từ xa đang tải. - Hiển thị ảnh xem trước trước khi phát video. :::important Để sử dụng tính năng này, hãy cập nhật Adapty Unity SDK lên phiên bản 3.8.0 trở lên. ::: Dưới đây là ví dụ về cách bạn có thể cung cấp custom assets thông qua một dictionary đơn giản: ```csharp showLineNumbers var customAssets = new Dictionary { { "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 Nếu một asset không được tìm thấy, paywall sẽ hiển thị với giao diện mặc định của nó. ::: ## Thiết lập timer do nhà phát triển định nghĩa \{#set-up-developer-defined-timers\} Để sử dụng custom timer trong ứng dụng Unity của bạn, bạn có thể truyền một dictionary gồm các ID timer và ngày kết thúc của chúng trực tiếp vào phương thức `SetCustomTimers`. Dưới đây là ví dụ: ```csharp showLineNumbers var customTimers = new Dictionary { { "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 }); ``` Trong ví dụ này, `CUSTOM_TIMER_NY` và `CUSTOM_TIMER_6H` là các **Timer ID** của các timer do nhà phát triển định nghĩa mà bạn đặt trong Adapty Dashboard. Bộ xử lý timer đảm bảo ứng dụng của bạn cập nhật động mỗi timer với giá trị chính xác. Ví dụ: - `CUSTOM_TIMER_NY`: Thời gian còn lại cho đến khi timer kết thúc, chẳng hạn như Năm mới. - `CUSTOM_TIMER_6H`: Thời gian còn lại trong khoảng thời gian 6 giờ bắt đầu khi người dùng mở paywall. ## Tăng tốc lấy paywall với paywall đối tượng mặc định \{#speed-up-paywall-fetching-with-default-audience-paywall\} Thông thường, paywall được lấy gần như ngay lập tức, vì vậy bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong các trường hợp bạn có nhiều đối tượng và paywall, và người dùng của bạn có kết nối internet yếu, việc lấy paywall có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống như vậy, bạn có thể muốn hiển thị paywall mặc định để đảm bảo trải nghiệm người dùng mượt mà thay vì không hiển thị paywall nào cả. Để giải quyết vấn đề này, bạn có thể sử dụng phương thức `GetPaywallForDefaultAudience`, phương thức này lấy paywall của placement được chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy paywall bằng phương thức `getPaywall`, như được trình bày chi tiết trong phần [Lấy Paywall](#fetch-paywall) ở trên. :::warning Hãy cân nhắc sử dụng `GetPaywall` thay vì `GetPaywallForDefaultAudience`, vì phương thức sau có những hạn chế quan trọng: - **Vấn đề tương thích**: Có thể tạo ra vấn đề khi hỗ trợ nhiều phiên bản ứng dụng, đòi hỏi thiết kế tương thích ngược hoặc chấp nhận rằng các phiên bản cũ hơn có thể hiển thị không chính xác. - **Không có cá nhân hóa**: Chỉ hiển thị nội dung cho đối tượng "All Users", loại bỏ việc nhắm mục tiêu dựa trên quốc gia, attribution hoặc custom attributes. Nếu việc lấy nhanh hơn vượt trội so với những hạn chế này cho trường hợp sử dụng của bạn, hãy sử dụng `GetPaywallForDefaultAudience` như được hiển thị bên dưới. Nếu không, hãy sử dụng `GetPaywall` như được mô tả [ở trên](#fetch-paywall). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | bắt buộc | Mã định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Mã định danh của bản địa hóa paywall. Tham số này được kỳ vọng là mã ngôn ngữ bao gồm một hoặc hai subtag được phân tách bằng ký tự dấu trừ (**-**). Subtag đầu tiên là cho ngôn ngữ, subtag thứ hai là cho khu vực.

Ví dụ: `en` có nghĩa là tiếng Anh, `pt-br` đại diện cho tiếng Bồ Đào Nha Brazil.

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố gắng tải dữ liệu từ máy chủ và sẽ trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình gặp vấn đề với kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ trải nghiệm thời gian tải nhanh hơn, bất kể kết nối internet của họ kém đến đâu. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên là an toàn để tránh các yêu cầu mạng.

Lưu ý rằng cache vẫn còn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ paywall cục bộ theo hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và paywall dự phòng. Chúng tôi cũng sử dụng CDN để lấy paywall nhanh hơn và một máy chủ dự phòng độc lập trong trường hợp CDN không thể truy cập được. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywall trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet kém.

| --- # File: unity-present-paywalls --- --- title: "Hiển thị paywall" description: "Tìm hiểu cách hiển thị paywall trong ứng dụng Unity của bạn với Adapty SDK." --- Nếu bạn đã tùy chỉnh paywall bằng Paywall Builder, bạn không cần lo lắng về việc render nó trong code ứng dụng để hiển thị cho người dùng. Paywall đó đã bao gồm cả nội dung hiển thị lẫn cách thức hiển thị. :::warning Hướng dẫn này đề cập đến **Paywall Builder mới**, yêu cầu Adapty SDK 3.3.0 trở lên. Để hiển thị paywall dùng Remote Config, xem [Render paywalls designed with remote config](present-remote-config-paywalls). ::: Để hiển thị một paywall, sử dụng phương thức `view.Present()` trên `view` được tạo bởi phương thức [`CreatePaywallView`](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Mỗi `view` chỉ có thể được sử dụng một lần. Nếu bạn cần hiển thị lại paywall, hãy gọi `CreatePaywallView` thêm một lần nữa để tạo một `view` instance mới. :::warning Việc tái sử dụng cùng một `view` mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ## Hiển thị hộp thoại \{#show-dialog\} Sử dụng phương thức này thay vì các hộp thoại cảnh báo gốc khi một paywall view đang được hiển thị trên Android. Trên Android, các cảnh báo thông thường xuất hiện phía sau paywall view, khiến người dùng không nhìn thấy chúng. Phương thức này đảm bảo hộp thoại được hiển thị đúng cách phía trên paywall trên tất cả các nền tảng. ```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 } }); ``` ## Cấu hình kiểu trình bày trên iOS \{#configure-ios-presentation-style\} Cấu hình cách paywall được hiển thị trên iOS bằng cách truyền tham số `iosPresentationStyle` vào phương thức `Present()`. Tham số này nhận giá trị `AdaptyUIIOSPresentationStyle.FullScreen` (mặc định) hoặc `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` --- # File: unity-handle-paywall-actions --- --- title: "Xử lý hành động nút trong Unity SDK" description: "Xử lý hành động nút trên paywall trong Unity bằng Adapty để tối ưu hóa doanh thu ứng dụng." --- Nếu bạn đang xây dựng paywall bằng Adapty Paywall Builder, việc thiết lập nút đúng cách là rất quan trọng: 1. Thêm [nút trong Paywall Builder](paywall-buttons) và gán cho nó một hành động có sẵn hoặc tạo ID hành động tùy chỉnh. 2. Viết code trong ứng dụng để xử lý từng hành động bạn đã gán. Hướng dẫn này hướng dẫn cách xử lý các hành động tùy chỉnh và hành động có sẵn trong code của bạn. :::warning **Chỉ có giao dịch mua và khôi phục được xử lý tự động.** Tất cả các hành động nút khác, chẳng hạn như đóng paywall hoặc mở liên kết, đều cần được xử lý trong code của ứng dụng. ::: ## Đóng paywall \{#close-paywalls\} Để thêm nút đóng paywall: 1. Trong Paywall Builder, thêm một nút và gán cho nó hành động **Close**. 2. Trong code ứng dụng, triển khai handler cho hành động `close` để đóng paywall. ```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; } } ``` ## Mở URL từ paywall \{#open-urls-from-paywalls\} :::tip Nếu bạn muốn thêm một nhóm liên kết (ví dụ: điều khoản sử dụng và khôi phục giao dịch mua), hãy thêm phần tử **Link** trong Paywall Builder và xử lý nó giống như các nút có hành động **Open URL**. ::: Để thêm nút mở liên kết từ paywall của bạn (ví dụ: **Điều khoản sử dụng** hoặc **Chính sách quyền riêng tư**): 1. Trong Paywall Builder, thêm một nút, gán cho nó hành động **Open URL**, và nhập URL bạn muốn mở. 2. Trong code ứng dụng, triển khai handler cho hành động `openUrl` để mở URL nhận được trong trình duyệt. ```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; } } ``` ## Đăng nhập vào ứng dụng \{#log-into-the-app\} Để thêm nút cho phép người dùng đăng nhập vào ứng dụng: 1. Trong Paywall Builder, thêm một nút và gán cho nó hành động **Custom** với ID `login`. 2. Trong code ứng dụng, triển khai handler cho hành động tùy chỉnh `login` để xác định người dùng của bạn. ```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; } } ``` ## Xử lý hành động tùy chỉnh \{#handle-custom-actions\} Để thêm nút xử lý các hành động khác: 1. Trong Paywall Builder, thêm một nút, gán cho nó hành động **Custom**, và đặt ID cho nó. 2. Trong code ứng dụng, triển khai handler cho ID hành động bạn đã tạo. Ví dụ, nếu bạn có thêm một bộ ưu đãi gói đăng ký hoặc sản phẩm mua một lần, bạn có thể thêm nút để hiển thị một paywall khác: ```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: "Xử lý sự kiện paywall" description: "Tìm hiểu cách xử lý các sự kiện paywall trong ứng dụng Unity của bạn với Adapty SDK." --- :::important Hướng dẫn này đề cập đến việc xử lý sự kiện cho các giao dịch mua, khôi phục, lựa chọn sản phẩm và hiển thị paywall. Bạn cũng cần triển khai xử lý nút bấm (đóng paywall, mở liên kết, v.v.). Xem [hướng dẫn xử lý hành động nút bấm](unity-handle-paywall-actions) để biết thêm chi tiết. ::: Các paywall được cấu hình bằng [Paywall Builder](adapty-paywall-builder) không cần thêm code để thực hiện và khôi phục giao dịch mua. Tuy nhiên, chúng tạo ra một số sự kiện mà ứng dụng của bạn có thể phản hồi. Những sự kiện đó bao gồm các lần nhấn nút (nút đóng, URL, lựa chọn sản phẩm, v.v.) cũng như thông báo về các hành động liên quan đến giao dịch mua được thực hiện trên paywall. Tìm hiểu cách phản hồi các sự kiện này bên dưới. :::warning Hướng dẫn này chỉ dành cho **paywall Paywall Builder mới** yêu cầu Adapty SDK v3.3.0 trở lên. ::: :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ## Xử lý sự kiện \{#handling-events\} Để kiểm soát hoặc theo dõi các tiến trình xảy ra trên màn hình paywall trong ứng dụng di động của bạn, hãy triển khai interface `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 } ``` ### Sự kiện do người dùng tạo ra \{#user-generated-events\} #### Paywall xuất hiện \{#paywall-appeared\} Được gọi khi màn hình paywall hiển thị trên màn hình. :::note Trên iOS, cũng được gọi khi người dùng nhấn vào [nút web paywall](web-paywall#step-2a-add-a-web-purchase-button) bên trong một paywall và web paywall mở trong trình duyệt trong ứng dụng. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Paywall biến mất \{#paywall-disappeared\} Được gọi khi màn hình paywall bị đóng khỏi màn hình. :::note Trên iOS, cũng được gọi khi [web paywall](web-paywall#step-2a-add-a-web-purchase-button) được mở từ paywall trong trình duyệt trong ứng dụng biến mất khỏi màn hình. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Chọn sản phẩm \{#product-selection\} Được gọi khi một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống). ```csharp showLineNumbers title="Unity" public void PaywallViewDidSelectProduct( AdaptyUIPaywallView view, string productId ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```javascript { "productId": "premium_monthly" } ```
#### Bắt đầu mua \{#started-purchase\} Được gọi khi người dùng khởi tạo quá trình mua hàng. ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Mua thành công, đã hủy hoặc đang chờ xử lý \{#successful-canceled-or-pending-purchase\} Nếu giao dịch mua thành công, người dùng hủy giao dịch, hoặc giao dịch mua đang ở trạng thái chờ xử lý, phương thức này sẽ được gọi. Các trường hợp người dùng hủy và thanh toán đang chờ xử lý (ví dụ: cần phê duyệt của phụ huynh) sẽ kích hoạt phương thức này, không phải `PaywallViewDidFailPurchase`. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```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" } } ```
Chúng tôi khuyến nghị đóng màn hình trong trường hợp này. #### Mua thất bại \{#failed-purchase\} Nếu giao dịch mua thất bại do lỗi, phương thức này sẽ được gọi. Điều này bao gồm các lỗi StoreKit/Google Play Billing (hạn chế thanh toán, sản phẩm không hợp lệ, lỗi mạng), lỗi xác minh giao dịch và lỗi hệ thống. Lưu ý rằng các trường hợp người dùng hủy sẽ kích hoạt `PaywallViewDidFinishPurchase` với kết quả đã hủy, và các thanh toán đang chờ xử lý không kích hoạt phương thức này. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```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" } } } ```
#### Bắt đầu khôi phục \{#started-restore\} Được gọi khi người dùng khởi tạo quá trình khôi phục: ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Khôi phục thành công \{#successful-restore\} Được gọi khi khôi phục giao dịch mua thành công: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishRestore( AdaptyUIPaywallView view, AdaptyProfile profile ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```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" } ] } } ```
Chúng tôi khuyến nghị đóng màn hình nếu người dùng có `accessLevel` cần thiết. Tham khảo chủ đề [Trạng thái gói đăng ký](unity-listen-subscription-changes) để tìm hiểu cách kiểm tra. #### Khôi phục thất bại \{#failed-restore\} Được gọi khi khôi phục giao dịch mua thất bại: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRestore( AdaptyUIPaywallView view, AdaptyError error ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Hoàn tất điều hướng thanh toán web \{#finished-web-payment-navigation\} Sau khi cố gắng mở [web paywall](web-paywall) để mua hàng (dù thành công hay thất bại), phương thức này sẽ được gọi: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishWebPaymentNavigation( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` **Tham số:** - `product`: Sản phẩm mà web paywall được mở (hoặc thử mở) - `error`: `null` nếu web paywall mở thành công, hoặc `AdaptyError` nếu thất bại
Ví dụ sự kiện (Nhấn để mở rộng) ```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" } } } ```
### Tải dữ liệu và hiển thị \{#data-fetching-and-rendering\} #### Lỗi tải sản phẩm \{#product-loading-errors\} Được gọi khi tải sản phẩm thất bại và cung cấp `AdaptyError`. Nếu bạn không truyền mảng sản phẩm trong quá trình khởi tạo, AdaptyUI sẽ tự lấy các đối tượng cần thiết từ máy chủ. Thao tác này có thể thất bại và AdaptyUI sẽ báo lỗi bằng cách gọi phương thức này: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailLoadingProducts( AdaptyUIPaywallView view, AdaptyError error ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
#### Lỗi hiển thị \{#rendering-errors\} Được gọi khi xảy ra lỗi trong quá trình hiển thị giao diện và cung cấp `AdaptyError`: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRendering( AdaptyUIPaywallView view, AdaptyError error ) { } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
Trong điều kiện bình thường, các lỗi như vậy không nên xảy ra, vì vậy nếu bạn gặp phải, vui lòng thông báo cho chúng tôi. --- # File: unity-web-paywalls --- --- title: "Triển khai web paywall trong Unity SDK" description: "Thiết lập web paywall để nhận thanh toán mà không mất phí và không cần kiểm duyệt của App Store." --- :::important Trước khi bắt đầu, hãy đảm bảo bạn đã [cấu hình web paywall trong dashboard](web-paywall) và đã cài đặt Adapty SDK phiên bản 3.14 trở lên. ::: ## Mở web paywall \{#open-web-paywalls\} Nếu bạn đang làm việc với paywall tự phát triển, bạn cần xử lý web paywall bằng phương thức SDK. Phương thức `Adapty.OpenWebPaywall`: 1. Tạo một URL duy nhất cho phép Adapty liên kết paywall cụ thể được hiển thị cho một người dùng nhất định với trang web họ được chuyển hướng đến. 2. Theo dõi khi người dùng quay lại ứng dụng và sau đó gọi `Adapty.GetProfile` theo chu kỳ ngắn để xác định xem quyền truy cập của hồ sơ người dùng có được cập nhật hay không. Nhờ vậy, nếu thanh toán thành công và quyền truy cập được cập nhật, gói đăng ký sẽ kích hoạt trong ứng dụng gần như ngay lập tức. ```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 Có hai phiên bản của phương thức `OpenWebPaywall`: 1. `OpenWebPaywall(product)` tạo URL theo paywall và đồng thời thêm dữ liệu sản phẩm vào URL. 2. `OpenWebPaywall(paywall)` tạo URL theo paywall mà không thêm dữ liệu sản phẩm vào URL. Dùng phiên bản này khi các sản phẩm trong Adapty paywall của bạn khác với những sản phẩm trên web paywall. ::: #### Xử lý lỗi \{#handle-errors\} | Mã lỗi | Mô tả | Hành động khuyến nghị | |-----------|--------------------------------------------------------|---------------------------------------------------------------------------| | `AdaptyErrorCode.WrongParam` | Paywall hoặc sản phẩm chưa được cấu hình URL mua hàng qua web, hoặc không thể mở URL trong trình duyệt | Kiểm tra thông báo lỗi để biết chi tiết. Xác minh cấu hình paywall/sản phẩm trong Adapty Dashboard, hoặc kiểm tra cài đặt thiết bị. | | `AdaptyErrorCode.DecodingFailed` | Không thể mã hóa đúng các tham số trong URL | Xác minh các tham số URL hợp lệ và được định dạng đúng | :::note Kiểm tra thuộc tính `Message` của lỗi để biết thông tin cụ thể về vấn đề xảy ra, vì `WrongParam` có thể chỉ ra nhiều sự cố khác nhau (thiếu URL mua hàng, không mở được trình duyệt, v.v.). ::: ## Mở web paywall trong trình duyệt trong ứng dụng \{#open-web-paywalls-in-an-in-app-browser\} :::important Mở web paywall trong trình duyệt trong ứng dụng được hỗ trợ bắt đầu từ Adapty SDK v3.15. ::: Theo mặc định, web paywall mở trong trình duyệt ngoài, khiến người dùng rời khỏi ứng dụng của bạn. Để mang lại trải nghiệm liền mạch cho người dùng, bạn có thể mở web paywall trong trình duyệt trong ứng dụng. Cách này hiển thị trang mua hàng web ngay bên trong ứng dụng, cho phép người dùng hoàn tất giao dịch mà không cần chuyển sang ứng dụng khác. Để bật tính năng này, truyền `AdaptyWebPresentation.InAppBrowser` vào phương thức `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 - Use fallback paywalls" description: "Xử lý các trường hợp người dùng ngoại tuyến hoặc máy chủ Adapty không khả dụng" --- :::warning Paywall dự phòng được hỗ trợ bởi Unity SDK v2.11 trở lên. ::: Để duy trì trải nghiệm người dùng mượt mà, điều quan trọng là phải thiết lập [paywall dự phòng](/fallback-paywalls) cho các flow, [paywall](paywalls) và [onboarding](onboardings) của bạn. Biện pháp phòng ngừa này giúp mở rộng khả năng của ứng dụng trong trường hợp mất kết nối internet một phần hoặc hoàn toàn. * **Nếu ứng dụng không thể kết nối đến máy chủ Adapty:** Ứng dụng vẫn có thể hiển thị flow hoặc paywall dự phòng, và truy cập cấu hình onboarding đã lưu cục bộ. * **Nếu ứng dụng không thể kết nối internet:** Ứng dụng vẫn có thể hiển thị flow hoặc paywall dự phòng. Onboarding chứa nội dung từ xa và cần có kết nối internet để hoạt động. :::important Trước khi thực hiện các bước trong hướng dẫn này, hãy [tải xuống](/local-fallback-paywalls) các file cấu hình dự phòng từ Adapty. ::: ## Cấu hình \{#configuration\} 1. Thêm các tệp cấu hình dự phòng vào thư mục `Assets/StreamingAssets` chung trong dự án của bạn. 2. Gọi phương thức `.setFallback` **trước khi** bạn tải paywall hoặc onboarding mục tiêu. ```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 }); ``` Các tham số: | Tham số | Mô tả | |:-------------|:------------------------------------------------------------------------| | **fileName** | Chuỗi chứa tên của tệp cấu hình dự phòng. | --- # File: unity-localizations-and-locale-codes --- --- title: "Sử dụng localization và mã locale trong Unity SDK" description: "Tìm hiểu cách localize paywall trong ứng dụng Unity của bạn với Adapty SDK." --- ## Tại sao điều này quan trọng \{#why-this-is-important\} Có một vài trường hợp mà mã locale phát huy tác dụng — ví dụ, khi bạn cần lấy paywall phù hợp với localization hiện tại của ứng dụng. Vì mã locale khá phức tạp và có thể khác nhau tùy từng nền tảng, chúng tôi sử dụng một tiêu chuẩn nội bộ cho tất cả các nền tảng được hỗ trợ. Tuy nhiên, do sự phức tạp này, điều quan trọng là bạn phải hiểu rõ mình đang gửi gì đến máy chủ của chúng tôi để nhận được localization đúng, và điều gì xảy ra tiếp theo — để bạn luôn nhận được kết quả như mong đợi. ## Tiêu chuẩn mã locale tại Adapty \{#locale-code-standard-at-adapty\} Đối với mã locale, Adapty sử dụng phiên bản được điều chỉnh nhẹ của [tiêu chuẩn BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): mỗi mã gồm các subtag viết thường, phân cách bằng dấu gạch ngang. Một số ví dụ: `en` (tiếng Anh), `pt-br` (tiếng Bồ Đào Nha (Brazil)), `zh` (tiếng Trung Giản thể), `zh-hant` (tiếng Trung Phồn thể). ## Cách khớp mã locale \{#locale-code-matching\} Khi Adapty nhận được yêu cầu từ SDK phía client kèm mã locale và bắt đầu tìm kiếm localization tương ứng cho paywall, quá trình diễn ra như sau: 1. Chuỗi locale đầu vào được chuyển thành chữ thường và tất cả dấu gạch dưới (`_`) được thay thế bằng dấu gạch ngang (`-`) 2. Chúng tôi tìm kiếm localization có mã locale khớp hoàn toàn 3. Nếu không tìm thấy kết quả khớp, chúng tôi lấy chuỗi con trước dấu gạch ngang đầu tiên (`pt` cho `pt-br`) và tiếp tục tìm kiếm 4. Nếu vẫn không tìm thấy, chúng tôi trả về localization mặc định `en` Nhờ vậy, một thiết bị iOS gửi `'pt_BR'`, một thiết bị Android gửi `pt-BR`, và một thiết bị khác gửi `pt-br` đều nhận được kết quả như nhau. ## Triển khai localization: cách khuyến nghị \{#implementing-localizations-recommended-way\} Nếu bạn đang tìm hiểu về localization, nhiều khả năng bạn đã làm việc với các file chuỗi đã được localize trong dự án. Trong trường hợp đó, chúng tôi khuyến nghị thêm một cặp key-value chứa mã locale Adapty tương ứng vào mỗi file cho từng localization. Sau đó, trích xuất giá trị của key đó khi gọi SDK, như sau: ```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('_', '-'); } } ``` Cách này giúp bạn kiểm soát hoàn toàn localization nào sẽ được trả về cho từng người dùng trong ứng dụng. ## Triển khai localization: cách khác \{#implementing-localizations-the-other-way\} Bạn có thể đạt kết quả tương tự (nhưng không hoàn toàn giống) mà không cần định nghĩa mã locale tường minh cho từng localization. Cách này có nghĩa là trích xuất mã locale từ các đối tượng khác mà nền tảng cung cấp, như sau: ```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; } } ``` Lưu ý rằng chúng tôi không khuyến nghị cách này vì một vài lý do: 1. Trên iOS, ngôn ngữ ưu tiên và locale hiện tại không giống nhau. Nếu muốn localization được chọn đúng, bạn phải hoặc dựa vào logic của Apple (vốn hoạt động tự động nếu dùng cách khuyến nghị với các file chuỗi đã localize), hoặc tự tái tạo lại logic đó. 2. Khó dự đoán chính xác máy chủ của Adapty sẽ nhận được gì. Chẳng hạn, trên iOS, có thể lấy được locale như `ar_OM@numbers='latn'` từ thiết bị và gửi đến máy chủ. Với yêu cầu này, bạn sẽ không nhận được localization `ar-om` như mong đợi, mà thay vào đó là `ar` — điều này có thể ngoài ý muốn. Nếu bạn vẫn quyết định dùng cách này — hãy đảm bảo đã kiểm tra tất cả các trường hợp sử dụng liên quan. --- # File: unity-troubleshoot-paywall-builder --- --- title: "Khắc phục sự cố Paywall Builder trong Unity SDK" description: "Khắc phục sự cố Paywall Builder trong Unity SDK" --- Hướng dẫn này giúp bạn xử lý các sự cố thường gặp khi sử dụng paywall được thiết kế trong Adapty Paywall Builder với Unity SDK. ## Lấy cấu hình paywall thất bại \{#getting-a-paywall-configuration-fails\} **Vấn đề**: Phương thức `CreateView` không thể truy xuất cấu hình paywall. **Nguyên nhân**: Paywall chưa được bật hiển thị trên thiết bị trong Paywall Builder. **Giải pháp**: Bật toggle **Show on device** trong Paywall Builder. ## Số lượt xem paywall quá lớn \{#the-paywall-view-number-is-too-big\} **Vấn đề**: Số lượt xem paywall hiển thị gấp đôi so với mong đợi. **Nguyên nhân**: Có thể bạn đang gọi `LogShowPaywall` trong code, khiến số lượt xem bị tính trùng nếu bạn đang dùng Paywall Builder. Với các paywall được thiết kế bằng Paywall Builder, analytics được theo dõi tự động nên bạn không cần dùng phương thức này. **Giải pháp**: Đảm bảo bạn không gọi `LogShowPaywall` trong code nếu đang sử dụng Paywall Builder. ## Các sự cố khác \{#other-issues\} **Vấn đề**: Bạn gặp phải các sự cố liên quan đến Paywall Builder không được đề cập ở trên. **Giải pháp**: Migrate SDK lên phiên bản mới nhất bằng cách sử dụng [hướng dẫn migration](unity-sdk-migration-guides) nếu cần. Nhiều sự cố đã được khắc phục trong các phiên bản SDK mới hơn. --- # File: unity-quickstart-manual --- --- title: "Bật tính năng mua hàng trong paywall tùy chỉnh trong Unity SDK" description: "Tích hợp Adapty SDK vào các paywall Unity tùy chỉnh của bạn để bật tính năng in-app purchase." --- Hướng dẫn này mô tả cách tích hợp Adapty vào các paywall tùy chỉnh của bạn. Bạn có thể toàn quyền kiểm soát việc triển khai paywall, trong khi Adapty SDK lo việc lấy sản phẩm, xử lý giao dịch mua mới và khôi phục các giao dịch trước đó. :::important **Hướng dẫn này dành cho các nhà phát triển đang xây dựng paywall tùy chỉnh.** Nếu bạn muốn cách đơn giản nhất để bật tính năng mua hàng, hãy dùng [Adapty Paywall Builder](unity-quickstart-paywalls). Với Paywall Builder, bạn tạo paywall bằng trình chỉnh sửa trực quan không cần code, Adapty tự động xử lý toàn bộ logic mua hàng, và bạn có thể thử nghiệm các thiết kế khác nhau mà không cần phát hành lại ứng dụng. ::: ## Trước khi bắt đầu \{#before-you-start\} ### Thiết lập sản phẩm \{#set-up-products\} Để bật tính năng in-app purchase, bạn cần hiểu ba khái niệm chính: - [**Sản phẩm**](product) – bất kỳ thứ gì người dùng có thể mua (gói đăng ký, consumable, quyền truy cập trọn đời) - [**Paywall**](paywalls) – các cấu hình xác định sản phẩm nào sẽ được hiển thị. Trong Adapty, paywall là cách duy nhất để lấy sản phẩm, nhưng thiết kế này cho phép bạn chỉnh sửa sản phẩm, giá cả và ưu đãi mà không cần thay đổi code ứng dụng. - [**Placement**](placements) – nơi và thời điểm bạn hiển thị paywall trong ứng dụng (ví dụ: `main`, `onboarding`, `settings`). Bạn thiết lập paywall cho các placement trên dashboard, sau đó gọi chúng theo placement ID trong code. Điều này giúp bạn dễ dàng chạy A/B test và hiển thị các paywall khác nhau cho từng nhóm người dùng. Hãy đảm bảo bạn hiểu các khái niệm này ngay cả khi bạn đang làm việc với paywall tùy chỉnh. Về cơ bản, đây là cách bạn quản lý các sản phẩm bán trong ứng dụng. Để triển khai paywall tùy chỉnh, bạn cần tạo một **paywall** và thêm nó vào một **placement**. Thiết lập này cho phép bạn lấy các sản phẩm. Để hiểu những gì bạn cần làm trên dashboard, hãy xem hướng dẫn bắt đầu nhanh [tại đây](quickstart). ### Quản lý người dùng \{#manage-users\} Bạn có thể làm việc có hoặc không có xác thực backend ở phía bạn. Tuy nhiên, Adapty SDK xử lý người dùng ẩn danh và người dùng đã xác định theo cách khác nhau. Đọc [hướng dẫn bắt đầu nhanh về xác định người dùng](unity-quickstart-identify) để hiểu các đặc điểm cụ thể và đảm bảo bạn đang làm việc với người dùng đúng cách. ## Bước 1. Lấy sản phẩm \{#step-1-get-products\} Để lấy sản phẩm cho paywall tùy chỉnh của bạn, bạn cần: 1. Lấy đối tượng `paywall` bằng cách truyền ID [placement](placements) vào phương thức `getPaywall`. 2. Lấy mảng sản phẩm cho paywall này bằng phương thức `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 }); }); } ``` ## Bước 2. Chấp nhận giao dịch mua \{#step-2-accept-purchases\} Khi người dùng nhấn vào một sản phẩm trong paywall tùy chỉnh của bạn, hãy gọi phương thức `makePurchase` với sản phẩm đã chọn. Thao tác này sẽ xử lý luồng mua hàng và trả về hồ sơ người dùng đã được cập nhật. ```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; } }); } ``` ## Bước 3. Khôi phục giao dịch mua \{#step-3-restore-purchases\} Các cửa hàng ứng dụng yêu cầu tất cả ứng dụng có gói đăng ký phải cung cấp cách để người dùng khôi phục giao dịch mua của họ. Gọi phương thức `restorePurchases` khi người dùng nhấn nút khôi phục. Thao tác này sẽ đồng bộ lịch sử mua hàng của họ với Adapty và trả về hồ sơ người dùng đã được cập nhật. ```csharp showLineNumbers using AdaptySDK; void RestorePurchases() { Adapty.RestorePurchases((profile, error) => { if (error != null) { // Handle the error return; } // Restore successful, profile updated }); } ``` ## Các bước tiếp theo \{#next-steps\} --- no_index: true --- import Callout from '../../../components/Callout.astro'; Bạn có câu hỏi hoặc gặp sự cố? Hãy xem [diễn đàn hỗ trợ](https://adapty.featurebase.app/) của chúng tôi — nơi bạn có thể tìm câu trả lời cho các câu hỏi thường gặp hoặc đặt câu hỏi của riêng mình. Đội ngũ và cộng đồng của chúng tôi luôn sẵn sàng giúp đỡ! Paywall của bạn đã sẵn sàng để hiển thị trong ứng dụng. Hãy kiểm tra giao dịch mua hàng của bạn trong [sandbox App Store](test-purchases-in-sandbox) hoặc trên [Google Play Store](testing-on-android) để đảm bảo bạn có thể hoàn thành một giao dịch mua thử nghiệm từ paywall. Tiếp theo, [kiểm tra xem người dùng đã hoàn tất giao dịch mua chưa](unity-check-subscription-status) để xác định có nên hiển thị paywall hay cấp quyền truy cập vào các tính năng trả phí. --- # File: fetch-paywalls-and-products-unity --- --- title: "Lấy thông tin paywall và sản phẩm cho remote config paywalls trong Unity SDK" description: "Lấy thông tin paywall và sản phẩm trong Adapty Unity SDK để tăng cường khả năng kiếm tiền từ người dùng." --- Trước khi hiển thị remote config và các paywall tùy chỉnh, bạn cần lấy thông tin về chúng. Lưu ý rằng chủ đề này đề cập đến remote config và paywall tùy chỉnh. Để biết hướng dẫn lấy paywalls cho Paywall Builder, vui lòng tham khảo [Lấy paywalls từ Paywall Builder và cấu hình của chúng](unity-get-pb-paywalls). :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. :::
Trước khi bắt đầu lấy paywalls và sản phẩm trong ứng dụng mobile (nhấp để mở rộng) 1. [Tạo sản phẩm của bạn](create-product) trong Adapty Dashboard. 2. [Tạo paywall và thêm sản phẩm vào paywall](create-paywall) trong Adapty Dashboard. 3. [Tạo placement và thêm paywall vào placement](create-placement) trong Adapty Dashboard. 4. [Cài đặt Adapty SDK](sdk-installation-unity) trong ứng dụng mobile của bạn.
## Lấy thông tin paywall \{#fetch-paywall-information\} Trong Adapty, một [sản phẩm](product) là sự kết hợp của các sản phẩm từ cả App Store và Google Play. Các sản phẩm đa nền tảng này được tích hợp vào các paywall, cho phép bạn hiển thị chúng tại các placement cụ thể trong ứng dụng mobile. Để hiển thị các sản phẩm, bạn cần lấy một [Paywall](paywalls) từ một trong các [placement](placements) của mình bằng phương thức `getPaywall`. :::important **Đừng hardcode ID sản phẩm.** ID duy nhất bạn nên hardcode là placement ID. Các paywall được cấu hình từ xa, vì vậy số lượng sản phẩm và ưu đãi hiện có có thể thay đổi bất cứ lúc nào. Ứng dụng của bạn phải xử lý các thay đổi này một cách linh hoạt—nếu hôm nay một paywall trả về hai sản phẩm và ngày mai trả về ba sản phẩm, hãy hiển thị tất cả chúng mà không cần thay đổi code. ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` | Tham số | Bắt buộc | Mô tả | |---------|--------|-----------| | **placementId** | bắt buộc | Định danh của [Placement](placements). Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Định danh của [bản địa hóa paywall](add-remote-config-locale). Tham số này phải là mã ngôn ngữ gồm một hoặc nhiều thẻ con được phân tách bằng ký tự dấu trừ (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.

Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).

Xem [Bản địa hóa và mã ngôn ngữ](unity-localizations-and-locale-codes) để biết thêm thông tin về mã ngôn ngữ và cách chúng tôi khuyến nghị sử dụng chúng.

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất, nhưng họ sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ như thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên làm việc để tránh các yêu cầu mạng là an toàn.

Lưu ý rằng cache vẫn được giữ nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ paywalls trong hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và [paywall dự phòng](unity-use-fallback-paywalls). Chúng tôi cũng sử dụng CDN để tải paywalls nhanh hơn và một máy chủ dự phòng độc lập trong trường hợp CDN không khả dụng. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywalls trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet bị hạn chế.

| | **loadTimeout** | mặc định: 5 giây |

Giá trị này giới hạn thời gian chờ cho phương thức này. Nếu hết thời gian chờ, dữ liệu đã cache hoặc fallback cục bộ sẽ được trả về.

Lưu ý rằng trong một số trường hợp hiếm gặp, phương thức này có thể hết thời gian chờ muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, vì hoạt động có thể bao gồm nhiều yêu cầu khác nhau bên dưới.

| Đừng hardcode ID sản phẩm! Vì các paywall được cấu hình từ xa, các sản phẩm có sẵn, số lượng sản phẩm và các ưu đãi đặc biệt (như dùng thử miễn phí) có thể thay đổi theo thời gian. Hãy đảm bảo code của bạn xử lý được các tình huống này. Ví dụ: nếu ban đầu bạn lấy được 2 sản phẩm, ứng dụng của bạn nên hiển thị 2 sản phẩm đó. Tuy nhiên, nếu sau này bạn lấy được 3 sản phẩm, ứng dụng của bạn nên hiển thị cả 3 sản phẩm mà không cần thay đổi code. Thứ duy nhất bạn phải hardcode là placement ID. Tham số phản hồi: | Tham số | Mô tả | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Một đối tượng [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) gồm: danh sách ID sản phẩm, định danh paywall, remote config, và một số thuộc tính khác. | ## Lấy sản phẩm \{#fetch-products\} Sau khi có paywall, bạn có thể truy vấn mảng sản phẩm tương ứng với nó: ```csharp showLineNumbers Adapty.GetPaywallProducts(paywall, (products, error) => { if(error != null) { // handle the error return; } // products - the requested products array }); ``` Tham số phản hồi: | Tham số | Mô tả | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Danh sách các đối tượng [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) gồm: định danh sản phẩm, tên sản phẩm, giá, đơn vị tiền tệ, thời hạn đăng ký, và một số thuộc tính khác. | Khi triển khai thiết kế paywall của riêng bạn, bạn có thể cần truy cập các thuộc tính này từ đối tượng [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html). Dưới đây là các thuộc tính được sử dụng phổ biến nhất, nhưng hãy tham khảo tài liệu được liên kết để biết đầy đủ chi tiết về tất cả các thuộc tính có sẵn. | Thuộc tính | Mô tả | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Để hiển thị tiêu đề của sản phẩm, sử dụng `product.LocalizedTitle`. Lưu ý rằng bản địa hóa dựa trên quốc gia cửa hàng mà người dùng đã chọn, không phải ngôn ngữ của thiết bị. | | **Price** | Để hiển thị phiên bản bản địa hóa của giá, sử dụng `product.Price.LocalizedString`. Bản địa hóa này dựa trên thông tin ngôn ngữ của thiết bị. Bạn cũng có thể truy cập giá dưới dạng số bằng `product.Price.Amount`. Giá trị sẽ được cung cấp theo đơn vị tiền tệ địa phương. Để lấy ký hiệu tiền tệ tương ứng, sử dụng `product.Price.CurrencySymbol`. | | **Subscription Period** | Để hiển thị chu kỳ (ví dụ: tuần, tháng, năm, v.v.), sử dụng `product.Subscription?.LocalizedPeriod`. Bản địa hóa này dựa trên ngôn ngữ của thiết bị. Để lấy chu kỳ đăng ký theo lập trình, sử dụng `product.Subscription?.Period`. Từ đó bạn có thể truy cập enum `Unit` để lấy độ dài (tức là `AdaptySubscriptionPeriodUnit.Day`, `AdaptySubscriptionPeriodUnit.Week`, `AdaptySubscriptionPeriodUnit.Month`, `AdaptySubscriptionPeriodUnit.Year`, hoặc `AdaptySubscriptionPeriodUnit.Unknown`). Giá trị `NumberOfUnits` sẽ cho bạn biết số đơn vị chu kỳ. Ví dụ: với gói đăng ký hàng quý, bạn sẽ thấy `AdaptySubscriptionPeriodUnit.Month` trong thuộc tính Unit và `3` trong thuộc tính NumberOfUnits. | | **Introductory Offer** | Để hiển thị huy hiệu hoặc chỉ báo khác cho thấy gói đăng ký có ưu đãi giới thiệu, hãy kiểm tra thuộc tính `product.Subscription?.Offer?.Phases`. Đây là danh sách có thể chứa tối đa hai giai đoạn giảm giá: giai đoạn dùng thử miễn phí và giai đoạn giá giới thiệu. Trong mỗi đối tượng giai đoạn có các thuộc tính hữu ích sau:
• `PaymentMode`: một enum với các giá trị `AdaptyPaymentMode.FreeTrial`, `AdaptyPaymentMode.PayAsYouGo`, `AdaptyPaymentMode.PayUpFront`, và `AdaptyPaymentMode.Unknown`. Dùng thử miễn phí sẽ là loại `AdaptyPaymentMode.FreeTrial`.
• `Price`: Giá chiết khấu dưới dạng số. Với dùng thử miễn phí, hãy tìm giá trị `0` ở đây.
• `LocalizedNumberOfPeriods`: một chuỗi được bản địa hóa theo ngôn ngữ của thiết bị, mô tả độ dài của ưu đãi. Ví dụ: ưu đãi dùng thử ba ngày hiển thị `"3 days"` trong trường này.
• `SubscriptionPeriod`: Ngoài ra, bạn có thể lấy thông tin chi tiết của chu kỳ ưu đãi bằng thuộc tính này. Nó hoạt động theo cách tương tự cho các ưu đãi như phần trước đã mô tả.
• `LocalizedSubscriptionPeriod`: Chu kỳ đăng ký được định dạng theo ngôn ngữ của người dùng cho phần giảm giá. | ## Tăng tốc độ lấy paywall với paywall đối tượng mặc định \{#speed-up-paywall-fetching-with-default-audience-paywall\} Thông thường, các paywall được lấy gần như ngay lập tức, vì vậy bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong những trường hợp bạn có nhiều đối tượng và paywall, và người dùng của bạn có kết nối internet yếu, việc lấy paywall có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống như vậy, bạn có thể muốn hiển thị một paywall mặc định để đảm bảo trải nghiệm người dùng mượt mà thay vì không hiển thị paywall nào cả. Để giải quyết vấn đề này, bạn có thể sử dụng phương thức `GetPaywallForDefaultAudience`, phương thức này lấy paywall của placement được chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy paywall bằng phương thức `getPaywall`, như đã được mô tả chi tiết trong phần [Lấy Paywall](#fetch-paywall) ở trên. :::warning Hãy cân nhắc sử dụng `GetPaywall` thay vì `GetPaywallForDefaultAudience`, vì phương thức sau có những hạn chế quan trọng: - **Vấn đề tương thích**: Có thể gây ra sự cố khi hỗ trợ nhiều phiên bản ứng dụng, yêu cầu thiết kế tương thích ngược hoặc chấp nhận rằng các phiên bản cũ hơn có thể hiển thị không chính xác. - **Không có cá nhân hóa**: Chỉ hiển thị nội dung cho đối tượng "All Users", không còn khả năng nhắm mục tiêu dựa trên quốc gia, attribution, hoặc thuộc tính tùy chỉnh. Nếu tốc độ lấy nhanh hơn có giá trị hơn so với những hạn chế này trong trường hợp sử dụng của bạn, hãy dùng `GetPaywallForDefaultAudience` như minh họa bên dưới. Nếu không, hãy dùng `GetPaywall` như mô tả [ở trên](#fetch-paywall). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | bắt buộc | Định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Định danh của bản địa hóa paywall. Tham số này phải là mã ngôn ngữ gồm một hoặc hai thẻ con được phân tách bằng ký tự dấu trừ (**-**). Thẻ con đầu tiên là ngôn ngữ, thẻ con thứ hai là vùng.

Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố tải dữ liệu từ máy chủ và trả về dữ liệu đã cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu đã cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất, nhưng họ sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ như thế nào. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong phiên làm việc để tránh các yêu cầu mạng là an toàn.

Lưu ý rằng cache vẫn được giữ nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ paywalls cục bộ trong hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và paywall dự phòng. Chúng tôi cũng sử dụng CDN để tải paywalls nhanh hơn và một máy chủ dự phòng độc lập trong trường hợp CDN không khả dụng. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của paywalls trong khi vẫn đảm bảo độ tin cậy ngay cả khi kết nối internet bị hạn chế.

| --- # File: present-remote-config-paywalls-unity --- --- title: "Hiển thị paywall được thiết kế bằng Remote Config trong Unity SDK" description: "Khám phá cách trình bày paywall dựa trên Remote Config trong Adapty Unity SDK để cá nhân hóa trải nghiệm người dùng." --- Nếu bạn đã tùy chỉnh paywall bằng Remote Config, bạn cần tự triển khai phần hiển thị trong code của ứng dụng để người dùng nhìn thấy nó. Vì Remote Config mang lại sự linh hoạt theo nhu cầu của bạn, bạn hoàn toàn chủ động quyết định những gì được đưa vào và giao diện paywall trông như thế nào. Chúng tôi cung cấp phương thức để lấy cấu hình Remote Config, giúp bạn tự do trình bày paywall tùy chỉnh đã được cấu hình qua Remote Config. ## Lấy Remote Config của paywall và hiển thị nó \{#get-paywall-remote-config-and-present-it\} Để lấy Remote Config của một paywall, truy cập thuộc tính `remoteConfig` và lấy các giá trị cần thiết. ```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; }); ``` Sau khi đã nhận được tất cả các giá trị cần thiết, đã đến lúc render và ghép chúng lại thành một trang có giao diện đẹp mắt. Hãy đảm bảo thiết kế phù hợp với nhiều kích thước màn hình và hướng xoay khác nhau của điện thoại, mang lại trải nghiệm mượt mà và thân thiện với người dùng trên mọi thiết bị. :::warning Hãy nhớ [ghi lại sự kiện xem paywall](present-remote-config-paywalls-unity#track-paywall-view-events) như mô tả bên dưới, để Adapty analytics có thể thu thập thông tin cho funnel và A/B test. ::: Sau khi hoàn tất việc hiển thị paywall, hãy tiếp tục thiết lập flow mua hàng. Khi người dùng thực hiện mua hàng, chỉ cần gọi `.MakePurchase()` với sản phẩm từ paywall của bạn. Để biết thêm chi tiết về phương thức `.MakePurchase()`, hãy đọc [Thực hiện mua hàng](unity-making-purchases). Chúng tôi khuyến nghị [tạo một paywall dự phòng](unity-use-fallback-paywalls). Paywall này sẽ hiển thị cho người dùng khi không có kết nối internet hoặc không có cache, đảm bảo trải nghiệm mượt mà ngay cả trong những tình huống đó. ## Ghi lại sự kiện xem paywall \{#track-paywall-view-events\} Adapty giúp bạn đo lường hiệu quả của các paywall. Trong khi chúng tôi tự động thu thập dữ liệu về các giao dịch mua hàng, việc ghi lại lượt xem paywall cần có sự tham gia của bạn vì chỉ bạn mới biết khi nào người dùng nhìn thấy paywall. Để ghi lại sự kiện xem paywall, chỉ cần gọi `.LogShowPaywall(paywall)` — sự kiện này sẽ được phản ánh trong số liệu paywall của bạn trong funnel và A/B test. :::important Không cần gọi `.LogShowPaywall(paywall)` nếu bạn đang hiển thị paywall được tạo trong [Paywall Builder](adapty-paywall-builder). ::: ```csharp showLineNumbers Adapty.LogShowPaywall(paywall, (error) => { // handle the error }); ``` Tham số của request: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:------------------------------------------------------------------| | **paywall** | bắt buộc | Một đối tượng [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | --- # File: unity-making-purchases --- --- title: "Thực hiện mua hàng trong ứng dụng với Unity SDK" description: "Hướng dẫn xử lý in-app purchase và gói đăng ký bằng Adapty." --- Hiển thị paywall trong ứng dụng là bước quan trọng để cung cấp cho người dùng quyền truy cập vào nội dung hoặc dịch vụ cao cấp. Tuy nhiên, chỉ hiển thị paywall là đủ để hỗ trợ mua hàng nếu bạn sử dụng [Paywall Builder](adapty-paywall-builder) để tuỳ chỉnh paywall của mình. Nếu bạn không dùng Paywall Builder, bạn cần sử dụng một phương thức riêng là `.makePurchase()` để hoàn tất giao dịch và mở khóa nội dung mong muốn. Phương thức này là cổng để người dùng tương tác với paywall và thực hiện giao dịch của họ. Nếu paywall của bạn có ưu đãi đang hoạt động cho sản phẩm mà người dùng đang muốn mua, Adapty sẽ tự động áp dụng ưu đãi đó tại thời điểm mua hàng. :::warning Lưu ý rằng ưu đãi giới thiệu chỉ được áp dụng tự động nếu bạn sử dụng paywall được thiết lập qua Paywall Builder. Trong các trường hợp khác, bạn cần [xác minh điều kiện của người dùng để nhận ưu đãi giới thiệu trên iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Bỏ qua bước này có thể khiến ứng dụng của bạn bị từ chối khi phát hành. Hơn nữa, điều này có thể dẫn đến việc tính giá đầy đủ cho những người dùng đủ điều kiện nhận ưu đãi giới thiệu. ::: Hãy đảm bảo bạn đã [hoàn tất cấu hình ban đầu](quickstart) mà không bỏ sót bất kỳ bước nào. Nếu không, chúng tôi không thể xác thực các giao dịch mua hàng. ## Thực hiện mua hàng \{#make-purchase\} :::note **Đang sử dụng [Paywall Builder](adapty-paywall-builder)?** Việc mua hàng được xử lý tự động — bạn có thể bỏ qua bước này. **Cần hướng dẫn từng bước?** Xem [hướng dẫn quickstart](unity-implement-paywalls-manually) để có hướng dẫn triển khai đầy đủ từ đầu đến cuối. ::: ```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; } }); } ``` Tham số yêu cầu: | Tham số | Bắt buộc | Mô tả | | :---------- | :------- |:------------------------------------------------------------------------------------------------------| | **Product** | bắt buộc | Một đối tượng [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) lấy từ paywall. | Tham số phản hồi: | Tham số | Mô tả | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

Nếu yêu cầu thành công, phản hồi sẽ chứa đối tượng này. Đối tượng [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) cung cấp thông tin đầy đủ về mức độ truy cập, gói đăng ký và các sản phẩm mua một lần của người dùng trong ứng dụng.

Kiểm tra trạng thái mức độ truy cập để xác định xem người dùng có quyền truy cập cần thiết vào ứng dụng hay không.

| :::warning **Lưu ý:** nếu bạn vẫn đang dùng StoreKit của Apple phiên bản thấp hơn v2.0 và Adapty SDK phiên bản thấp hơn v2.9.0, bạn cần cung cấp [Apple App Store shared secret](app-store-connection-configuration#step-5-enter-app-store-shared-secret) thay thế. Phương thức này hiện đã bị Apple ngừng hỗ trợ. ::: ## Thay đổi gói đăng ký khi mua hàng \{#change-subscription-when-making-a-purchase\} Khi người dùng chọn một gói đăng ký mới thay vì gia hạn gói hiện tại, cách hoạt động phụ thuộc vào cửa hàng: - Đối với App Store, gói đăng ký được tự động cập nhật trong cùng nhóm gói đăng ký. Nếu người dùng mua gói đăng ký từ một nhóm trong khi đã có gói đăng ký từ nhóm khác, cả hai gói sẽ được kích hoạt đồng thời. - Đối với Google Play, gói đăng ký không được tự động cập nhật. Bạn cần quản lý việc chuyển đổi trong code ứng dụng như mô tả bên dưới. Để thay thế gói đăng ký bằng một gói khác trên Android, gọi phương thức `.makePurchase()` với tham số bổ sung: ```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 }); ``` Tham số yêu cầu bổ sung: | Tham số | Bắt buộc | Mô tả | | :--------------------------- | :------- |:-------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | bắt buộc | Một đối tượng [`AdaptySubscriptionUpdateParameters`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_subscription_update_parameters.html). | Bạn có thể đọc thêm về gói đăng ký và các chế độ thay thế trong tài liệu Google Developer: - [Về các chế độ thay thế](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Khuyến nghị của Google về các chế độ thay thế](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Chế độ thay thế [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Lưu ý: phương thức này chỉ áp dụng cho việc nâng cấp gói đăng ký. Không hỗ trợ hạ cấp. - Chế độ thay thế [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Lưu ý: Việc thay đổi gói đăng ký thực sự chỉ xảy ra khi chu kỳ thanh toán hiện tại kết thúc. ## Đổi mã ưu đãi trên iOS \{#redeem-offer-codes-in-ios\} --- no_index: true --- import Callout from '../../../components/Callout.astro';
Về offer code Offer code cho phép bạn tặng ưu đãi hoặc dùng thử miễn phí cho những người dùng cụ thể. Không giống như các ưu đãi thông thường được áp dụng tự động, offer code được phân phối bên ngoài ứng dụng — qua email marketing, mạng xã hội, hoặc tài liệu in ấn. Người dùng đổi code bằng cách nhập vào App Store, truy cập URL đổi thưởng, hoặc qua hộp thoại trong ứng dụng. Để thiết lập offer code, mở một gói đăng ký trong App Store Connect và vào mục **Offer Codes**. Bạn có thể tạo [ba loại](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) offer code: - **Free** — gói đăng ký miễn phí trong một khoảng thời gian nhất định, sau đó gia hạn với giá đầy đủ. - **Pay as you go** — người dùng trả giá ưu đãi theo từng chu kỳ thanh toán trong một khoảng thời gian, sau đó gói đăng ký gia hạn với giá đầy đủ. - **Pay up front** — người dùng trả một lần với giá ưu đãi cho toàn bộ thời gian ưu đãi, sau đó gói đăng ký gia hạn với giá đầy đủ. Bạn không cần thêm offer code vào Adapty. Apple gắn tag mọi giao dịch trong thời gian ưu đãi với danh mục offer code. Điều này bao gồm lần đổi code đầu tiên và tất cả các lần gia hạn ưu đãi tiếp theo. Adapty phát hiện tag đó và ghi lại từng giao dịch với danh mục ưu đãi `offer_code`. Khi thời gian ưu đãi kết thúc và gói đăng ký gia hạn với giá đầy đủ, tag đó sẽ không còn nữa. Bạn có thể lọc analytics theo loại ưu đãi **Offer Code** trên [Adapty Dashboard](controls-filters-grouping-compare-proceeds). #### Xử lý sự chênh lệch doanh thu \{#revenue-discrepancy-troubleshooting\} Nếu bạn nhận thấy một giao dịch offer code xuất hiện trong Adapty với giá đầy đủ thay vì giá ưu đãi, hãy kiểm tra lại những điểm sau trong App Store Connect: - Offer code đã được cấu hình đúng giá cho tất cả các khu vực mà người dùng có thể đổi code. - Giá ưu đãi đã được thiết lập cho quốc gia hoặc khu vực cụ thể của người dùng. Apple gửi giá theo khu vực trong giao dịch. Nếu không có giá khu vực nào được cấu hình cho ưu đãi, Apple có thể gửi giá đầy đủ của sản phẩm. Bạn có thể lọc và kiểm tra các giao dịch offer code trên [Adapty Dashboard](controls-filters-grouping-compare-proceeds) theo loại ưu đãi **Offer Code** và bộ lọc **Offer Discount Type**. #### Promo code cũ (đã ngừng hỗ trợ) \{#legacy-promo-codes-deprecated\} Apple đã ngừng hỗ trợ promo code cho in-app purchase vào tháng 3 năm 2026. Offer code thay thế chúng với nhiều tính năng hơn: điều kiện tham gia có thể cấu hình, ngày hết hạn, và lên đến 1 triệu code mỗi quý. Nếu trước đây bạn sử dụng promo code cho in-app purchase, hãy chuyển sang offer code trong App Store Connect. Promo code cũ (giới hạn 100 code mỗi ứng dụng mỗi phiên bản) cấp quyền truy cập miễn phí vào một gói đăng ký. Không giống như offer code, Apple không đưa thông tin giảm giá vào giao dịch promo code — Apple gửi giá đầy đủ của sản phẩm trong biên lai. Kết quả là Adapty ghi lại các giao dịch này theo giá đầy đủ, gây ra sự chênh lệch doanh thu giữa Adapty analytics và App Store Connect. Nếu bạn thấy các giao dịch lịch sử với giá đầy đủ trong khi đáng lẽ phải miễn phí, nhiều khả năng đó là từ promo code cũ. Vì các code này đã bị ngừng hỗ trợ, hãy chuyển sang offer code để theo dõi doanh thu chính xác.
Để hiển thị trang đổi mã trong ứng dụng của bạn: ```csharp showLineNumbers Adapty.PresentCodeRedemptionSheet((error) => { // handle the error }); ``` :::danger Theo quan sát của chúng tôi, trang Offer Code Redemption trong một số ứng dụng có thể không hoạt động ổn định. Chúng tôi khuyến nghị chuyển hướng người dùng trực tiếp đến App Store. Để thực hiện điều này, bạn cần mở URL theo định dạng sau: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Quản lý prepaid plans (Android) \{#manage-prepaid-plans-android\} Nếu người dùng ứng dụng của bạn có thể mua [prepaid plans](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (ví dụ: mua gói đăng ký không tự gia hạn trong vài tháng), bạn có thể bật [pending transactions](https://developer.android.com/google/play/billing/subscriptions#pending) cho prepaid plans. ```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: "Khôi phục giao dịch mua trong ứng dụng di động với Unity SDK" description: "Tìm hiểu cách khôi phục giao dịch mua trong Adapty để đảm bảo trải nghiệm người dùng liền mạch." --- Khôi phục giao dịch mua trên cả iOS và Android là tính năng cho phép người dùng lấy lại quyền truy cập vào nội dung đã mua trước đó — chẳng hạn như gói đăng ký hoặc in-app purchase — mà không bị tính phí thêm. Tính năng này đặc biệt hữu ích cho những người đã gỡ cài đặt rồi cài lại ứng dụng, hoặc chuyển sang thiết bị mới và muốn tiếp tục sử dụng nội dung đã mua mà không phải trả tiền lại. :::note Trong các paywall được xây dựng bằng [Paywall Builder](adapty-paywall-builder), giao dịch mua sẽ được khôi phục tự động mà không cần thêm code từ phía bạn. Nếu đó là trường hợp của bạn — bạn có thể bỏ qua bước này. ::: Để khôi phục giao dịch mua khi bạn không dùng [Paywall Builder](adapty-paywall-builder) để tùy chỉnh paywall, hãy gọi phương thức `.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 } }); ``` Tham số trả về: | Tham số | Mô tả | |---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

Một đối tượng [`AdaptyProfile`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Model này chứa thông tin về mức độ truy cập, gói đăng ký và các sản phẩm mua một lần.

Kiểm tra **trạng thái mức độ truy cập** để xác định xem người dùng có quyền truy cập vào ứng dụng hay không.

| :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: --- # File: implement-observer-mode-unity --- --- title: "Implement Observer mode in Unity SDK" description: "Triển khai observer mode trong Adapty để theo dõi các sự kiện gói đăng ký của người dùng trong Unity SDK." --- Nếu bạn đã có hạ tầng mua hàng riêng và chưa sẵn sàng chuyển hoàn toàn sang Adapty, bạn có thể tìm hiểu về [Observer mode](observer-vs-full-mode). Ở dạng cơ bản, Observer Mode cung cấp analytics nâng cao và tích hợp liền mạch với các hệ thống attribution và analytics. Nếu điều này đáp ứng nhu cầu của bạn, bạn chỉ cần: 1. Bật tính năng này khi cấu hình Adapty SDK bằng cách đặt tham số `observerMode` thành `true`. Làm theo hướng dẫn cài đặt cho [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Báo cáo giao dịch](report-transactions-observer-mode-unity) từ hạ tầng mua hàng hiện có của bạn lên Adapty. ### Cài đặt Observer mode \{#observer-mode-setup\} Bật Observer mode nếu bạn tự xử lý việc mua hàng và trạng thái gói đăng ký, đồng thời sử dụng Adapty để gửi các sự kiện gói đăng ký và analytics. :::important Khi chạy ở Observer mode, Adapty SDK sẽ không đóng bất kỳ giao dịch nào, vì vậy hãy đảm bảo bạn tự xử lý điều đó. ::: ```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) { } } ``` Tham số: | Tham số | Mô tả | |--------------|-------------------------------------------------------------------------------------------------------------| | observerMode | Giá trị boolean kiểm soát [Observer mode](observer-vs-full-mode). Giá trị mặc định là `false`. | ## Sử dụng paywall của Adapty trong Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Nếu bạn cũng muốn sử dụng tính năng paywall và A/B test của Adapty, bạn hoàn toàn có thể — nhưng cần thêm một số cấu hình khi ở Observer mode. Ngoài các bước trên, bạn cần thực hiện: 1. Hiển thị paywall như thường lệ đối với [remote config paywalls](present-remote-config-paywalls-unity). 3. [Liên kết paywall](report-transactions-observer-mode-unity) với các giao dịch mua hàng. --- # File: report-transactions-observer-mode-unity --- --- title: "Báo cáo giao dịch trong Observer Mode trong Unity SDK" description: "Báo cáo giao dịch mua hàng trong Adapty Observer Mode để theo dõi thông tin người dùng và doanh thu trong Unity SDK." --- Trong Observer Mode, Adapty SDK không thể tự theo dõi các giao dịch mua hàng được thực hiện qua hệ thống mua hàng hiện có của bạn. Bạn cần báo cáo các giao dịch từ cửa hàng ứng dụng của mình. Việc thiết lập điều này **trước** khi phát hành ứng dụng là rất quan trọng để tránh lỗi trong analytics. Sử dụng `reportTransaction` để báo cáo rõ ràng từng giao dịch để Adapty nhận diện. :::warning **Đừng bỏ qua việc báo cáo giao dịch!** Nếu bạn không gọi `ReportTransaction`, Adapty sẽ không nhận diện được giao dịch, giao dịch sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp. ::: Nếu bạn sử dụng paywall của Adapty, hãy cung cấp `variationId` khi báo cáo giao dịch. Điều này liên kết giao dịch mua hàng với paywall đã kích hoạt nó, đảm bảo analytics paywall chính xác. ```csharp showLineNumbers Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` Các tham số: | Tham số | Bắt buộc | Mô tả | | ------------- | -------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | bắt buộc |
  • Đối với iOS: Mã định danh của giao dịch.
  • Đối với Android: Mã định danh dạng chuỗi `purchase.getOrderId` của giao dịch mua, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing.
| | variationId | tùy chọn | Mã định danh dạng chuỗi của biến thể. Bạn có thể lấy nó bằng thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). |
Trong Observer Mode, Adapty SDK không thể tự theo dõi các giao dịch mua hàng được thực hiện qua hệ thống mua hàng hiện có của bạn. Bạn cần báo cáo các giao dịch từ cửa hàng ứng dụng hoặc khôi phục chúng. Việc thiết lập điều này **trước** khi phát hành ứng dụng là rất quan trọng để tránh lỗi trong analytics. Sử dụng `reportTransaction` trên cả hai nền tảng để báo cáo rõ ràng từng giao dịch, và sử dụng `restorePurchases` trên Android như một bước bổ sung để đảm bảo Adapty nhận diện được giao dịch. :::warning **Đừng bỏ qua việc báo cáo giao dịch và khôi phục giao dịch mua hàng!** Nếu bạn không gọi các phương thức này, Adapty sẽ không nhận diện được giao dịch, giao dịch sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp. ::: Nếu bạn sử dụng paywall của Adapty, hãy cung cấp `PAYWALL_VARIATION_ID` khi báo cáo giao dịch. Điều này liên kết giao dịch mua hàng với paywall đã kích hoạt nó, đảm bảo analytics paywall chính xác. ```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 }); ``` Các tham số: | Tham số | Bắt buộc | Mô tả | | ------------- | -------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | bắt buộc |
  • Đối với iOS, StoreKit 1: đối tượng [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).
  • Đối với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).
  • Đối với Android: Mã định danh dạng chuỗi (`purchase.getOrderId`) của giao dịch mua, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing.
| | variationId | tùy chọn | Mã định danh dạng chuỗi của biến thể. Bạn có thể lấy nó bằng thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). |
**Báo cáo giao dịch** - Các phiên bản đến 3.1.x tự động lắng nghe các giao dịch trong App Store, nên không cần báo cáo thủ công. - Phiên bản 3.2 không hỗ trợ Observer Mode. **Báo cáo giao dịch** Sử dụng `restorePurchases` để báo cáo giao dịch cho Adapty trong Observer Mode, như được giải thích trên trang [Khôi phục giao dịch mua hàng trong mã di động](unity-restore-purchase). :::warning **Đừng bỏ qua việc báo cáo giao dịch!** Nếu bạn không gọi `restorePurchases`, Adapty sẽ không nhận diện được giao dịch, giao dịch sẽ không xuất hiện trong analytics và sẽ không được gửi đến các tích hợp. ::: **Liên kết paywall với giao dịch** Adapty SDK không thể xác định nguồn gốc của giao dịch mua hàng vì bạn là người xử lý chúng. Do đó, nếu bạn định sử dụng paywall và/hoặc A/B test trong Observer Mode, bạn cần liên kết giao dịch đến từ cửa hàng ứng dụng với paywall tương ứng trong mã ứng dụng di động của bạn. Điều này rất quan trọng cần thực hiện đúng trước khi phát hành ứng dụng, nếu không sẽ dẫn đến lỗi trong analytics. ```csharp Adapty.SetVariationForTransaction("", "", (error) => { if(error != null) { // handle the error return; } // successful binding }); ``` | Tham số | Bắt buộc | Mô tả | | ------------------------------------------------------ | -------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | bắt buộc |

Đối với iOS, StoreKit 1: đối tượng [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Đối với iOS, StoreKit 2: đối tượng [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Đối với Android: Mã định danh dạng chuỗi (purchase.getOrderId của giao dịch mua, trong đó purchase là một instance của lớp [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) trong thư viện billing.

| | variationId | bắt buộc | Mã định danh dạng chuỗi của biến thể. Bạn có thể lấy nó bằng thuộc tính `variationId` của đối tượng [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). |
--- # File: unity-troubleshoot-purchases --- --- title: "Xử lý sự cố mua hàng trong Unity SDK" description: "Xử lý sự cố mua hàng trong Unity SDK" --- Hướng dẫn này giúp bạn giải quyết các vấn đề thường gặp khi triển khai mua hàng thủ công trong Unity SDK. ## makePurchase được gọi thành công nhưng hồ sơ người dùng không được cập nhật \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Vấn đề**: Phương thức `makePurchase` hoàn tất thành công, nhưng hồ sơ người dùng và trạng thái gói đăng ký không được cập nhật trong Adapty. **Nguyên nhân**: Thường là do thiếu hoặc sai cấu hình Google Play Store. **Giải pháp**: Đảm bảo bạn đã hoàn thành tất cả [các bước thiết lập Google Play](initial-android). ## makePurchase bị gọi hai lần \{#makepurchase-is-invoked-twice\} **Vấn đề**: Phương thức `makePurchase` bị gọi nhiều lần cho cùng một giao dịch mua. **Nguyên nhân**: Thường xảy ra khi flow mua hàng bị kích hoạt nhiều lần do vấn đề quản lý trạng thái UI hoặc người dùng thao tác quá nhanh. **Giải pháp**: Đảm bảo bạn đã hoàn thành tất cả [các bước thiết lập Google Play](initial-android). ## AdaptyError.cantMakePayments trong chế độ observer \{#adaptyerrorcantmakepayments-in-observer-mode\} **Vấn đề**: Bạn nhận được `AdaptyError.cantMakePayments` khi dùng `makePurchase` trong chế độ observer. **Nguyên nhân**: Trong chế độ observer, bạn phải tự xử lý việc mua hàng ở phía mình, không dùng phương thức `makePurchase` của Adapty. **Giải pháp**: Nếu bạn dùng `makePurchase` để xử lý mua hàng, hãy tắt chế độ observer. Bạn cần chọn một trong hai: dùng `makePurchase` hoặc tự xử lý mua hàng trong chế độ observer. Xem [Triển khai chế độ Observer](implement-observer-mode-unity) để biết thêm chi tiết. ## Lỗi 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\} **Vấn đề**: Bạn nhận được lỗi billing không khả dụng từ Google Play Store. **Nguyên nhân**: Lỗi này không liên quan đến Adapty. Đây là lỗi của Google Play Billing Library, cho biết tính năng thanh toán không khả dụng trên thiết bị. **Giải pháp**: Lỗi này không liên quan đến Adapty. Bạn có thể tìm hiểu thêm trong tài liệu Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## Không tìm thấy makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Vấn đề**: Bạn gặp sự cố với `makePurchasesCompletionHandlers` không được tìm thấy. **Nguyên nhân**: Thường liên quan đến vấn đề kiểm thử sandbox. **Giải pháp**: Tạo một người dùng sandbox mới và thử lại. Cách này thường giải quyết được các vấn đề về purchase completion handler liên quan đến sandbox. ## Các vấn đề khác \{#other-issues\} **Vấn đề**: Bạn gặp các sự cố liên quan đến mua hàng khác không được đề cập ở trên. **Giải pháp**: Migrate SDK lên phiên bản mới nhất bằng cách sử dụng [hướng dẫn migration](unity-sdk-migration-guides) nếu cần. Nhiều vấn đề đã được khắc phục trong các phiên bản SDK mới hơn. --- # File: unity-identifying-users --- --- title: "Xác định người dùng trong Unity SDK" description: "Tìm hiểu cách xác định người dùng trong ứng dụng Unity của bạn với Adapty SDK." --- Adapty tạo một ID hồ sơ nội bộ cho mỗi người dùng. Tuy nhiên, nếu bạn có hệ thống xác thực riêng, bạn nên đặt Customer User ID của mình. Bạn có thể tìm người dùng theo Customer User ID của họ trong phần [Profiles](profiles-crm) và sử dụng nó trong [server-side API](getting-started-with-server-side-api), thông tin này sẽ được gửi đến tất cả các tích hợp. ### Đặt customer user ID khi cấu hình \{#setting-customer-user-id-on-configuration\} Nếu bạn có user ID trong quá trình cấu hình, chỉ cần truyền nó dưới dạng tham số `customerUserId` vào phương thức `.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 Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ### Đặt customer user ID sau khi cấu hình \{#setting-customer-user-id-after-configuration\} Nếu bạn không có user ID trong cấu hình SDK, bạn có thể đặt nó sau bất kỳ lúc nào bằng phương thức `.identify()`. Các trường hợp phổ biến nhất khi sử dụng phương thức này là sau khi đăng ký hoặc đăng nhập, khi người dùng chuyển từ người dùng ẩn danh sang người dùng đã xác thực. ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { if(error == null) { // successful identify } }); ``` Tham số yêu cầu: - **Customer User ID** (bắt buộc): chuỗi định danh người dùng. :::warning Gửi lại dữ liệu người dùng quan trọng Trong một số trường hợp, chẳng hạn khi người dùng đăng nhập lại vào tài khoản của họ, máy chủ của Adapty đã có thông tin về người dùng đó. Trong những trường hợp này, Adapty SDK sẽ tự động chuyển sang làm việc với người dùng mới. Nếu bạn đã truyền bất kỳ dữ liệu nào cho người dùng ẩn danh, chẳng hạn như thuộc tính tùy chỉnh hoặc attribution từ các mạng bên thứ ba, bạn nên gửi lại dữ liệu đó cho người dùng đã được xác định. Ngoài ra, điều quan trọng cần lưu ý là bạn nên yêu cầu lại tất cả các paywall và sản phẩm sau khi xác định người dùng, vì dữ liệu của người dùng mới có thể khác. ::: ### Đăng xuất và đăng nhập \{#logging-out-and-logging-in\} Bạn có thể đăng xuất người dùng bất kỳ lúc nào bằng cách gọi phương thức `.logout()`: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` Sau đó bạn có thể đăng nhập lại người dùng bằng phương thức `.identify()`. ## Gán `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) là một **UUID** cho phép bạn liên kết các giao dịch App Store với danh tính người dùng nội bộ của mình. StoreKit gắn token này với mọi giao dịch, để backend của bạn có thể khớp dữ liệu App Store với người dùng của mình. Hãy sử dụng một UUID ổn định được tạo cho mỗi người dùng và tái sử dụng nó cho cùng một tài khoản trên các thiết bị khác nhau. Điều này đảm bảo rằng các giao dịch mua và thông báo App Store được liên kết chính xác. Bạn có thể đặt token theo hai cách – trong quá trình kích hoạt SDK hoặc khi xác định người dùng. :::important Bạn phải luôn truyền `appAccountToken` cùng với `customerUserId`. Nếu bạn chỉ truyền token, nó sẽ không được đưa vào giao dịch. ::: ```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 } }); ``` ## Đặt obfuscated account ID (Android) \{#set-obfuscated-account-ids-android\} Google Play yêu cầu obfuscated account ID cho một số trường hợp sử dụng nhất định nhằm tăng cường quyền riêng tư và bảo mật của người dùng. Các ID này giúp Google Play xác định các giao dịch mua trong khi vẫn giữ thông tin người dùng ẩn danh, điều này đặc biệt quan trọng để ngăn chặn gian lận và phân tích. Bạn có thể cần đặt các ID này nếu ứng dụng của bạn xử lý dữ liệu người dùng nhạy cảm hoặc nếu bạn cần tuân thủ các quy định về quyền riêng tư cụ thể. Các obfuscated ID cho phép Google Play theo dõi các giao dịch mua mà không để lộ định danh người dùng thực tế. ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; // During configuration: 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) { // handle the error return; } }); // Or when identifying users Adapty.Identify("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID", (error) => { if (error == null) { // successful identify } }); ``` ## Phát hiện người dùng trên nhiều thiết bị \{#detect-users-across-devices\} --- no_index: true --- Khi SDK được kích hoạt, nó tự động đọc các quyền hiện có của người dùng từ StoreKit (iOS) hoặc Google Play Billing (Android) và đồng bộ chúng với backend của Adapty. Một gói đăng ký đang hoạt động sẽ xuất hiện trên hồ sơ người dùng Adapty mà không cần ứng dụng gọi `restorePurchases`. Điều **không** xảy ra tự động là nhận diện rằng một hồ sơ người dùng trên thiết bị mới thuộc về cùng một người dùng như hồ sơ trên thiết bị ban đầu. Adapty khớp các hồ sơ người dùng theo Customer User ID, vì vậy tính liên tục danh tính phụ thuộc vào những gì bạn sử dụng làm CUID. **Những gì Adapty có thể phát hiện trên nhiều thiết bị** | Cài đặt của bạn | Adapty phát hiện được gì | Bạn phải làm gì | | --- | --- | --- | | Customer User ID = `device_id` (không đăng nhập ứng dụng) | Thiết bị mới nhận được CUID khác và do đó có hồ sơ người dùng khác. Gói đăng ký đồng bộ với hồ sơ người dùng mới thông qua sự kiện **Access level updated**, nhưng `subscription_started` không kích hoạt — hồ sơ người dùng mới được coi là người kế thừa của giao dịch mua ban đầu. Các phân tích dựa trên `subscription_started` sẽ đếm thiếu những người dùng quay lại. | Sử dụng ID tài khoản ổn định làm Customer User ID để người dùng quay lại khớp với hồ sơ người dùng hiện có trên các thiết bị. | | Customer User ID = ID tài khoản ổn định (đăng nhập trên mọi thiết bị) | SDK tự động đồng bộ gói đăng ký khi `activate()`, và `identify()` khớp hồ sơ người dùng hiện có theo CUID. | Không cần cài đặt thêm — cả danh tính lẫn gói đăng ký đều được xử lý tự động. | | Người kế thừa Apple Family Sharing | Thành viên gia đình nhận gói đăng ký thông qua sự kiện **Access level updated** — `subscription_started` không kích hoạt. | Lắng nghe sự kiện **Access level updated**. Xem [Apple Family Sharing](apple-family-sharing) để biết ma trận sự kiện đầy đủ. | | Cùng tài khoản Apple/Google, người dùng khác nhau trong ứng dụng | Hồ sơ người dùng đầu tiên ghi lại giao dịch mua trở thành hồ sơ cha. Các hồ sơ người dùng tiếp theo thấy gói đăng ký thông qua chuỗi kế thừa, với một sự kiện **Access level updated**. | Yêu cầu đăng nhập, sau đó chọn [chế độ chia sẻ](sharing-paid-access-between-user-accounts) phù hợp với mô hình của bạn. | **Khôi phục giao dịch mua trên thiết bị mới** Hiển thị nút "Khôi phục giao dịch mua" do người dùng khởi tạo trên paywall của bạn. Apple App Review (hướng dẫn 3.1.1) yêu cầu có nút này, và nó đóng vai trò dự phòng khi quá trình đồng bộ tự động bỏ sót một trường hợp đặc biệt. Nút này nên gọi `restorePurchases` trên SDK của bạn. Không cần gọi `restorePurchases` theo chương trình khi khởi chạy lần đầu trong điều kiện sử dụng bình thường — SDK đã thực hiện tương đương khi `activate()`. Chỉ dùng các lệnh gọi theo chương trình để buộc kiểm tra biên lai mới, ví dụ khi debug trường hợp mất quyền truy cập sau khi `activate()` đã hoàn thành. --- # File: unity-setting-user-attributes --- --- title: "Đặt thuộc tính người dùng trong Unity SDK" description: "Tìm hiểu cách cập nhật thuộc tính người dùng và dữ liệu hồ sơ trong ứng dụng Unity của bạn với Adapty SDK." --- Bạn có thể đặt các thuộc tính tùy chọn như email, số điện thoại, v.v. cho người dùng ứng dụng. Sau đó, bạn có thể dùng các thuộc tính này để tạo [phân khúc](segments) người dùng hoặc xem chúng trong CRM. ### Đặt thuộc tính người dùng \{#setting-user-attributes\} Để đặt thuộc tính người dùng, hãy gọi phương thức `.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 } }); ``` Lưu ý rằng các thuộc tính bạn đã đặt trước đó bằng phương thức `updateProfile` sẽ không bị xóa. :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ### Danh sách các key được phép \{#the-allowed-keys-list\} Các key `` được phép của `AdaptyProfileParameters.Builder` và các giá trị `` tương ứng được liệt kê bên dưới: | Key | Value | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | Enum, các giá trị được phép là: `female`, `male`, `other` | | birthday | Date | ### Thuộc tính người dùng tùy chỉnh \{#custom-user-attributes\} Bạn có thể đặt các thuộc tính tùy chỉnh của riêng mình. Những thuộc tính này thường liên quan đến cách người dùng sử dụng ứng dụng. Ví dụ, với ứng dụng thể dục, đó có thể là số buổi tập mỗi tuần; với ứng dụng học ngôn ngữ, đó có thể là trình độ của người dùng, v.v. Bạn có thể dùng chúng trong các phân khúc để tạo paywall và ưu đãi có mục tiêu, đồng thời dùng trong phân tích để xác định chỉ số sản phẩm nào ảnh hưởng nhiều nhất đến doanh thu. ```csharp showLineNumbers try { builder = builder.SetCustomStringAttribute("string_key", "string_value"); builder = builder.SetCustomDoubleAttribute("double_key", 123.0f); } catch (Exception e) { // handle the exception } ``` Để xóa một key đã có, hãy dùng phương thức `.withRemoved(customAttributeForKey:)`: ```csharp showLineNumbers try { builder = builder.RemoveCustomAttribute("key_to_remove"); } catch (Exception e) { // handle the exception } ``` Đôi khi bạn cần biết những thuộc tính tùy chỉnh nào đã được thiết lập trước đó. Để làm điều này, hãy dùng trường `customAttributes` của đối tượng `AdaptyProfile`. :::warning Lưu ý rằng giá trị của `customAttributes` có thể không phải là mới nhất, vì thuộc tính người dùng có thể được gửi từ các thiết bị khác nhau bất cứ lúc nào, nên các thuộc tính trên server có thể đã thay đổi kể từ lần đồng bộ hóa cuối cùng. ::: ### Giới hạn \{#limits\} - Tối đa 30 thuộc tính tùy chỉnh mỗi người dùng - Tên key tối đa 30 ký tự. Tên key có thể bao gồm các ký tự chữ và số cùng với các ký tự sau: `_` `-` `.` - Giá trị có thể là chuỗi hoặc số thực (float) với không quá 50 ký tự. --- # File: unity-listen-subscription-changes --- --- title: "Kiểm tra trạng thái gói đăng ký trong Unity SDK" description: "Theo dõi và quản lý trạng thái gói đăng ký người dùng trong Adapty để cải thiện việc giữ chân khách hàng trong ứng dụng Unity của bạn." --- Với Adapty, việc theo dõi trạng thái gói đăng ký trở nên đơn giản hơn bao giờ hết. Bạn không cần phải tự chèn ID sản phẩm vào code. Thay vào đó, bạn có thể dễ dàng xác nhận trạng thái gói đăng ký của người dùng bằng cách kiểm tra [mức độ truy cập](access-level) đang hoạt động.
Trước khi bắt đầu kiểm tra trạng thái gói đăng ký (Nhấp để mở rộng) - Với iOS, hãy thiết lập [App Store Server Notifications](enable-app-store-server-notifications) - Với Android, hãy thiết lập [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn)
## Mức độ truy cập và đối tượng AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Mức độ truy cập là thuộc tính của đối tượng [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Chúng tôi khuyến nghị lấy hồ sơ người dùng khi ứng dụng khởi động, chẳng hạn như khi bạn [xác định người dùng](unity-identifying-users#setting-customer-user-id-on-configuration), rồi cập nhật lại mỗi khi có thay đổi. Bằng cách này, bạn có thể sử dụng đối tượng hồ sơ mà không cần gửi yêu cầu lặp đi lặp lại. Để nhận thông báo khi hồ sơ người dùng được cập nhật, hãy lắng nghe các thay đổi hồ sơ như mô tả trong phần [Lắng nghe cập nhật trạng thái gói đăng ký](#listening-for-subscription-status-updates) bên dưới. :::tip Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác. ::: ## Lấy mức độ truy cập từ máy chủ \{#retrieving-the-access-level-from-the-server\} Để lấy mức độ truy cập từ máy chủ, sử dụng phương thức `.GetProfile()`: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` Tham số phản hồi: | Tham số | Mô tả | | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile |

Một đối tượng [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Thông thường, bạn chỉ cần kiểm tra trạng thái mức độ truy cập của hồ sơ để xác định người dùng có quyền truy cập premium vào ứng dụng hay không.

Phương thức `.getProfile` cung cấp kết quả cập nhật nhất vì nó luôn cố truy vấn API. Nếu vì lý do nào đó (ví dụ: không có kết nối internet), Adapty SDK không thể lấy thông tin từ máy chủ, dữ liệu từ cache sẽ được trả về. Cũng cần lưu ý rằng Adapty SDK thường xuyên cập nhật cache `AdaptyProfile` để giữ thông tin này luôn ở trạng thái mới nhất.

| Phương thức `.getProfile()` cung cấp cho bạn hồ sơ người dùng, từ đó bạn có thể lấy trạng thái mức độ truy cập. Bạn có thể có nhiều mức độ truy cập cho mỗi ứng dụng. Ví dụ: nếu bạn có ứng dụng báo và bán gói đăng ký cho các chủ đề khác nhau một cách độc lập, bạn có thể tạo các mức độ truy cập "sports" và "science". Tuy nhiên, trong hầu hết các trường hợp, bạn chỉ cần một mức độ truy cập — khi đó, bạn có thể sử dụng mức độ truy cập mặc định "premium". Dưới đây là ví dụ kiểm tra mức độ truy cập "premium" mặc định: ```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 } }); ``` ### Lắng nghe cập nhật trạng thái gói đăng ký \{#listening-for-subscription-status-updates\} Mỗi khi gói đăng ký của người dùng thay đổi, Adapty sẽ kích hoạt một sự kiện. Để nhận thông báo từ Adapty, bạn cần thực hiện một số cấu hình bổ sung: ```csharp showLineNumbers // Extend `AdaptyEventListener ` with `OnLoadLatestProfile ` method: public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle any changes to subscription state } } ``` Adapty cũng kích hoạt một sự kiện khi ứng dụng khởi động. Trong trường hợp này, trạng thái gói đăng ký được lưu trong cache sẽ được truyền vào. ### Cache trạng thái gói đăng ký \{#subscription-status-cache\} Cache được tích hợp trong Adapty SDK lưu trữ trạng thái gói đăng ký của hồ sơ người dùng. Điều này có nghĩa là ngay cả khi máy chủ không khả dụng, dữ liệu được lưu trong cache vẫn có thể được truy cập để cung cấp thông tin về trạng thái gói đăng ký của hồ sơ. Tuy nhiên, cần lưu ý rằng không thể truy vấn dữ liệu trực tiếp từ cache. SDK định kỳ truy vấn máy chủ mỗi phút để kiểm tra các cập nhật hoặc thay đổi liên quan đến hồ sơ. Nếu có bất kỳ thay đổi nào, chẳng hạn như giao dịch mới hoặc các cập nhật khác, chúng sẽ được đồng bộ vào dữ liệu cache để giữ cho nó luôn nhất quán với máy chủ. --- # File: unity-deal-with-att --- --- title: "Xử lý ATT trong Unity SDK" description: "Bắt đầu với Adapty trên Unity để đơn giản hóa việc thiết lập và quản lý gói đăng ký." --- Nếu ứng dụng của bạn sử dụng framework AppTrackingTransparency và hiển thị yêu cầu ủy quyền theo dõi ứng dụng tới người dùng, bạn cần gửi [trạng thái ủy quyền](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) tới Adapty. ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetAppTrackingTransparencyStatus(IOSAppTrackingTransparencyStatus.Authorized); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != null) { // handle the error } }); ``` :::warning Chúng tôi khuyến nghị bạn gửi giá trị này càng sớm càng tốt khi nó thay đổi. Chỉ như vậy, dữ liệu mới được gửi kịp thời tới các tích hợp mà bạn đã cấu hình. ::: --- # File: kids-mode-unity --- --- title: "Chế độ Kids Mode trong Unity SDK" description: "Dễ dàng bật Kids Mode để tuân thủ chính sách của Apple và Google. Không thu thập IDFA, GAID hay dữ liệu quảng cáo trong Unity SDK." --- Nếu ứng dụng Unity của bạn dành cho trẻ em, bạn phải tuân thủ chính sách của [Apple](https://developer.apple.com/kids/) và [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Nếu bạn đang sử dụng Adapty SDK, một vài bước đơn giản sẽ giúp bạn cấu hình SDK để đáp ứng các chính sách này và vượt qua quá trình xét duyệt của cửa hàng ứng dụng. ## Yêu cầu cần thực hiện? \{#whats-required\} Bạn cần cấu hình Adapty SDK để tắt tính năng thu thập: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [Địa chỉ IP](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Ngoài ra, chúng tôi khuyến nghị bạn cẩn thận khi sử dụng customer user ID. User ID theo định dạng `` chắc chắn sẽ bị coi là thu thập dữ liệu cá nhân, tương tự như việc sử dụng email. Đối với Kids Mode, cách tốt nhất là sử dụng các định danh ngẫu nhiên hoặc đã được ẩn danh hóa (ví dụ: ID đã hash hoặc UUID do thiết bị tạo ra) để đảm bảo tuân thủ. ## Bật Kids Mode \{#enabling-kids-mode\} ### Cập nhật trong Adapty Dashboard \{#updates-in-the-adapty-dashboard\} Trong Adapty Dashboard, bạn cần tắt tính năng thu thập địa chỉ IP. Để thực hiện, hãy vào [App settings](https://app.adapty.io/settings/general) và nhấp vào **Disable IP address collection** trong mục **Collect users' IP address**. ### Cập nhật trong code ứng dụng di động của bạn \{#updates-in-your-mobile-app-code\} Hỗ trợ Kids Mode trong Unity sắp ra mắt! Hiện tại, bạn có thể tham khảo các hướng dẫn dành riêng cho từng nền tảng: - [Kids Mode trong iOS SDK](kids-mode) để cấu hình iOS - [Kids Mode trong Android SDK](kids-mode-android) để cấu hình Android --- # File: unity-get-onboardings --- --- title: "Lấy onboarding trong Unity SDK" description: "Tìm hiểu cách lấy onboarding trong Adapty cho Unity." --- Sau khi [bạn đã thiết kế phần giao diện cho onboarding](design-onboarding) bằng builder trong Adapty Dashboard, bạn có thể hiển thị nó trong ứng dụng Unity của mình. Bước đầu tiên trong quá trình này là lấy onboarding gắn với placement cùng cấu hình view như mô tả bên dưới. Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã cài đặt [Adapty Unity SDK](sdk-installation-unity) phiên bản 3.14.0 trở lên. 2. Bạn đã [tạo một onboarding](create-onboarding). 3. Bạn đã thêm onboarding vào một [placement](placements). ## Lấy onboarding và tạo view \{#fetch-onboarding-and-create-view\} Khi bạn tạo một [onboarding](onboardings) bằng builder no-code của chúng tôi, nó được lưu trữ dưới dạng một container chứa cấu hình mà ứng dụng của bạn cần lấy và hiển thị. Container này quản lý toàn bộ trải nghiệm — nội dung hiển thị, cách trình bày, và cách xử lý các tương tác của người dùng (như câu trả lời quiz hoặc đầu vào từ form). Container cũng tự động theo dõi các sự kiện analytics, vì vậy bạn không cần triển khai tính năng theo dõi view riêng. Để đạt hiệu suất tốt nhất, hãy lấy cấu hình onboarding sớm để hình ảnh có đủ thời gian tải xuống trước khi hiển thị cho người dùng. Để lấy một onboarding, sử dụng phương thức `GetOnboarding`: ```csharp showLineNumbers Adapty.GetOnboarding("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | bắt buộc | Định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Định danh ngôn ngữ bản địa hóa của onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai thẻ phụ được phân tách bằng ký tự gạch ngang (**-**). Thẻ phụ đầu tiên là cho ngôn ngữ, thẻ thứ hai là cho khu vực.

Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).

Xem [Localizations and locale codes](flutter-localizations-and-locale-codes) để biết thêm thông tin về mã locale và cách chúng tôi khuyến nghị sử dụng chúng.

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu được cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có không ổn định đến đâu. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong suốt phiên để tránh các yêu cầu mạng là an toàn.

Lưu ý rằng cache vẫn còn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ onboarding cục bộ ở hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và onboarding dự phòng. Chúng tôi cũng sử dụng CDN để lấy onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của các onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp kết nối internet hạn chế.

| | **loadTimeout** | mặc định: 5 giây |

Giá trị này giới hạn timeout cho phương thức này. Nếu timeout bị vượt quá, dữ liệu cache hoặc fallback cục bộ sẽ được trả về.

Lưu ý rằng trong những trường hợp hiếm gặp, phương thức này có thể timeout muộn hơn một chút so với giá trị được chỉ định trong `loadTimeout`, vì thao tác có thể bao gồm nhiều yêu cầu khác nhau bên dưới.

| Tham số phản hồi: | Tham số | Mô tả | |:----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Một đối tượng [`AdaptyOnboarding`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_onboarding.html) với: định danh và cấu hình onboarding, Remote Config, và một số thuộc tính khác. | Sau khi lấy onboarding, hãy gọi phương thức `CreateOnboardingView`. :::warning Kết quả của phương thức `CreateOnboardingView` chỉ có thể được sử dụng một lần. Nếu bạn cần sử dụng lại, hãy gọi lại phương thức `CreateOnboardingView`. Gọi nó hai lần mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers AdaptyUI.CreateOnboardingView(onboarding, (view, error) => { // handle the result }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |:---------------| :------------- |:-----------------------------------------------------------------------------| | **onboarding** | bắt buộc | Một đối tượng `AdaptyOnboarding` để lấy view cho onboarding mong muốn. | | **externalUrlsPresentation** |

tùy chọn

mặc định: `InAppBrowser`

|

Kiểm soát cách các liên kết trong onboarding được mở. Các tùy chọn có sẵn:

- `AdaptyWebPresentation.InAppBrowser` - Mở liên kết trong trình duyệt trong ứng dụng (mặc định)

- `AdaptyWebPresentation.ExternalBrowser` - Mở liên kết trong trình duyệt ngoài của thiết bị

Xem [Customize how links open in onboardings](unity-present-onboardings#customize-how-links-open-in-onboardings) để biết ví dụ sử dụng.

| Sau khi tải thành công onboarding và cấu hình view của nó, bạn có thể [hiển thị nó trong ứng dụng di động của mình](unity-present-onboardings). ## Tăng tốc lấy onboarding với onboarding đối tượng mặc định \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Thông thường, onboarding được lấy gần như ngay lập tức, vì vậy bạn không cần lo lắng về việc tăng tốc quá trình này. Tuy nhiên, trong những trường hợp bạn có nhiều đối tượng và onboarding, và người dùng của bạn có kết nối internet yếu, việc lấy onboarding có thể mất nhiều thời gian hơn mong muốn. Trong những tình huống như vậy, bạn có thể muốn hiển thị một onboarding mặc định để đảm bảo trải nghiệm người dùng mượt mà thay vì không hiển thị onboarding nào cả. Để giải quyết điều này, bạn có thể sử dụng phương thức `GetOnboardingForDefaultAudience`, phương thức này lấy onboarding của placement được chỉ định cho đối tượng **All Users**. Tuy nhiên, điều quan trọng cần hiểu là cách tiếp cận được khuyến nghị là lấy onboarding bằng phương thức `getOnboarding`, như được mô tả chi tiết trong phần [Lấy Onboarding](#fetch-onboarding) ở trên. :::warning Hãy cân nhắc sử dụng `GetOnboarding` thay vì `GetOnboardingForDefaultAudience`, vì phương thức sau có những hạn chế quan trọng: - **Vấn đề tương thích**: Có thể tạo ra sự cố khi hỗ trợ nhiều phiên bản ứng dụng, yêu cầu thiết kế tương thích ngược hoặc chấp nhận rằng các phiên bản cũ hơn có thể hiển thị không chính xác. - **Không có cá nhân hóa**: Chỉ hiển thị nội dung cho đối tượng "All Users", loại bỏ việc nhắm mục tiêu dựa trên quốc gia, attribution hoặc thuộc tính tùy chỉnh. Nếu việc lấy nhanh hơn vượt trội so với những nhược điểm này cho trường hợp sử dụng của bạn, hãy dùng `GetOnboardingForDefaultAudience` như bên dưới. Nếu không, hãy dùng `GetOnboarding` như mô tả [ở trên](#fetch-onboarding). ::: ```csharp showLineNumbers Adapty.GetOnboardingForDefaultAudience("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Tham số: | Tham số | Bắt buộc | Mô tả | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | bắt buộc | Định danh của [Placement](placements) mong muốn. Đây là giá trị bạn đã chỉ định khi tạo placement trong Adapty Dashboard. | | **locale** |

tùy chọn

mặc định: `en`

|

Định danh ngôn ngữ bản địa hóa của onboarding. Tham số này được kỳ vọng là một mã ngôn ngữ gồm một hoặc hai thẻ phụ được phân tách bằng ký tự gạch ngang (**-**). Thẻ phụ đầu tiên là cho ngôn ngữ, thẻ thứ hai là cho khu vực.

Ví dụ: `en` là tiếng Anh, `pt-br` là tiếng Bồ Đào Nha (Brazil).

| | **fetchPolicy** | mặc định: `.reloadRevalidatingCacheData` |

Theo mặc định, SDK sẽ cố tải dữ liệu từ server và trả về dữ liệu được cache trong trường hợp thất bại. Chúng tôi khuyến nghị tùy chọn này vì nó đảm bảo người dùng của bạn luôn nhận được dữ liệu mới nhất.

Tuy nhiên, nếu bạn cho rằng người dùng của mình có kết nối internet không ổn định, hãy cân nhắc sử dụng `.returnCacheDataElseLoad` để trả về dữ liệu cache nếu có. Trong trường hợp này, người dùng có thể không nhận được dữ liệu mới nhất tuyệt đối, nhưng họ sẽ có thời gian tải nhanh hơn, bất kể kết nối internet của họ có không ổn định đến đâu. Cache được cập nhật thường xuyên, vì vậy việc sử dụng nó trong suốt phiên để tránh các yêu cầu mạng là an toàn.

Lưu ý rằng cache vẫn còn nguyên khi khởi động lại ứng dụng và chỉ bị xóa khi ứng dụng được cài đặt lại hoặc thông qua việc dọn dẹp thủ công.

Adapty SDK lưu trữ onboarding cục bộ ở hai lớp: cache được cập nhật thường xuyên như mô tả ở trên và onboarding dự phòng. Chúng tôi cũng sử dụng CDN để lấy onboarding nhanh hơn và một server dự phòng độc lập trong trường hợp CDN không thể truy cập. Hệ thống này được thiết kế để đảm bảo bạn luôn nhận được phiên bản mới nhất của các onboarding trong khi vẫn đảm bảo độ tin cậy ngay cả trong trường hợp kết nối internet hạn chế.

| --- # File: unity-present-onboardings --- --- title: "Hiển thị onboarding trong Unity SDK" description: "Tìm hiểu cách hiển thị onboarding hiệu quả để tăng tỷ lệ chuyển đổi." --- Nếu bạn đã tùy chỉnh onboarding bằng builder, bạn không cần lo lắng về việc render nó trong code Unity để hiển thị cho người dùng. Onboarding đó đã chứa đầy đủ thông tin về những gì sẽ hiển thị và cách hiển thị. Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã cài đặt [Adapty Unity SDK](sdk-installation-unity) phiên bản 3.14.0 trở lên. 2. Bạn đã [tạo một onboarding](create-onboarding). 3. Bạn đã thêm onboarding vào một [placement](placements). Để hiển thị onboarding, sử dụng phương thức `view.Present()` trên `view` được tạo bởi phương thức `CreateOnboardingView`. Mỗi `view` chỉ có thể được sử dụng một lần. Nếu bạn cần hiển thị lại paywall, hãy gọi `CreateOnboardingView` thêm một lần để tạo instance `view` mới. :::warning Tái sử dụng cùng một `view` mà không tạo lại có thể dẫn đến lỗi `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((presentError) => { if (presentError != null) { // handle the error } }; ``` ## Cấu hình kiểu hiển thị trên iOS \{#configure-ios-presentation-style\} Cấu hình cách onboarding được hiển thị trên iOS bằng cách truyền tham số `iosPresentationStyle` vào phương thức `Present()`. Tham số này chấp nhận các giá trị `AdaptyUIIOSPresentationStyle.FullScreen` (mặc định) hoặc `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` ## Tùy chỉnh cách mở liên kết trong onboarding \{#customize-how-links-open-in-onboardings\} :::important Tính năng tùy chỉnh cách mở liên kết trong onboarding được hỗ trợ từ Adapty SDK v3.15 trở lên. ::: Theo mặc định, các liên kết trong onboarding mở bằng trình duyệt trong ứng dụng, mang lại trải nghiệm liền mạch bằng cách hiển thị trang web ngay trong ứng dụng mà không cần chuyển sang ứng dụng khác. Để mở liên kết bằng trình duyệt ngoài thay thế, hãy truyền `AdaptyWebPresentation.ExternalBrowser` vào phương thức `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 } }); } ); ``` Các tùy chọn có sẵn: - `AdaptyWebPresentation.InAppBrowser` - Mở liên kết bằng trình duyệt trong ứng dụng (mặc định) - `AdaptyWebPresentation.ExternalBrowser` - Mở liên kết bằng trình duyệt ngoài của thiết bị --- # File: unity-handling-onboarding-events --- --- title: "Xử lý sự kiện onboarding trong Unity SDK" description: "Xử lý các sự kiện liên quan đến onboarding trong Unity bằng Adapty." --- Trước khi bắt đầu, hãy đảm bảo rằng: 1. Bạn đã cài đặt [Adapty Unity SDK](sdk-installation-unity) phiên bản 3.14.0 trở lên. 2. Bạn đã [tạo một onboarding](create-onboarding). 3. Bạn đã thêm onboarding vào một [placement](placements). Các onboarding được cấu hình bằng builder sẽ tạo ra các sự kiện mà ứng dụng của bạn có thể phản hồi. Hãy xem cách xử lý các sự kiện này bên dưới. Để kiểm soát hoặc theo dõi các tiến trình diễn ra trên màn hình onboarding trong ứng dụng Unity của bạn, hãy triển khai interface `AdaptyOnboardingsEventsListener`. ## Hành động tùy chỉnh \{#custom-actions\} Trong builder, bạn có thể thêm hành động **custom** cho một nút và gán cho nó một ID. Sau đó, bạn có thể sử dụng ID này trong code và xử lý nó như một hành động tùy chỉnh. Ví dụ: nếu người dùng nhấn vào một nút tùy chỉnh như **Login** hoặc **Allow notifications**, phương thức `OnboardingViewOnCustomAction` sẽ được kích hoạt với tham số `actionId` là **Action ID** từ builder. Bạn có thể tự tạo ID theo ý muốn, ví dụ như "allowNotifications". Để xử lý các sự kiện onboarding, hãy triển khai interface `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) } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
## Đóng onboarding \{#closing-onboarding\} Onboarding được coi là đã đóng khi người dùng nhấn vào một nút có hành động **Close** được gán. :::important Lưu ý rằng bạn cần tự quản lý điều gì xảy ra khi người dùng đóng onboarding. Ví dụ: bạn cần dừng hiển thị chính onboarding đó. ::: Triển khai phương thức `OnboardingViewOnCloseAction` trong class của bạn: ```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 } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
## Mở paywall \{#opening-a-paywall\} :::tip Hãy xử lý sự kiện này để mở paywall nếu bạn muốn hiển thị nó bên trong onboarding. Nếu bạn muốn mở paywall sau khi onboarding đóng, có một cách đơn giản hơn – xử lý [`OnboardingViewOnCloseAction`](#closing-onboarding) và mở paywall mà không cần dựa vào dữ liệu sự kiện. ::: Cách làm liền mạch nhất khi làm việc với paywall trong onboarding là đặt action ID bằng với placement ID của paywall. Như vậy, sau sự kiện `OnboardingViewOnPaywallAction`, bạn có thể dùng placement ID để lấy và mở paywall ngay lập tức. Lưu ý rằng, trên iOS, chỉ có thể hiển thị một view (paywall hoặc onboarding) trên màn hình tại một thời điểm. Nếu bạn hiển thị paywall chồng lên onboarding, bạn không thể kiểm soát onboarding ở nền theo cách lập trình. Nếu cố dismiss onboarding, paywall sẽ bị đóng thay vào đó, khiến onboarding vẫn còn hiển thị. Để tránh điều này, hãy luôn dismiss view onboarding trước khi hiển thị paywall. ```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 } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
## Hoàn tất tải onboarding \{#finishing-loading-onboarding\} Khi onboarding tải xong, hãy triển khai phương thức `OnboardingViewDidFinishLoading`: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta ) { // handle loading completion } // ... other interface methods } ```
Ví dụ sự kiện (Nhấn để mở rộng) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
## Theo dõi điều hướng \{#tracking-navigation\} Phương thức `OnboardingViewOnAnalyticsEvent` được gọi khi các sự kiện analytics khác nhau xảy ra trong flow onboarding. Đối tượng `analyticsEvent` có thể là một trong các kiểu sau: | Kiểu | Mô tả | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Khi onboarding đã được tải | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | Khi bất kỳ màn hình nào được hiển thị | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Khi một màn hình hoàn tất. Bao gồm `ElementId` tùy chọn (định danh của phần tử đã hoàn tất) và `Reply` tùy chọn (phản hồi từ người dùng). Được kích hoạt khi người dùng thực hiện bất kỳ hành động nào để thoát khỏi màn hình. | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Khi màn hình thứ hai được hiển thị | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Được kích hoạt khi email của người dùng được thu thập qua trường nhập liệu | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Được kích hoạt khi người dùng đến màn hình có ID `final`. Nếu bạn cần sự kiện này, [hãy gán ID `final` cho màn hình cuối cùng](design-onboarding). | | `AdaptyOnboardingsAnalyticsEventUnknown` | Cho bất kỳ loại sự kiện nào không được nhận dạng. Bao gồm `Name` (tên của sự kiện không xác định) và `meta` (metadata bổ sung) | Mỗi sự kiện bao gồm thông tin `meta` chứa: | Trường | Mô tả | |------------|-------------| | `OnboardingId` | Định danh duy nhất của flow onboarding | | `ScreenClientId` | Định danh của màn hình hiện tại | | `ScreenIndex` | Vị trí của màn hình hiện tại trong flow | | `ScreensTotal` | Tổng số màn hình trong flow | Dưới đây là ví dụ về cách sử dụng sự kiện analytics để theo dõi: ```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 Phương thức `TrackEvent` là một placeholder mà bạn cần tự triển khai để gửi analytics đến dịch vụ analytics mà bạn muốn sử dụng. :::
Ví dụ sự kiện (Nhấn để mở rộng) ```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: unity-onboarding-input --- --- title: "Xử lý dữ liệu từ onboarding trong Unity SDK" description: "Lưu và sử dụng dữ liệu từ onboarding trong ứng dụng Unity của bạn với Adapty SDK." --- Khi người dùng trả lời câu hỏi trong quiz hoặc nhập dữ liệu vào trường nhập liệu, phương thức `OnboardingViewOnStateUpdatedAction` sẽ được gọi. Bạn có thể lưu hoặc xử lý loại trường đó trong code của mình. Triển khai phương thức `OnboardingViewOnStateUpdatedAction` trong class của bạn: ```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 } ``` Các tham số bao gồm: | Tham số | Mô tả | |----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | Mã định danh duy nhất cho phần tử nhập liệu. Bạn có thể dùng nó để liên kết câu hỏi với câu trả lời khi lưu dữ liệu. | | `@params` | Đối tượng dữ liệu đầu vào của người dùng. Có thể là một trong các kiểu sau. | | `AdaptyOnboardingsSelectParams` | Chọn một tùy chọn. Chứa `Id`, `Value`, `Label` | | `AdaptyOnboardingsMultiSelectParams` | Chọn nhiều tùy chọn. Chứa danh sách `Params` (mỗi phần tử có `Id`, `Value`, `Label`)
• `input`: Đối tượng với `type`, `value`
• `datePicker`: Đối tượng với `day`, `month`, `year` | | `AdaptyOnboardingsInputParams` | Trường nhập văn bản. Chứa `Input` có thể là `AdaptyOnboardingsTextInput`, `AdaptyOnboardingsEmailInput`, hoặc `AdaptyOnboardingsNumberInput` | | `AdaptyOnboardingsDatePickerParams` | Chọn ngày tháng. Chứa `Day`, `Month`, `Year` (có thể null) |
Ví dụ dữ liệu đã lưu (có thể khác trong triển khai của bạn) ```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 } } } ```
## Các trường hợp sử dụng \{#use-cases\} ### Bổ sung thông tin vào hồ sơ người dùng \{#enrich-user-profiles-with-data\} Nếu bạn muốn liên kết ngay dữ liệu đầu vào với hồ sơ người dùng và tránh hỏi lại cùng một thông tin, bạn cần [cập nhật hồ sơ người dùng](unity-setting-user-attributes) với dữ liệu đó khi xử lý hành động. Ví dụ: bạn yêu cầu người dùng nhập tên vào trường văn bản có ID là `name` và muốn đặt giá trị đó làm tên của họ. Ngoài ra, bạn yêu cầu họ nhập email vào trường `email`. Trong code ứng dụng, nó có thể trông như thế này: ```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 } ``` ### Tùy chỉnh paywall dựa trên câu trả lời \{#customize-paywalls-based-on-answers\} Bằng cách sử dụng quiz trong onboarding, bạn cũng có thể tùy chỉnh paywall hiển thị cho người dùng sau khi họ hoàn thành onboarding. Ví dụ: bạn có thể hỏi người dùng về kinh nghiệm thể thao của họ và hiển thị các CTA và sản phẩm khác nhau cho từng nhóm người dùng. 1. [Thêm quiz](onboarding-quizzes) trong onboarding builder và gán các ID có ý nghĩa cho các tùy chọn. 2. Xử lý các phản hồi từ quiz dựa trên ID của chúng và [đặt thuộc tính tùy chỉnh](unity-setting-user-attributes) cho người dùng. ```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. [Tạo phân khúc](segments) cho từng giá trị thuộc tính tùy chỉnh. 4. Tạo một [placement](placements) và thêm [đối tượng](audience) cho từng phân khúc bạn đã tạo. 5. [Hiển thị paywall](unity-paywalls) cho placement trong code ứng dụng của bạn. Nếu onboarding của bạn có nút mở paywall, hãy triển khai code paywall như một [phản hồi cho hành động của nút đó](unity-handling-onboarding-events#opening-a-paywall). --- # File: unity-sdk-call-order --- --- title: "Thứ tự gọi trong Unity SDK" description: "Tránh mất quyền truy cập premium, thiếu attribution, và lỗi #2002 không liên tục bằng cách gọi các phương thức Adapty SDK theo đúng thứ tự." --- `Adapty.Activate()` phải hoàn tất trước khi bạn gọi bất kỳ phương thức nào khác của Adapty SDK. Cho đến khi callback hoàn tất được kích hoạt, SDK chưa có trạng thái. Bất kỳ lệnh gọi nào được thực hiện trước hoặc song song với `Activate()` sẽ thất bại với lỗi [`#2002 notActivated`](unity-handle-errors#custom-network-codes). Nếu ứng dụng của bạn xác thực người dùng và bạn thu thập customer user ID sau khi khởi chạy, hãy gọi `Adapty.Identify()` tại thời điểm đó. Đừng gọi các phương thức liên quan đến hành động của người dùng cho đến khi callback của `Identify` được kích hoạt. Các lệnh gọi chạy đua với nó sẽ thất bại với lỗi [`#3006 profileWasChanged`](unity-handle-errors#custom-network-codes), hoặc rơi vào hồ sơ người dùng ẩn danh được tạo lúc kích hoạt. Khi điều này xảy ra, attribution, MMP ID như `appsflyer_id`, và quyền sở hữu cài đặt không phải lúc nào cũng được chuyển sang hồ sơ người dùng đã xác định. Nếu ứng dụng của bạn không xác thực người dùng, hãy bỏ qua `Identify` và tiếp tục làm việc với hồ sơ người dùng ẩn danh. Các SDK MMP và analytics (AppsFlyer, Adjust, Branch, PostHog) tuân theo cùng một quy tắc. Khởi tạo chúng trước và chờ callback UID của chúng trước khi gọi `Adapty.Activate`. Nếu không, MMP ID sẽ rơi vào hồ sơ người dùng ẩn danh tạm thời và không phải lúc nào cũng được chuyển sang hồ sơ người dùng đã xác định. Để biết thông tin cụ thể về AppsFlyer, xem [AppsFlyer](appsflyer). ## Thứ tự đúng \{#the-correct-order\} Lộ trình của bạn phụ thuộc vào hai yếu tố: thời điểm bạn biết customer user ID, và liệu bạn có sử dụng SDK MMP hoặc analytics hay không. - **Bước 2 và 5**: Bắt buộc với mọi ứng dụng. Kích hoạt SDK, sau đó gọi các phương thức SDK. - **Bước 1 và 3**: Chỉ bắt buộc nếu bạn tích hợp SDK MMP hoặc analytics (AppsFlyer, Adjust, Branch, PostHog). - **Bước 4**: Chỉ bắt buộc nếu ứng dụng của bạn xác thực người dùng và thu thập customer user ID sau khi khởi chạy. Nếu bạn có customer user ID ngay khi khởi chạy ứng dụng, hãy truyền trực tiếp vào `Activate()` (bước 2a). Lộ trình này không bao giờ tạo hồ sơ người dùng ẩn danh, vì vậy bước 4 là không cần thiết. | Bước | Lệnh gọi | Thời điểm | Ghi chú | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Khởi tạo SDK MMP hoặc analytics của bạn (AppsFlyer, Adjust, PostHog, Branch) | Khởi chạy ứng dụng, đầu tiên | Chờ callback UID của MMP, ví dụ `getAppsFlyerId`. | | 2a | `Adapty.Activate(builder.Build(), ...)` với `SetCustomerUserId` được đặt trên builder | Khởi chạy ứng dụng, sau bước 1, nếu bạn có customer user ID | Được khuyến nghị. Không bao giờ tạo hồ sơ người dùng ẩn danh. | | 2b | `Adapty.Activate(builder.Build(), ...)` không có `SetCustomerUserId` | Khởi chạy ứng dụng, sau bước 1, nếu bạn không có customer user ID (hoặc không bao giờ thu thập) | Adapty tạo một hồ sơ người dùng ẩn danh. | | 3 | `Adapty.SetIntegrationIdentifier(key, value, callback)` cho mỗi MMP | Sau bước 2, trước bất kỳ lệnh gọi hành động người dùng nào | Bắt buộc để MMP ID rơi đúng vào hồ sơ người dùng. | | 4 | `Adapty.Identify("YOUR_USER_ID", callback)` | Sau bước 3 (hoặc bước 2 nếu không có MMP), trước bước 5 — chỉ trên lộ trình 2b có xác thực | Chờ callback hoàn tất. Các lệnh gọi đồng thời trong `Identify` tạo ra lỗi `#3006 profileWasChanged`. | | 5 | `GetPaywall`, `GetPaywallProducts`, `RestorePurchases`, `MakePurchase`, `UpdateAttribution`, `UpdateProfile` | Sau bước 4 nếu bạn gọi `Identify`; nếu không thì sau bước 3 (hoặc bước 2 nếu không có MMP) | Các lệnh gọi này cần một hồ sơ người dùng ổn định. | :::important Bỏ qua các bước này dẫn đến mất quyền truy cập premium cho người dùng quay lại, thiếu `appsflyer_id` trên hồ sơ người dùng, và paywall được trả về cho sai đối tượng. ::: ## Cài đặt Web2app và web-funnel \{#web2app-and-web-funnel-installs\} Nếu người dùng mua hàng trên web checkout (Stripe, Paddle) và sau đó cài đặt ứng dụng native, `Activate()` đầu tiên của thiết bị sẽ tạo một hồ sơ người dùng ẩn danh mới. Hồ sơ người dùng này không được liên kết với hồ sơ người dùng web. Nếu bạn có thể xác định customer user ID trước khi khởi chạy ứng dụng (từ flow xác thực hoặc install referrer của bạn), hãy truyền trực tiếp vào `Activate()`. Nếu không, giao dịch mua trên web sẽ không hiển thị trên thiết bị cho đến khi bạn gọi `Identify("YOUR_USER_ID")` và sau đó là `RestorePurchases`. Để biết metadata cần gửi với mỗi web checkout, xem: - [Stripe](stripe) - [Paddle](paddle) --- # File: unity-optimize-paywall-fetching --- --- title: "Tối ưu hóa việc lấy paywall trong Unity SDK" description: "Lấy paywall Adapty đáng tin cậy: thời điểm, caching và các pattern dự phòng cho Unity." --- Một lần lấy paywall đáng tin cậy trong Unity cần làm được ba việc: hiển thị nhanh, trả về đúng paywall theo đối tượng, và dự phòng hợp lý khi mạng chậm. Các quy tắc dưới đây bao gồm thời điểm, caching và các pattern dự phòng để đạt được điều đó. :::tip Các quy tắc này giả định `Adapty.Activate()` và `Adapty.Identify()` đã được thực thi xong. Xem [Thứ tự gọi trong Unity SDK](unity-sdk-call-order). ::: ## Quy tắc và những lỗi thường gặp \{#rules-and-pitfalls\} | Nên làm | Không nên làm | Lý do | |---|---|---| | Lấy placement bạn sắp hiển thị. | Prefetch tất cả các placement đồng thời lúc khởi động. | Bulk prefetch chặn main thread và gây màn hình đen trong quá trình tải. | | Gọi `GetPaywall` sau khi attribution đã có thời gian xử lý xong — ví dụ, 1–2 giây sau `Activate` hoặc sau khi `OnLoadLatestProfile` kích hoạt. | Gọi `GetPaywall` trong `Awake()`. | Attribution chưa được cập nhật. Paywall sẽ được xử lý theo đối tượng mặc định và âm thầm bỏ qua các phân khúc cũng như cá nhân hóa ASA. | | Đặt `loadTimeout` và cấu hình [paywall dự phòng](fallback-paywalls) cho mọi placement. | Chờ `GetPaywall` vô thời hạn. | Không có timeout, người dùng có kết nối kém sẽ thấy màn hình trắng cho đến khi mạng phục hồi — hoặc thoát app. | Xem [Lấy paywall và sản phẩm](fetch-paywalls-and-products-unity) để tham khảo các tham số `fetchPolicy` và `loadTimeout`, và [Placements](placements) để chọn đúng placement. ## Tối ưu cho kết nối kém \{#tune-for-poor-connectivity\} Đối với những thị trường có kết nối liên tục kém (vùng nông thôn, khi đang di chuyển, khu vực bị ảnh hưởng bởi định tuyến mạng): - Đặt `fetchPolicy` thành `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` cho mọi lần lấy ngoại trừ lần đầu tiên. - Cấu hình [paywall dự phòng](fallback-paywalls) cho mọi placement trong Adapty Dashboard. - Đặt `loadTimeout` từ 3–5 giây và chấp nhận fallback khi timeout kích hoạt. - Đừng chặn việc hiển thị paywall bằng `GetProfile`. Gọi `GetPaywall` độc lập để một profile chậm không chặn giao diện người dùng. --- # File: unity-test --- --- title: "Test & release in Unity SDK" description: "Tìm hiểu cách kiểm thử và phát hành ứng dụng Unity của bạn với Adapty SDK." --- Nếu bạn đã tích hợp Adapty SDK vào ứng dụng Unity, bạn cần kiểm tra xem mọi thứ đã được thiết lập đúng chưa và các giao dịch mua có hoạt động như mong đợi trên cả hai nền tảng iOS và Android. Điều này bao gồm việc kiểm thử cả tích hợp SDK lẫn luồng mua thực tế với môi trường sandbox của Apple và môi trường kiểm thử của Google Play. ## Kiểm thử ứng dụng \{#test-your-app\} Để kiểm thử toàn diện các in-app purchase, hãy xem hướng dẫn kiểm thử theo nền tảng của chúng tôi: [Hướng dẫn kiểm thử iOS](test-purchases-in-sandbox) và [Hướng dẫn kiểm thử Android](testing-on-android). ## Chuẩn bị phát hành \{#prepare-for-release\} Trước khi gửi ứng dụng lên cửa hàng, hãy làm theo [Danh sách kiểm tra phát hành](release-checklist) để xác nhận: - Kết nối cửa hàng và thông báo máy chủ đã được cấu hình - Các giao dịch mua hoàn tất và được báo cáo về Adapty - Quyền truy cập được mở khóa và khôi phục đúng cách - Các yêu cầu về quyền riêng tư và đánh giá đã được đáp ứng --- # File: InvalidProductIdentifiers-unity --- --- title: "Sửa lỗi Code-1000 noProductIDsFound trong Unity SDK" description: "Giải quyết lỗi identifier sản phẩm không hợp lệ khi quản lý gói đăng ký trong Adapty." --- Lỗi mã 1000, `noProductIDsFound`, cho biết không có sản phẩm nào bạn yêu cầu trên paywall có thể mua được trong App Store, mặc dù chúng đã được liệt kê ở đó. Lỗi này đôi khi đi kèm với cảnh báo `InvalidProductIdentifiers`. Nếu cảnh báo xuất hiện mà không có lỗi, bạn có thể bỏ qua an toàn. Nếu bạn đang gặp lỗi `noProductIDsFound`, hãy làm theo các bước sau để giải quyết: ## Bước 1. Kiểm tra bundle ID \{#step-2-check-bundle-id\} --- no_index: true --- 1. Mở [App Store Connect](https://appstoreconnect.apple.com/apps). Chọn ứng dụng của bạn và điều hướng đến phần **General** → **App Information**. 2. Sao chép **Bundle ID** trong mục **General Information**. 3. Mở tab [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) từ menu trên cùng của Adapty và dán giá trị vừa sao chép vào trường **Bundle ID**. 4. Quay lại trang **App information** trong App Store Connect và sao chép **Apple ID** tại đó. 5. Trên trang [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) trong Adapty dashboard, dán ID vào trường **Apple app ID**. ## Bước 2. Kiểm tra sản phẩm \{#step-3-check-products\} 1. Truy cập **App Store Connect** và điều hướng đến [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) trong menu bên trái. 2. Nhấp vào tên nhóm gói đăng ký. Bạn sẽ thấy các sản phẩm được liệt kê trong phần **Subscriptions**. 3. Đảm bảo sản phẩm bạn đang kiểm tra được đánh dấu là **Ready to Submit**. 4. So sánh ID sản phẩm trong bảng với ID trong tab [**Products**](https://app.adapty.io/products) trên Adapty Dashboard. Nếu các ID không khớp, hãy sao chép ID sản phẩm từ bảng và [tạo một sản phẩm](create-product) với ID đó trong Adapty Dashboard. ## Bước 3. Kiểm tra tính khả dụng của sản phẩm \{#step-4-check-product-availability\} 1. Quay lại **App Store Connect** và mở phần **Subscriptions** tương tự. 2. Nhấp vào tên nhóm gói đăng ký để xem các sản phẩm của bạn. 3. Chọn sản phẩm bạn đang kiểm tra. 4. Cuộn đến phần **Availability** và kiểm tra xem tất cả các quốc gia và khu vực cần thiết đã được liệt kê chưa. ## Bước 4. Kiểm tra giá sản phẩm \{#step-5-check-product-prices\} 1. Truy cập lại phần **Monetization** → **Subscriptions** trong **App Store Connect**. 2. Nhấp vào tên nhóm gói đăng ký. 3. Chọn sản phẩm bạn đang kiểm tra. 4. Cuộn xuống phần **Subscription Pricing** và mở rộng phần **Current Pricing for New Subscribers**. 5. Đảm bảo tất cả các mức giá cần thiết đều được liệt kê. ## Bước 5. Kiểm tra trạng thái ứng dụng trả phí, tài khoản ngân hàng và biểu mẫu thuế còn hoạt động 1. Trên trang chủ [**App Store Connect**](https://appstoreconnect.apple.com/), nhấp vào **Business**. 2. Chọn tên công ty của bạn. 3. Cuộn xuống và kiểm tra xem **Paid Apps Agreement**, **Bank Account** và **Tax forms** của bạn đều hiển thị trạng thái **Active** hay chưa. Bằng cách làm theo các bước trên, bạn sẽ có thể giải quyết cảnh báo `InvalidProductIdentifiers` và đưa sản phẩm của mình lên cửa hàng. ## Bước 6. Tạo lại sản phẩm nếu bị kẹt Các bước 1–5 có thể đều đạt — trạng thái `Approved`, Bundle ID khớp, API key hợp lệ — nhưng SDK vẫn trả về `1000 noProductIDsFound`. Trong trường hợp đó, sản phẩm có thể bị kẹt trong registry của Apple. Registry sản phẩm của Apple đôi khi rơi vào trạng thái mà sản phẩm tồn tại trong giao diện App Store Connect nhưng không được hiển thị qua đường tra cứu StoreKit. Hãy xóa sản phẩm trong App Store Connect và tạo lại với cùng product ID. Sau khi tạo lại, hãy chờ tối đa 24 giờ để thay đổi được áp dụng. --- # File: cantMakePayments-unity --- --- title: "Cách sửa lỗi Code-1003 cantMakePayment trong Unity SDK" description: "Khắc phục lỗi không thể thanh toán khi quản lý gói đăng ký trong Adapty." --- Lỗi 1003, `cantMakePayments`, cho biết thiết bị không thể thực hiện in-app purchase. Nếu bạn gặp lỗi `cantMakePayments`, thường là do một trong các nguyên nhân sau: - Giới hạn thiết bị: Lỗi này không liên quan đến Adapty. Xem cách khắc phục bên dưới. - Cấu hình Observer mode: Không thể dùng đồng thời phương thức `makePurchase` và Observer mode. Xem phần bên dưới. ## Sự cố: Giới hạn thiết bị \{#issue-device-restrictions\} | Sự cố | Giải pháp | |-------------------------------|-----------------------------------------------------------------------------------------------------------------------------| | Giới hạn Screen Time | Tắt giới hạn In-App Purchase trong [Screen Time](https://support.apple.com/en-us/102470) | | Tài khoản bị tạm khóa | Liên hệ Apple Support để giải quyết vấn đề tài khoản | | Giới hạn khu vực | Sử dụng tài khoản App Store từ vùng được hỗ trợ | ## Sự cố: Dùng đồng thời Observer mode và makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Nếu bạn đang dùng `makePurchases` để xử lý giao dịch mua, bạn không cần dùng Observer mode. [Observer mode](observer-vs-full-mode) chỉ cần thiết khi bạn tự triển khai logic mua hàng. Vì vậy, nếu bạn đang dùng `makePurchase`, bạn có thể xóa phần kích hoạt Observer mode khỏi code khởi tạo SDK một cách an toàn. --- # File: migration-to-unity-sdk-314 --- --- title: "Migrate Adapty Unity SDK to v3.14" description: "Migrate to Adapty Unity SDK v3.14 for better performance and new monetization features." --- Adapty SDK 3.14.0 là một bản phát hành lớn mang lại một số cải tiến, tuy nhiên có thể yêu cầu bạn thực hiện một số bước migration: 1. Event listener riêng cho các sự kiện paywall. 2. Đổi tên `AdaptyUI.CreateView` thành `AdaptyUI.CreatePaywallView` và các phương thức liên quan. 3. Cập nhật phương thức `MakePurchase` để sử dụng `AdaptyPurchaseParameters` thay vì các tham số riêng lẻ. 4. Thay thế `SetFallbackPaywalls` bằng phương thức `SetFallback`. 5. Cập nhật cách truy cập thuộc tính paywall để sử dụng `AdaptyPlacement`. 6. Cập nhật cách truy cập Remote Config để sử dụng đối tượng `AdaptyRemoteConfig`. 7. Thay thế `VendorProductIds` bằng `ProductIdentifiers` trong model `AdaptyPaywall`. 8. Cập nhật fetch policy của `GetPaywall` để sử dụng `AdaptyFetchPolicy`. ## Event listener riêng cho các sự kiện paywall \{#separate-event-listener-for-paywall-events\} Nếu bạn hiển thị các paywall được thiết kế bằng [Paywall Builder](adapty-paywall-builder), các sự kiện của paywall view giờ đây sử dụng interface `AdaptyPaywallsEventsListener` và phương thức `SetPaywallsEventsListener` riêng biệt. Interface `AdaptyEventListener` cốt lõi vẫn dùng để nhận cập nhật hồ sơ người dùng và thông tin chi tiết về cài đặt. ```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 } ``` [Tìm hiểu thêm về cách xử lý sự kiện paywall](unity-handling-events). ## Đổi tên các phương thức tạo và hiển thị view \{#rename-view-creation-and-presentation-methods\} Các phương thức tạo và hiển thị view đã được đổi tên: ```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 }); }); } ``` Tương tự, phương thức dismiss cũng được đổi tên: ```diff showLineNumbers - AdaptyUI.DismissView(view, (error) => { + AdaptyUI.DismissPaywallView(view, (error) => { // handle the error }); ``` ## Cập nhật phương thức MakePurchase \{#update-makepurchase-method\} Phương thức `MakePurchase` giờ đây sử dụng `AdaptyPurchaseParameters` thay vì các đối số `subscriptionUpdateParams` và `isOfferPersonalized` riêng lẻ. Điều này giúp đảm bảo an toàn kiểu dữ liệu tốt hơn và cho phép mở rộng các tham số mua hàng trong tương lai. ```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; } }); } ``` Nếu không cần thêm tham số nào, bạn có thể dùng đơn giản như sau: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { // handle purchase result }); } ``` ## Cập nhật phương thức fallback \{#update-fallback-method\} :::important Khi nâng cấp lên Unity SDK 3.14, bạn cần tải xuống các file fallback mới từ Adapty dashboard và thay thế các file hiện có trong dự án của mình. ::: Phương thức để thiết lập fallback đã được cập nhật. Phương thức `SetFallbackPaywalls` đã được đổi tên thành `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 }); } ``` Xem ví dụ code hoàn chỉnh tại trang [Sử dụng paywall dự phòng trong Unity](unity-use-fallback-paywalls). ## Cập nhật cách truy cập thuộc tính paywall \{#update-paywall-property-access\} Các thuộc tính sau đã được chuyển từ `AdaptyPaywall` sang `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; } ``` ## Cập nhật cách truy cập Remote Config \{#update-remote-config-access\} Các thuộc tính Remote Config đã được tái cấu trúc vào đối tượng `AdaptyRemoteConfig` để tổ chức tốt hơn: ```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; } ``` ## Cập nhật cách sử dụng model AdaptyPaywall \{#update-adapty-paywall-model-usage\} Thuộc tính `VendorProductIds` đã bị deprecated và thay thế bằng `ProductIdentifiers`. Thuộc tính mới trả về các đối tượng `AdaptyProductIdentifier` thay vì chuỗi đơn giản, cung cấp thông tin sản phẩm có cấu trúc hơn. ```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 + } } ``` Đối tượng `AdaptyProductIdentifier` cho phép truy cập ID sản phẩm của nhà cung cấp thông qua thuộc tính `VendorProductId`, duy trì chức năng tương đương đồng thời cung cấp cấu trúc tốt hơn cho các cải tiến trong tương lai. ## Cập nhật fetch policy của GetPaywall \{#update-getpaywall-fetch-policy\} Kiểu tham số `fetchPolicy` trong phương thức `GetPaywall` đã được đổi từ `AdaptyPaywallFetchPolicy` sang `AdaptyPlacementFetchPolicy`. Thay đổi này giúp thống nhất cách sử dụng fetch policy trong toàn bộ 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: "Migrate Adapty Unity SDK to v3.4" description: "Migrate lên Adapty Unity SDK v3.4 để cải thiện hiệu suất và các tính năng kiếm tiền mới." --- Adapty SDK 3.4.0 là một bản phát hành lớn với những cải tiến yêu cầu bạn thực hiện các bước migration. ## Cập nhật file paywall dự phòng \{#update-fallback-paywall-files\} Cập nhật các file paywall dự phòng để đảm bảo tương thích với phiên bản SDK mới: 1. [Tải về các file paywall dự phòng đã cập nhật](fallback-paywalls) từ Adapty Dashboard. 2. [Thay thế các paywall dự phòng hiện có trong ứng dụng di động của bạn](unity-use-fallback-paywalls) bằng các file mới. ## Cập nhật cài đặt Observer Mode \{#update-implementation-of-observer-mode\} Nếu bạn đang sử dụng Observer Mode, hãy đảm bảo cập nhật cài đặt của nó. Trước đây, các phương thức khác nhau được dùng để báo cáo giao dịch cho Adapty. Trong phiên bản mới, phương thức `reportTransaction` cần được sử dụng thống nhất trên cả Android và iOS. Phương thức này báo cáo rõ ràng từng giao dịch cho Adapty, đảm bảo giao dịch được nhận diện. Nếu có sử dụng paywall, hãy truyền variation ID để liên kết giao dịch với paywall đó. :::warning **Đừng bỏ qua việc báo cáo giao dịch!** Nếu bạn không gọi `reportTransaction`, Adapty sẽ không nhận diện được giao dịch, giao dịch sẽ không xuất hiện trong analytics và sẽ không được gửi đến các integration. ::: ```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: "Migrate Adapty Unity SDK to v3.3" description: "Migrate to Adapty Unity SDK v3.3 for better performance and new monetization features." --- Adapty SDK 3.3.0 là một bản phát hành lớn mang lại một số cải tiến, tuy nhiên có thể yêu cầu bạn thực hiện một số bước migration. 1. Nâng cấp lên Adapty SDK v3.3.x. 2. Đổi tên nhiều class, thuộc tính và phương thức trong các module Adapty và AdaptyUI của Adapty SDK. 3. Từ nay, phương thức `SetLogLevel` nhận một callback làm đối số. 4. Từ nay, phương thức `PresentCodeRedemptionSheet` nhận một callback làm đối số. 5. Thay đổi cách tạo paywall view. 6. Xóa phương thức `GetProductsIntroductoryOfferEligibility`. 7. Lưu paywall dự phòng vào các file riêng biệt (một file cho mỗi nền tảng) trong `Assets/StreamingAssets/` và truyền tên file vào phương thức `SetFallbackPaywalls`. 8. Cập nhật cách thực hiện mua hàng. 9. Cập nhật xử lý sự kiện Paywall Builder. 10. Cập nhật xử lý lỗi paywall trong Paywall Builder. 11. Cập nhật cấu hình tích hợp cho Adjust, Amplitude, AppMetrica, Appsflyer, Branch, Firebase và Google Analytics, Mixpanel, OneSignal, Pushwoosh. 13. Cập nhật cài đặt Observer mode. 14. Cập nhật khởi tạo Unity plugin với lệnh gọi `Activate` tường minh. ## Nâng cấp Adapty Unity SDK lên 3.3.x \{#upgrade-adapty-unity-sdk-to-33x\} Cho đến phiên bản này, Adapty SDK là SDK cốt lõi và bắt buộc để Adapty hoạt động đúng trong ứng dụng của bạn, còn AdaptyUI SDK là SDK tùy chọn chỉ cần thiết khi bạn sử dụng Adapty Paywall Builder. Bắt đầu từ phiên bản 3.3.0, AdaptyUI SDK đã bị deprecated và AdaptyUI được tích hợp vào Adapty SDK dưới dạng một module. Do những thay đổi này, bạn cần xóa AdaptyUISDK và cài đặt lại AdaptySDK. 1. Xóa cả hai dependency **AdaptySDK** và **AdaptyUISDK** khỏi dự án của bạn. 2. Xóa các thư mục **AdaptySDK** và **AdaptyUISDK**. 3. Import lại package AdaptySDK như mô tả trong trang [Cài đặt & cấu hình Adapty SDK cho Unity](sdk-installation-unity). ## Đổi tên \{#renamings\} 1. Đổi tên trong module Adapty: | Phiên bản cũ | Phiên bản mới | | ------------------------- | ------------------------ | | 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. Đổi tên trong module AdaptyUI: | Phiên bản cũ | Phiên bản mới | | ------------------ | ------------------ | | CreatePaywallView | CreateView | | PresentPaywallView | PresentView | | DismissPaywallView | DismissView | | AdaptyUI.View | AdaptyUIView | | AdaptyUI.Action | AdaptyUIUserAction | ## Thay đổi phương thức SetLogLevel \{#change-the-setloglevel-method\} Từ nay, phương thức `SetLogLevel` nhận một callback làm đối số. ```diff showLineNumbers - Adapty.SetLogLevel(Adapty.LogLevel.Verbose); + Adapty.SetLogLevel(Adapty.LogLevel.Verbose, null); // or you can pass the callback to handle the possible error ``` ## Thay đổi phương thức PresentCodeRedemptionSheet \{#change-the-presentcoderedemptionsheet-method\} Từ nay, phương thức `PresentCodeRedemptionSheet` nhận một callback làm đối số. ```diff showLineNumbers - Adapty.PresentCodeRedemptionSheet(); + Adapty.PresentCodeRedemptionSheet(null); // or you can pass the callback to handle the possible error ``` ## Thay đổi cách tạo paywall view \{#change-how-the-paywall-view-is-created\} Để xem ví dụ code đầy đủ, hãy xem phần [Lấy cấu hình view của paywall được thiết kế bằng Paywall Builder](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 }); ``` ## Xóa phương thức GetProductsIntroductoryOfferEligibility \{#remove-the-getproductsintroductoryoffereligibility-method\} Trước Adapty iOS SDK 3.3.0, đối tượng sản phẩm luôn bao gồm các ưu đãi, bất kể người dùng có đủ điều kiện hay không. Bạn phải kiểm tra điều kiện thủ công trước khi sử dụng ưu đãi. Hiện tại, đối tượng sản phẩm chỉ bao gồm ưu đãi nếu người dùng đủ điều kiện. Điều này có nghĩa là bạn không cần kiểm tra điều kiện nữa — nếu có ưu đãi, người dùng đã đủ điều kiện. ## Cập nhật phương thức cung cấp paywall dự phòng \{#update-method-for-providing-fallback-paywalls\} Cho đến phiên bản này, các paywall dự phòng được truyền dưới dạng JSON đã được serialize. Bắt đầu từ v3.3.0, cơ chế đã thay đổi: 1. Lưu các paywall dự phòng vào file trong `/Assets/StreamingAssets/`, 1 file cho Android và 1 file cho iOS. 2. Truyền tên file vào phương thức `SetFallbackPaywalls`. Code của bạn sẽ thay đổi như sau: ```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 }); } ``` Xem ví dụ code đầy đủ trong trang [Sử dụng paywall dự phòng trong Unity](unity-use-fallback-paywalls). ## Cập nhật cách thực hiện mua hàng \{#update-making-purchase\} Trước đây, các giao dịch bị hủy và đang chờ xử lý được coi là lỗi và trả về các mã `PaymentCancelled` và `PendingPurchase` tương ứng. Hiện tại, một class `AdaptyPurchaseResultType` mới được sử dụng để xử lý các giao dịch bị hủy, thành công và đang chờ xử lý. Cập nhật code mua hàng theo cách sau: ```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; } }); } ``` Xem ví dụ code đầy đủ trong trang [Thực hiện mua hàng trong ứng dụng](unity-making-purchases). ## Cập nhật xử lý sự kiện Paywall Builder \{#update-handling-of-paywall-builder-events\} Các giao dịch bị hủy và đang chờ xử lý không còn được coi là lỗi nữa, tất cả các trường hợp này được xử lý bằng phương thức `PaywallViewDidFinishPurchase`. 1. Xóa xử lý sự kiện mua hàng bị hủy. 2. Cập nhật xử lý sự kiện mua hàng thành công như sau: ```diff showLineNumbers - public void OnFinishPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Profile profile - ) { } + public void PaywallViewDidFinishPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyPurchaseResult purchasedResult + ) { } ``` 3. Cập nhật xử lý các action: ```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. Cập nhật xử lý khi bắt đầu mua hàng: ```diff showLineNumbers - public void OnSelectProduct( - AdaptyUI.View view, - Adapty.PaywallProduct product - ) { } + public void PaywallViewDidSelectProduct( + AdaptyUIView view, + string productId + ) { } ``` 5. Cập nhật xử lý khi mua hàng thất bại: ```diff showLineNumbers - public void OnFailPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Error error - ) { } + public void PaywallViewDidFailPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyError error + ) { } ``` 6. Cập nhật xử lý sự kiện khôi phục thành công: ```diff showLineNumbers - public void OnFailRestore( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailRestore( + AdaptyUIView view, + AdaptyError error + ) { } ``` Xem ví dụ code đầy đủ trong trang [Xử lý sự kiện paywall](unity-handling-events). ## Cập nhật xử lý lỗi paywall trong Paywall Builder \{#update-handling-of-paywall-builder-paywall-errors\} Cách xử lý lỗi cũng đã thay đổi, hãy cập nhật code của bạn theo hướng dẫn bên dưới. 1. Cập nhật xử lý lỗi tải sản phẩm: ```diff showLineNumbers - public void OnFailLoadingProducts( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailLoadingProducts( + AdaptyUIView view, + AdaptyError error + ) { } ``` 2. Cập nhật xử lý lỗi rendering: ```diff showLineNumbers - public void OnFailRendering( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailRendering( + AdaptyUIView view, + AdaptyError error + ) { } ``` ## Cập nhật cấu hình SDK tích hợp bên thứ ba \{#update-third-party-integration-sdk-configuration\} Bắt đầu từ Adapty Unity SDK 3.3.0, chúng tôi đã cập nhật public API cho phương thức `updateAttribution`. Trước đây, nó nhận một dictionary `[AnyHashable: Any]`, cho phép bạn truyền trực tiếp các đối tượng attribution từ các dịch vụ khác nhau. Bây giờ, nó yêu cầu `[String: any Sendable]`, vì vậy bạn cần chuyển đổi các đối tượng attribution trước khi truyền vào. Để đảm bảo các tích hợp hoạt động đúng với Adapty Unity SDK 3.3.0 trở lên, hãy cập nhật cấu hình SDK cho các tích hợp sau theo mô tả trong các phần bên dưới. ### Adjust Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - using static AdaptySDK.Adapty; using AdaptySDK; Adjust.GetAdid((adid) => { - Adjust.GetAttribution((attribution) => { - Dictionary data = new Dictionary(); - - 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 data = new Dictionary(); 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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Amplitude](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp AppMetrica](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp AppsFlyer](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Branch](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 parameters, string error) { + string attributionString = JsonUtility.ToJson(parameters); + + Adapty.UpdateAttribution( + attributionString, + "branch", + (error) => { + // handle the error + }); + }); ``` ### Firebase và Google Analytics Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Firebase và Google Analytics](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Mixpanel](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp OneSignal](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 Cập nhật code ứng dụng của bạn như bên dưới. Để xem ví dụ code đầy đủ, hãy xem phần [Cấu hình SDK cho tích hợp Pushwoosh](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 + }); ``` ## Cập nhật cài đặt Observer mode \{#update-observer-mode-implementation\} Cập nhật cách bạn liên kết các paywall với giao dịch. Trước đây, bạn dùng phương thức `setVariationId` để gán `variationId`. Bây giờ, bạn có thể đưa `variationId` trực tiếp khi ghi lại giao dịch bằng phương thức `reportTransaction` mới. Xem ví dụ code đầy đủ trong trang [Liên kết paywall với giao dịch mua hàng trong Observer mode](report-transactions-observer-mode-unity). ```diff showLineNumbers // every time when calling transaction.finish() - Adapty.SetVariationForTransaction("", "", (error) => { - if(error != null) { - // handle the error - return; - } - - // successful binding - }); + Adapty.ReportTransaction( + "YOUR_TRANSACTION_ID", + "PAYWALL_VARIATION_ID", // optional + (error) => { + // handle the error + }); ``` ## Cập nhật khởi tạo Unity plugin \{#update-the-unity-plugin-initialization\} Bắt đầu từ Adapty Unity SDK 3.3.0, việc gọi tường minh phương thức `Activate` trong quá trình khởi tạo plugin là bắt buộc: ```csharp showLineNumbers Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: migration-to-unity-sdk-v3 --- --- title: "Migrate Adapty Unity SDK to v3.0" description: "Migrate to Adapty Unity SDK v3.0 for better performance and new monetization features." --- Adapty SDK v3.0 mang đến hỗ trợ cho [Adapty Paywall Builder](adapty-paywall-builder) — phiên bản mới của công cụ tạo paywall trực quan không cần code. Với tính linh hoạt tối đa và khả năng thiết kế phong phú, các paywall của bạn sẽ trở nên hiệu quả và sinh lời hơn. ## Quy trình nâng cấp \{#upgrade-process\} Quy trình nâng cấp cho Unity bao gồm các bước tương tự như các nền tảng khác: 1. Nâng cấp lên Adapty SDK v3.x 2. Migrate các paywall hiện có sang Paywall Builder mới Để biết hướng dẫn migration chi tiết dành riêng cho Unity, vui lòng tham khảo [hướng dẫn cài đặt Unity SDK](sdk-installation-unity) và làm theo các bước migration chung được trình bày trong hướng dẫn migration chính. --- # File: unity-migration-guide --- --- title: "Hướng dẫn migration SDK" description: "Hướng dẫn migration cho Unity Adapty SDK." --- ## Hướng dẫn Migration \{#migration-guides\} ### [Hướng dẫn migration lên Unity Adapty SDK 3.x](unity-sdk-migration-guides) \{#migration-guide-to-unity-adapty-sdk-3x\} Tìm hiểu cách migrate từ các phiên bản cũ lên Unity Adapty SDK 3.x. ## Tính năng mới \{#whats-new\} ### Phiên bản 3.x \{#version-3x\} - Cải tiến cách hiển thị paywall - Xử lý lỗi tốt hơn - Hỗ trợ C# tốt hơn - Tối ưu hóa hiệu suất ### Phiên bản 2.x \{#version-2x\} - Tính năng onboarding mới - Cải tiến analytics - Cải thiện flow mua hàng - Sửa lỗi và cải thiện độ ổn định ## Thay đổi Breaking \{#breaking-changes\} ### Phiên bản 3.x \{#version-3x-breaking\} - Cập nhật observer API - Thay đổi phương thức hiển thị paywall - Thay đổi cấu trúc xử lý lỗi ### Phiên bản 2.x \{#version-2x-breaking\} - Cập nhật onboarding API - Thay đổi cấu trúc hồ sơ người dùng - Thay đổi flow mua hàng ## Danh sách kiểm tra Migration \{#migration-checklist\} Khi migrate lên phiên bản mới: - [ ] Xem xét các thay đổi breaking - [ ] Cập nhật các lời gọi API - [ ] Kiểm tra toàn bộ chức năng - [ ] Cập nhật xử lý lỗi - [ ] Xác minh theo dõi analytics - [ ] Kiểm tra trên tất cả các nền tảng --- # End of Documentation _Generated on: 2026-07-01T16:30:13.940Z_ _Successfully processed: 41/41 files_